GIS奮闘記 (original) (raw)

明けましておめでとうございます。本年も GIS奮闘記をよろしくお願いいたします。

2022年末に Microsoft が4780万kmを超える道路データ(GeoJSON形式)の公開をアナウンスしました。本年一発目のエントリーではそのデータを可視化してみようと思います。

公開されている道路データについて

深層学習モデルを使ってBing Maps画像(2020年〜2022年に撮影されたMaxarやAirbusの衛星画像含む)の道路を判読して作成したそうです。OpenStreetMapに無いデータも含まれているみたいですね。

可視化する道路データ

2023年1月7日時点では日本の道路データは公開されていませんでした(少なくとも東アジアの国の道路データは見つかりませんでした)。残念ですが、今回はオーストラリアの道路データを可視化してみようと思います。

実行環境

macOS 13.0.1
Python 3.9.6
QGIS3.16

手順

  1. 道路データをダウンロード
  2. オーストラリアの道路データを抽出
  3. 抽出したデータを可視化

道路データをダウンロード

道路データは Githubでダウンロードできます。

オーストラリアのデータが入っている 「Oceania」 のデータ(Oceania-Full.zip.)をダウンロードします。

zip の中には tsv が格納されています。あれっ、GeoJSONじゃないの?!と思ったのですが、どうやら tsvファイルの2列目が以下のように GeoJSON 形式になっているみたいです(1列目は国名)。なんとも微妙な形で公開するなとぼやきつつも必要なデータ(2列目)だけ抜き取ろうと思います。

留意点

これらの道路データには属性が付与されていないことがわかりました。道路といっても一般道や高速道路、一方通行や進行方向など様々な属性があるかと思います。それらの属性がデータで判別できないとなると当該データは背景として使うくらいしか用途がないかなと思います。

2. オーストラリアの道路データを抽出

以下のようにオーストラリアの道路データのみを抽出して GeoJSON ファイルとして出力します。

import pandas as pd

output_path = "Oceania-Full.geojson"

df = pd.read_csv("Oceania-Full.tsv", sep='\t')

road_list = [] for index, row in df.iterrows():

if row[0] == 'AUS':

    
    road_list.append(row[1])

output1 = '{ "type": "FeatureCollection", "features": ' output2 = road_list output3 = '}' output = output1 + str(output2) + output3

with open(output_path, 'w') as f: f.write(output.replace("'", ''))

以下のような GeoJSON ファイルが出力されます。

3. 抽出したデータを可視化

QGIS で出力した GeoJSON を読み込みます。わかりやすいように背景図をつけました。

このようにオーストラリアの道路データが可視化されたことがわかります。

さいごに

いかがでしたでしょうか?Microsoft はあまり GIS のイメージが無かったのですが、調べてみると意外と GIS もやっているみたいですね。興味のある方はぜひデータを眺めてみてください。本日は以上です。

さて、本日は Python を使って無相関検定をしてみようと思います。以下二つのエントリーで「相関係数の算出」と「データの散布図へのプロット」を実施しましたが、使用した二つの変数(築年数と販売価格)に対して無相関検定を実施します。

www.gis-py.com

www.gis-py.com

相関と仮説検定

データ分析を行なっていると、「たまたま」相関係数の値が大きくなってしまうことがよくあります。算出した相関係数が「たまたま」出たものなのか、それとも相関係数に有意性があるのかを確かめる手法が仮説検定です。今回は「無相関検定」というものを使ってみます。

無相関検定とは

「相関係数が r = 0である」ことを帰無仮説として採用して、「本当は相関が無いのに、たまたまデータに見られた相関係数の値が発生する確率」を p値として計算します。この値が事前に設定した有意水準を下回っていれば、対立仮説となる「相関係数が0ではない」という主張を支持することができるのが無相関検定です。

つまり、無相関検定は二つの変数間に相関が無いと仮定して、標本にみられる相関がどれだけ「たまたま」発生するかを評価します。

p値とは

統計的仮説検定において、帰無仮説の元で検定統計量がその値となる確率のこと。P値が小さいほど、検定統計量がその値となることはあまり起こりえないことを意味する。一般的にP値が5%または1%以下の場合に帰無仮説を偽として棄却し、対立仮説を採択する。(出典:統計WEB)

有意水準とは

有意水準は、検定において帰無仮説を設定したときにその帰無仮説を棄却する基準となる確率のことです。(アルファ)で表され、5%(0.05)や1%(0.01)といった値がよく使われます。有意水準は検定を行う前に設定しておきます。(出典:統計WEB)

本エントリーでは有意水準を5%(0.05)とします。

相関係数r と p 値のパターンとその解釈

  1. r の絶対値が大きく、p値も有意水準を下回っている場合
    二つの変数の間に何らかの関係があると解釈します。
  2. r の絶対値は小さいが、p値は有意水準を下回っている場合
    相関は存在すると考えられるが、関係の強さは弱いと解釈します。
  3. p値が有意水準を上回っている場合
    標本から得られた r の値は相関の無い変数の間からでもたまたま生じうる程度のものなので、相関関係があるかどうかはこのデータからは結論づけられないと解釈します。

使用するデータ

以下のエントリーで収集した SUUMO の中古物件情報を使います。

www.gis-py.com

相関関係を分析する属性

SUUMO の中古物件情報の以下の属性を使います。

実行環境

macOS 12.3.1
Python 3.9.6

サンプルコード

「築年数」「販売価格」の二変数に対して無相関検定を実施するサンプルです。

import pandas as pd from scipy.stats import pearsonr

df = pd.read_csv("property.csv")

age_of_building = df["築年数"].values price = df["販売価格"].values

res = pearsonr(age_of_building, price) r_value = res[0] p_value = res[1]

print('相関係数:', r_value) print('p値:', p_value)

p値が有意水準(今回は0.05)を上回っているため、帰無仮説は棄却せずに二つの変数に相関関係があるかどうかはこのデータからは結論づけられないと言えるかと思います。

さいごに

最近は GIS ではなく統計に関するエントリーが増えてきてしまっていますが、GIS の世界でも統計的な処理をする機会は少なくないかと思います。興味のある方は相関分析など統計的な処理にチャレンジしてみてください。

さて、本日は相関分析に使うデータを散布図で可視化してみようと思います。前回エントリーでは相関係数の計算を行いました。今回は相関係数算出に使ったデータを散布図で可視化してみようと思います。

散布図とは

散布図は、横軸と縦軸にそれぞれ別の量をとり、データが当てはまるところに点を打って示す(「プロットする」といいます。)グラフです。2つの量に関係があるかどうかをみるのに非常に便利なグラフです。(出典:なるほど統計学園)

使用するデータ

前回エントリーと同じく、以下のエントリーで収集した SUUMO の中古物件情報を使います。

www.gis-py.com

相関関係を分析する属性

SUUMO の中古物件情報の以下の属性を使います。

二つの属性に相関関係がある場合

築年数が高くなれば販売価格が高くなり、築年数が低くなれば販売価格が低くなります。

使用するライブラリ

Pythonでグラフを作るためのライブラリである seaborn を使います。

作成する散布図

以下二つの散布図を作成します。

実行環境

macOS 12.3.1
Python 3.9.6

サンプルコード

散布図

中古一戸建てとマンションで色分けし、プロットしたポイントに対してラベリングを行なっています。

import seaborn as sns import pandas as pd from matplotlib import pyplot as plt import numpy as np import japanize_matplotlib

jitter_value = 2

def label_point(x, y, val, ax): a = pd.concat({"x": jitter(x, jitter_value), "y": jitter(y, jitter_value), "val": val}, axis=1) for i, point in a.iterrows(): ax.text(point["x"]+.02, point["y"], str(point["val"]))

def jitter(values, j): return values + np.random.normal(j, 0.1, values.shape)

df_top = pd.read_csv("property.csv")[0:20] df_bottom = pd.read_csv("property.csv")[3400:3420] df_correlation = df_top.append(df_bottom)

plt.figure(figsize = (20, 10))

ax = sns.scatterplot(data = df_correlation, x = jitter(df_correlation["販売価格"], jitter_value), y = jitter(df_correlation["築年数"], jitter_value), hue = "カテゴリ")

ax.set_title("SUUMO中古物件相関分析", pad = 8, fontsize = 20, color = 'blue')

label_point(df_correlation["販売価格"], df_correlation["築年数"], df_correlation["所在地"], ax)

plt.savefig("test.png")

plt.show()

このように散布図が作成されました。

散布図+回帰線

lmplot というメソッドを使うことで「回帰モデルの傾向線」を可視化することができます。

import seaborn as sns import pandas as pd from matplotlib import pyplot as plt import numpy as np import japanize_matplotlib

df_top = pd.read_csv("property.csv")[0:20] df_bottom = pd.read_csv("property.csv")[3400:3420] df_correlation = df_top.append(df_bottom)

plt.figure(figsize = (20, 10))

ax = sns.lmplot(x = "販売価格", y = "築年数", hue = "カテゴリ", data = df_correlation, x_jitter = 2, y_jitter = 2, height = 10, aspect = 2, legend = False)

ax.set(title = "SUUMO中古物件相関分析") plt.legend(loc = 'upper right')

for ax in ax.axes.ravel(): for t, x, y in df_correlation[['所在地', '販売価格', '築年数']].values.tolist(): ax.annotate(t, (x, y))

plt.savefig("test.png")

plt.show()

傾向線付きの散布図が作成されました(どうしてもタイトルが切れてしまいますが、とりあえず作成はできました)。中古一戸建てに関しては販売価格と築年数は負の相関関係にありますが、中古マンションに関しては正の相関関係にあることがわかります。

さいごに

このような感じで散布図を作ることができます。相関分析を実施する際は相関係数の算出だけではなく散布図や傾向線の作成は必須かと思いますので、興味のある方はぜひ試してみてください。本日は以上です。

さて、本日は Python で相関係数を算出してみようと思います。今の業務で Tableau を使って相関係数を計算しているのですが、計算対象のデータパターンが多くて(相関係数を算出する対象が何十、何百パターンもある)、 Python で一括処理をしたいと思っているというのが背景にあります。

相関係数とは

相関係数とは、二つの変数の関係性の強さを示す数値である。変数とはたとえば人の体温や身長、体重、気温や株価など、刻々と変化する様々な数値のことである。特定の二つの変数の関係性の強さを表す場合に相関係数が用いられ、相関係数を導き出す手法が相関分析である。(出典:DIAMOND Chain Store 相関係数とは?相関係数を知ることで何がわかる?Excelを使った計算方法から、実例まで解説!)

正の相関と負の相関

相関係数は-1から1の値をとり1に近いほど二つの変数の相関関係が強いと言えます。(以下画像は「5分でわかる!「正の相関」「負の相関」と「相関係数」」より抜粋)

使用するデータ

以下のエントリーで収集した SUUMO の中古物件情報を使います。

www.gis-py.com

相関関係を分析する属性

SUUMO の中古物件情報の以下の属性を使います。

二つの属性に相関関係がある場合

築年数が高くなれば販売価格が高くなり、築年数が低くなれば販売価格が低くなります。

実行環境

macOS 12.3.1
Python 3.9.6

サンプルコード

築年数と販売価格の相関係数を算出するサンプルコードです。

import csv import pandas as pd

input_file = "property.csv" attribute_list = []

with open(input_file, 'r', encoding='utf-8') as f: header = next(csv.reader(f)) reader = csv.reader(f)

price_list = []
building_area_list = []
for row in reader:
    
    
    price_list.append(int(row[1]))

    
    building_area_list.append(int(row[12]))

price_ser = pd.Series(price_list) building_area_ser = pd.Series(building_area_list)

res = price_ser.corr(building_area_ser)

print(res)

相関係数は約-0.32という結果になりました。そのため、築年数と販売価格には負の相関関係にあると言えます。

さいごに

大量のデータパターン(相関係数を算出する対象が何十、何百パターンもある場合)での相関分析をする際は Python での相関係数の算出が役に立ちそうですね。ちなみにですが、 GIS の世界にも空間相関分析というものがあるみたいですね。本日は以上です。

さて、本日は気象庁の天気予報 JSON で遊んでみようと思います。気象庁のデータに関しては以下のエントリーでも扱いましたが、この時はスクレイピングを行いました。今回は公開されている JSON を活用して天気情報を取得してみたいと思います。

www.gis-py.com

気象庁天気予報 JSON とは

気象庁のWebサイトのリニューアルに伴い、天気予報情報がJSON形式で取得できるようになりました。正式公開の WebAPI ではないのですが、「政府標準利用規約に準拠して利用可能」なようです。

気象庁天気予報 JSON の種類

  1. エリアコード
  2. コンテンツ種別
  3. 各地の気象情報

今回は「3.各地の気象情報」を利用します。以下のようなURLにリクエストを投げるとエリアコードに応じた結果が取得できます。

東京の場合はhttps://www.jma.go.jp/bosai/forecast/data/forecast/130000.json

https://www.jma.go.jp/bosai/forecast/data/forecast/エリアコード.json

東京のエリアコードでリクエストを投げると以下のような結果になります。東京の各エリアに関して三日間(以下の場合3/30,3/30,4/1)の天気情報を取得することができます。

[{'publishingOffice': '気象庁', 'reportDatetime': '2022-03-30T17:00:00+09:00', 'timeSeries': [{'timeDefines': ['2022-03-30T17:00:00+09:00', '2022-03-31T00:00:00+09:00', '2022-04-01T00:00:00+09:00'], 'areas': [{'area': {'name': '東京地方', 'code': '130010'}, 'weatherCodes': ['201', '212', '201'], 'weathers': ['くもり\u3000夜のはじめ頃\u3000晴れ', 'くもり\u3000夜\u3000雨', 'くもり\u3000時々\u3000晴れ'], 'winds': ['南の風\u300023区西部\u3000では\u3000南の風\u3000やや強く', '南の風\u3000後\u3000北東の風\u300023区西部\u3000では\u3000後\u3000北東の風\u3000やや強く', '北の風'], 'waves': ['1メートル\u3000後\u30000.5メートル', '0.5メートル\u3000後\u30001メートル', '1メートル']}, {'area': {'name': '伊豆諸島北部', 'code': '130020'}, 'weatherCodes': ['200', '212', '313'], 'weathers': ['くもり\u3000所により\u3000雨', 'くもり\u3000夜遅く\u3000雨', '雨\u3000後\u3000くもり'], 'winds': ['南の風\u3000後\u3000南西の風', '南西の風\u3000後\u3000やや強く', '北東の風\u3000やや強く'], 'waves': ['2メートル', '2メートル', '2.5メートル']}, {'area': {'name': '伊豆諸島南部', 'code': '130030'}, 'weatherCodes': ['200', '212', '313'], 'weathers': ['くもり\u3000所により\u3000雨', 'くもり\u3000夜遅く\u3000雨', '雨\u3000後\u3000くもり'], 'winds': ['南の風', '西の風\u3000後\u3000やや強く', '北東の風\u3000やや強く'], 'waves': ['2.5メートル\u3000ただし\u3000三宅島\u3000では\u30002メートル', '2メートル', '2.5メートル\u3000後\u30004メートル\u3000ただし\u3000三宅島\u3000では\u30002.5メートル\u3000後\u30003メートル']}, --- 省略 ---

今回のゴール

全国の気象庁天気予報 JSON の中から「天気」、「風」、「波」の情報を CSVとして出力(降水確率や気温などは今回は対象外としています)

実行環境

macOS 11.3.1
Python 3.9.6

サンプルコード

import requests import json import csv

area_dic = {'北海道/釧路':'014100', '北海道/旭川':'012000', '北海道/札幌':'016000', '青森県':'020000', '岩手県':'030000', '宮城県':'040000', '秋田県':'050000', '山形県':'060000', '福島県':'070000', '茨城県':'080000', '栃木県':'090000', '群馬県':'100000', '埼玉県':'110000', '千葉県':'120000', '東京都':'130000', '神奈川県':'140000', '新潟県':'150000', '富山県':'160000', '石川県':'170000', '福井県':'180000', '山梨県':'190000', '長野県':'200000', '岐阜県':'210000', '静岡県':'220000', '愛知県':'230000', '三重県':'240000', '滋賀県':'250000', '京都府':'260000', '大阪府':'270000', '兵庫県':'280000', '奈良県':'290000', '和歌山県':'300000', '鳥取県':'310000', '島根県':'320000', '岡山県':'330000', '広島県':'340000', '山口県':'350000', '徳島県':'360000', '香川県':'370000', '愛媛県':'380000', '高知県':'390000', '福岡県':'400000', '佐賀県':'410000', '長崎県':'420000', '熊本県':'430000', '大分県':'440000', '宮崎県':'450000', '鹿児島県':'460100', '沖縄県/那覇':'471000', '沖縄県/石垣':'474000' }

output_file = "weather_report.csv"

header = ["都道府県","データ配信元","報告日時", "地方名","予報日時","天気","風","波"]

def main(): make_csv()

def make_csv(): with open(output_file, 'w', encoding='utf-8') as f: writer = csv.writer(f, lineterminator="\n") writer.writerow(header)

    write_lists = get_info()

    
    writer.writerows(write_lists)

def get_info(): write_lists = [] base_url = "https://www.jma.go.jp/bosai/forecast/data/forecast/" for k, v in area_dic.items():

    if k.find("/"):
        prefecture = k[0:k.find("/")]
    else:
        prefecture = k

    url = base_url + v + ".json"

    res = requests.get(url).json()

    for re in res:
        publishingOffice = re["publishingOffice"]
        reportDatetime = re["reportDatetime"]

        timeSeries = re["timeSeries"]

        for time in timeSeries:
            
            if 'pops' in time["areas"][0]:
                pass
            elif 'temps' in time["areas"][0]:
                pass
            elif 'tempsMax' in time["areas"][0]:
                pass
            else:
                for i in range(len(time["areas"])):

                    local_name = time["areas"][i]["area"]["name"]

                    for j in range(len(timeSeries[0]["timeDefines"])):

                        if 'weathers' not in time["areas"][i]:
                            weather = ""
                        else:
                            weather = time["areas"][i]["weathers"][j]

                        if 'winds' not in time["areas"][i]:
                            wind = ""
                        else:
                            wind = time["areas"][i]["winds"][j]

                        if 'waves' not in time["areas"][i]:
                            wave = ""
                        else:
                            wave = time["areas"][i]["waves"][j]

                        timeDefine = time["timeDefines"][j]

                        
                        write_list = []
                        write_list.append(prefecture)
                        write_list.append(publishingOffice)
                        write_list.append(reportDatetime)
                        write_list.append(local_name)
                        write_list.append(timeDefine)
                        write_list.append(weather)
                        write_list.append(wind)
                        write_list.append(wave)

                        write_lists.append(write_list)
return write_lists

if name == 'main': main()

結果は以下のようになりました。今回のゴールである「天気」、「風」、「波」の情報の取得に成功しました。f:id:sanvarie:20220331152133p:plain

さいごに

このJSONを使えば簡単に天気予報情報を取得できることがわかりました。今回は省略しましたが、気温や降水確率なども取得できるので興味のある方はぜひチャレンジしてみてください。本日は以上です。

さて、本日は気象庁防災情報XMLの地震情報をCSV出力しようと思います。

気象庁防災情報XMLとは

気象庁は過去長年にわたり、それぞれの防災情報毎に情報の性質・利用形態などを考慮し、気象庁独自の電文形式(フォーマット)を作成してきました。この方式は、防災情報の種類が少なく、情報の伝達がFAXや低速の通信回線の時代はそれぞれの情報に適していましたが、高度にICT化された社会において、より詳細で高度化された防災情報をより効果的に活用していただくために、新たな防災情報の提供様式を検討すべきと考え、「気象庁防災情報XMLフォーマット」を策定することとし、平成23年5月12日より運用を開始しました。(出典:気象庁

気象庁防災情報XMLの仕様について

気象庁のサイトにまとまっているのでこちらをご参照ください。

また、地震情報に関してはこちらにもまとまっています。

今回取得するデータ

こちらのサイトから地震情報を取得します。(今回は長期フィードを使用)

f:id:sanvarie:20220320070157p:plain

この中には火山情報も入っており、そこを抜いて地震情報のみを取得します。以下画像の赤枠が地震情報となっており、同じようにURLに「VXSE」を含んだXMLから地震情報を取得したいと思います。

f:id:sanvarie:20220321101929p:plain

取得する項目

さまざまな項目があるのですが、今回は重要そうな以下の項目を取得しようと思います。

実行環境

macOS 11.3.1
Python 3.9.6

サンプルコード

気象庁防災情報XMLから地震情報を取得するサンプルです。

import re import csv import urllib.request from bs4 import BeautifulSoup

output_file = "eathquake.csv"

header = ["イベントID","地震発生場所","地震発生時刻","観測点で地震を検知した時刻","緯度","経度", "マグニチュード","県名称","市町村名称","最大震度","震度観測点の情報"]

with open(output_file, 'w', encoding='utf-8') as f: writer = csv.writer(f, lineterminator="\n") writer.writerow(header)

url = "https://www.data.jma.go.jp/developer/xml/feed/eqvol_l.xml"
res = urllib.request.urlopen(url).read()
soup = BeautifulSoup(res)


search = re.compile('.*VXSE.*')

for vxse in soup.find_all(text=search):

    
    res = urllib.request.urlopen(vxse).read()
    soup = BeautifulSoup(res)

    
    event_id = soup.find("eventid").text

    for content in soup.find_all("body"):
        earthquake = content.find("earthquake")

        if earthquake is not None: 
            
            location = earthquake.find("name").text 
            origintime = earthquake.find("origintime").text 
            arrivaltime = earthquake.find("arrivaltime").text 
            
            coordinate = earthquake.find("jmx_eb:coordinate").text 
            lat = coordinate[1:coordinate[1:].find("+") + 1]
            lon = coordinate[coordinate[1:].find("+") + 2:coordinate[1:].find("+") + 7]
            lat = int(lat[0:2])+(int(lat[3:])/60)+(0/3600)
            lon = int(lon[0:3])+(int(lon[4:])/60)+(0/3600)
            magnitude = earthquake.find("jmx_eb:magnitude").text 

        intensity = content.find("intensity")
        if intensity is not None: 
            for pref in intensity.find_all("pref"):
                
                prefectuer = pref("name")[0].text                  

                for city in pref.find_all("city"):
                    city_name = city("name")[0].text
                    maxint = city("maxint")[0].text 

                    write_list = []
                    write_list.append(event_id)
                    write_list.append(location)
                    write_list.append(origintime)
                    write_list.append(arrivaltime)
                    write_list.append(lat)
                    write_list.append(lon)
                    write_list.append(magnitude)
                    write_list.append(prefectuer)
                    write_list.append(city_name)
                    write_list.append(maxint)

                    intensity_station = ""
                    for intensitystation in pref.find_all("intensitystation"): 
                        intensity_station = intensity_station + intensitystation("name")[0].text + " " 
                    
                    write_list.append(intensity_station)

                    
                    writer.writerow(write_list)

出力結果は以下のようになりました。f:id:sanvarie:20220321102555p:plain

さいごに

いかがでしたでしょうか?位置情報を含んでいるので出力結果を可視化してみてもいいかもしれないですね。また、気象庁防災情報XMLは地震情報以外にも様々な情報を提供しているので今後は他の情報でも遊んでみようと思います。本日は以上です。

皆様、お久しぶりです。前回のエントリーから間が空いてしまいましたが、また少しずつブログの更新をしていきたいと思っています。今回は Elasticsearch と Python を使って空間検索をしてみようと思います。

以前にも Elasticsearch に関して以下のエントリーを書いているのでぜひ読んでみてください。

www.gis-py.com

なお、Elasticsearch のインストールや設定などについては本エントリーでは割愛しています。

実行環境

macOS 11.3.1
Python 3.9.6
Elasticsearch 7.17.0
elasticsearch 8.0.0(Elasticsearch の Python クライアント)

手順

  1. インデックスの作成
  2. 位置情報データを Elasticsearch に格納
  3. 空間検索

1.インデックスの作成

Elasticsearch 上にインデックスを作成します。詳細については以下をご参照ください。

www.elastic.co

PUT culture_sports_park { "mappings": { "properties": { "location": { "type": "geo_point" } } } }

2.位置情報データを Elasticsearch に格納

今回は「東京都オープンデータカタログサイト」から文化・スポーツ施設・公園の geojsonデータをダウンロードしてみたいと思います。

catalog.data.metro.tokyo.lg.jp

Elasticsearch の Python クライアント である elasticsearch を使って以下のようにデータを格納します。

import geojson from elasticsearch import Elasticsearch, helpers

with open("culture_sports_park.geojson") as f: gj = geojson.load(f)

es = Elasticsearch("http://localhost:9200")

k = ({
    "_index": "culture_sports_park",
    "_source": {
        "name": feature["properties"]["施設名称"],
        "address": feature["properties"]["所在地"],
        "note": feature["properties"]["備考"],
        "location": {
            "lat":feature["geometry"]["coordinates"][1],
            "lon": feature["geometry"]["coordinates"][0]
        }
    }

} for feature in gj['features'])

helpers.bulk(es, k)

以下のコマンドでデータが格納されているか確認します。

GET culture_sports_park/_search

ばっちりですね。

f:id:sanvarie:20220314075335p:plain

これを Kibana 上で可視化してみると以下のようになります。

f:id:sanvarie:20220314075904p:plain

3.空間検索

蒲田駅から半径5kmにある文化・スポーツ施設・公園を検索してみます。

from elasticsearch import Elasticsearch

es = Elasticsearch("http://localhost:9200")

query = { "query": { "geo_distance": { "distance": "5km", "location": { "lat": 35.562479, "lon": 139.716073 } } } }

result = es.search(index="culture_sports_park", body=query, size=100)

for document in result["hits"]["hits"]: print(document["_source"])

このような結果となりました。

{'name': '旗の台文化センター', 'address': '東京都品川区旗の台5-19-5', 'note': '所在地:旗の台5-19-5電話:3786-5191 施設内容:第1〜2会議室 レクリエーションホール スポーツ室 グループ室 休館日:年末年始・毎月第4日曜日', 'location': {'lat': 35.603845, 'lon': 139.700253}} {'name': '南大井文化センター', 'address': '東京都品川区南大井1-12-6', 'note': '所在地:南大井1-12-6 電話:3764-6511 施設内容:第1〜3講習室\u3000美術工芸室\u3000レクリエーションホール\u3000スポーツ室\u3000託児室\u3000グループ室 休館日:年末年始・毎月第4日曜日', 'location': {'lat': 35.596235, 'lon': 139.737582}} {'name': '南大井図書館', 'address': '東京都品川区南大井3-7-13保育園等と併設', 'note': '品川区南大井3-7-13併設南大井児童センター南大井保育園南大井シルバーセンター', 'location': {'lat': 35.591586, 'lon': 139.734398}} {'name': '源氏前図書館', 'address': '東京都品川区中延4-14-17保育園と併設', 'note': '品川区中延4-14-17併設源氏前保育園', 'location': {'lat': 35.605478, 'lon': 139.709904}} {'name': '大井図書館', 'address': '東京都品川区大井5-19-14滝王子児童センターと併設', 'note': '品川区大井5-19-14併設滝王子児童センター', 'location': {'lat': 35.598196, 'lon': 139.729134}} {'name': '品川歴史館', 'address': '東京都品川区大井6-11-1', 'note': '品川区大井6-11-1電話:3777-4060 交通:大井町駅から東急バス「鹿島神社前」下車徒歩1分、JR大森駅山王北口下車徒歩10分 開館時間:午前9時〜午後5時(入館は4時30分まで) 休館日:年末年始、月曜日および祝日', 'location': {'lat': 35.596791, 'lon': 139.730102}} {'name': 'メイプルセンター', 'address': '東京都品川区西大井1-4-25', 'note': '(公財)品川文化振興事業団が運営するカルチャーセンター。 多様な講座を開催。所在地:西大井1-4-25電話:3774-5050 交通:JR横須賀線西大井駅 休館日:毎月第4日曜日、年末年始 開館時間:午前9時〜午後9時30分(土・日・祝日は午後5時まで)', 'location': {'lat': 35.602061, 'lon': 139.722429}} {'name': 'しながわ水族館', 'address': '東京都品川区勝島3-2-1しながわ区民公園内', 'note': '品川区勝島3-2-1しながわ区民公園内電話:3762-3431 交通: JR大井町駅から無料バス開館時間: 午前10時〜午後5時(入館は午後4時30分まで) 休館日: 毎週火曜日、1月1日 *春休み、ゴールデンウィーク、夏休みは営業', 'location': {'lat': 35.588634, 'lon': 139.737364}} {'name': '大井ふ頭中央海浜公園野球場', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.590939, 'lon': 139.752439}} {'name': 'しながわ区民公園野球場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 京浜急行\u3000立会川駅、大森海岸駅から徒歩約5分公園内には、しながわ水族館、野球場、庭球場、ディキャンプ場、プール(夏季)あり。', 'location': {'lat': 35.594479, 'lon': 139.740205}} {'name': 'しながわ区民公園庭球場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 野球場、庭球場ディキャンプ場あり', 'location': {'lat': 35.593656, 'lon': 139.739353}} {'name': '大井ふ頭中央海浜公園', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.592503, 'lon': 139.752289}} {'name': '大井ふ頭中央海浜公園陸上競技場・球技場', 'address': '東京都品川区八潮4-1-19', 'note': '品川区八潮4-1-19', 'location': {'lat': 35.594031, 'lon': 139.753818}} {'name': 'しながわ区民公園デイキャンプ場', 'address': '東京都品川区勝島3-2-2', 'note': '品川区勝島3-2-2 野球場、テニス場ディキャンプ場あり', 'location': {'lat': 35.592863, 'lon': 139.738629}} {'name': '大井海岸公園', 'address': '東京都品川区南大井3-27-5', 'note': '品川区南大井3-27-5', 'location': {'lat': 35.588463, 'lon': 139.733647}} {'name': '大井水神公園', 'address': '東京都品川区南大井5-16-1', 'note': '品川区南大井5-16-1、南大井6-14-2、南大井6-15-2', 'location': {'lat': 35.595355, 'lon': 139.732493}} {'name': '鈴ケ森公園', 'address': '東京都品川区南大井4-18-14', 'note': '品川区南大井4-18-14', 'location': {'lat': 35.59351, 'lon': 139.734935}} {'name': '大井中央公園', 'address': '東京都品川区大井1-46-8', 'note': '品川区大井1-46-8', 'location': {'lat': 35.604517, 'lon': 139.732352}} {'name': '大井坂下公園', 'address': '東京都品川区南大井6-23-11', 'note': '品川区南大井6-23-11', 'location': {'lat': 35.589516, 'lon': 139.730786}} {'name': '金子山公園', 'address': '東京都品川区西大井4-11-6', 'note': '品川区西大井4-11-6', 'location': {'lat': 35.59583, 'lon': 139.720733}} {'name': '新浜川公園', 'address': '東京都品川区東大井2-26-18', 'note': '品川区東大井2-26-18', 'location': {'lat': 35.597859, 'lon': 139.740928}} {'name': 'わかくさ公園', 'address': '東京都品川区勝島1-6-1', 'note': '品川区勝島1-6-1', 'location': {'lat': 35.598043, 'lon': 139.743905}} {'name': '原っぱ公園', 'address': '東京都品川区西大井6-1-14', 'note': '品川区西大井6-1-14', 'location': {'lat': 35.601099, 'lon': 139.714796}} {'name': '宮下公園', 'address': '東京都品川区大井2-7-16', 'note': '品川区大井2-7-16', 'location': {'lat': 35.605213, 'lon': 139.728399}} {'name': '浜川公園', 'address': '東京都品川区南大井4-8-22', 'note': '品川区南大井4-8-22', 'location': {'lat': 35.597232, 'lon': 139.735757}} {'name': '滝王子公園', 'address': '東京都品川区大井5-19-5', 'note': '品川区大井5-19-5', 'location': {'lat': 35.598389, 'lon': 139.728806}} {'name': '出石公園', 'address': '東京都品川区西大井3-16-27', 'note': '品川区西大井3-16-27', 'location': {'lat': 35.594866, 'lon': 139.722095}} {'name': '浜川北公園', 'address': '東京都品川区東大井3-26-6', 'note': '品川区東大井3-26-6', 'location': {'lat': 35.599143, 'lon': 139.737533}} {'name': '桐畑公園', 'address': '東京都品川区南大井6-1-17', 'note': '品川区南大井6-1-17', 'location': {'lat': 35.593052, 'lon': 139.73167}} {'name': '西の森公園', 'address': '東京都品川区西大井4-2-1', 'note': '品川区西大井4-2-1', 'location': {'lat': 35.597725, 'lon': 139.721316}} {'name': '関ケ原公園', 'address': '東京都品川区東大井6-12-21', 'note': '品川区東大井6-12-21', 'location': {'lat': 35.602174, 'lon': 139.735626}} {'name': '倉田公園', 'address': '東京都品川区大井4-29-23', 'note': '品川区大井4-29-23', 'location': {'lat': 35.59851, 'lon': 139.73236}} {'name': '西大井広場公園', 'address': '東京都品川区西大井1-4-10', 'note': '品川区西大井1-4-10、二葉2-19-7', 'location': {'lat': 35.602258, 'lon': 139.723426}} {'name': '元芝公園', 'address': '東京都品川区東大井3-7-18', 'note': '品川区東大井3-7-18', 'location': {'lat': 35.602744, 'lon': 139.739034}} {'name': '西大井緑地公園', 'address': '東京都品川区西大井6-10-16', 'note': '品川区西大井6-10-16', 'location': {'lat': 35.600333, 'lon': 139.720565}} {'name': 'しおじ公園', 'address': '東京都品川区八潮5-8-1', 'note': '品川区八潮5-8-1、八潮5-6-9', 'location': {'lat': 35.596166, 'lon': 139.750656}} {'name': '大井鹿島公園', 'address': '東京都品川区大井6-8-2', 'note': '品川区大井6-8-2', 'location': {'lat': 35.596954, 'lon': 139.732443}} {'name': '谷垂公園', 'address': '東京都品川区西大井6-13-10', 'note': '品川区西大井6-13-10', 'location': {'lat': 35.599954, 'lon': 139.717892}} {'name': 'やまなか公園', 'address': '東京都品川区大井3-22-3', 'note': '品川区大井3-22-3', 'location': {'lat': 35.601039, 'lon': 139.727831}} {'name': '大森貝塚遺跡庭園', 'address': '東京都品川区大井6-21-6', 'note': '品川区大井6-21-6', 'location': {'lat': 35.593454, 'lon': 139.729945}} {'name': '旗の台公園', 'address': '東京都品川区旗の台5-19-9', 'note': '品川区旗の台5-19-9', 'location': {'lat': 35.604026, 'lon': 139.700415}} {'name': '旗の台南公園', 'address': '東京都品川区旗の台5-11-11', 'note': '品川区旗の台5-11-11', 'location': {'lat': 35.603715, 'lon': 139.70271}} {'name': '東中延公園', 'address': '東京都品川区東中延2-10-2', 'note': '品川区東中延2-10-2', 'location': {'lat': 35.606891, 'lon': 139.712651}} {'name': '荏原町公園', 'address': '東京都品川区中延5-14-5', 'note': '品川区中延5-14-5', 'location': {'lat': 35.602276, 'lon': 139.708936}} {'name': '大原公園', 'address': '東京都品川区戸越6-14-1', 'note': '品川区戸越6-14-1', 'location': {'lat': 35.60706, 'lon': 139.71428}} {'name': '豊町公園', 'address': '東京都品川区豊町6-16-3', 'note': '品川区豊町6-16-3', 'location': {'lat': 35.604645, 'lon': 139.716402}} {'name': '二葉公園', 'address': '東京都品川区二葉4-13-5', 'note': '品川区二葉4-13-5', 'location': {'lat': 35.603668, 'lon': 139.718124}} {'name': '旗の台北公園', 'address': '東京都品川区旗の台3-10-15', 'note': '品川区旗の台3-10-15', 'location': {'lat': 35.605695, 'lon': 139.704672}} {'name': '源氏前公園', 'address': '東京都品川区中延6-4-15', 'note': '中延 6-4-15', 'location': {'lat': 35.602198, 'lon': 139.712407}} {'name': '旗の台なか公園', 'address': '東京都品川区旗の台3-9-12', 'note': '品川区旗の台3-9-12', 'location': {'lat': 35.604708, 'lon': 139.705797}} {'name': '戸越南公園', 'address': '東京都品川区戸越6-8-8', 'note': '品川区戸越6-8-8', 'location': {'lat': 35.607305, 'lon': 139.718587}} {'name': '旗の台広場公園', 'address': '東京都品川区旗の台3-1-5', 'note': '品川区旗の台3-1-5', 'location': {'lat': 35.606235, 'lon': 139.705977}} {'name': '二鳳公園', 'address': '東京都品川区豊町4-19-20', 'note': '品川区豊町4-19-20', 'location': {'lat': 35.605414, 'lon': 139.721282}} {'name': '鹿島庚塚児童遊園', 'address': '東京都品川区大井7-29-11', 'note': '品川区大井7-29-11', 'location': {'lat': 35.59392, 'lon': 139.72937}} {'name': '富士見ケ丘児童遊園', 'address': '東京都品川区西大井5-7', 'note': '品川区西大井5-7-3', 'location': {'lat': 35.599427, 'lon': 139.716358}} {'name': '北浜川児童遊園', 'address': '東京都品川区東大井2-25-22', 'note': '品川区東大井2-25-22', 'location': {'lat': 35.597968, 'lon': 139.739288}} {'name': '出石児童遊園', 'address': '東京都品川区西大井3-1-5', 'note': '品川区西大井3-1-5', 'location': {'lat': 35.597804, 'lon': 139.723842}} {'name': '関ケ原児童遊園', 'address': '東京都品川区南大井5-2-15', 'note': '品川区南大井5-2-15', 'location': {'lat': 35.600409, 'lon': 139.734917}} {'name': '東大井三丁目児童遊園', 'address': '東京都品川区東大井3-29-6', 'note': '品川区東大井3-29-6', 'location': {'lat': 35.598928, 'lon': 139.737048}} {'name': '西大井ちびっこ児童遊園', 'address': '東京都品川区西大井2-24-9', 'note': '品川区西大井2-24-9', 'location': {'lat': 35.597834, 'lon': 139.72503}} {'name': '作守児童遊園', 'address': '東京都品川区大井4-12-14', 'note': '品川区大井4-12-14', 'location': {'lat': 35.601587, 'lon': 139.733211}} {'name': '桜橋児童遊園', 'address': '東京都品川区東大井3-22-1', 'note': '品川区東大井3-22-1', 'location': {'lat': 35.599858, 'lon': 139.735899}} {'name': '西大井二丁目児童遊園', 'address': '東京都品川区西大井2-22-7', 'note': '品川区西大井2-22-7', 'location': {'lat': 35.598816, 'lon': 139.724691}} {'name': '大井倉田児童遊園', 'address': '東京都品川区大井4-22-30', 'note': '品川区大井4-22-30', 'location': {'lat': 35.600254, 'lon': 139.732213}} {'name': '鈴ケ森道路児童遊園', 'address': '東京都品川区南大井1-22-1', 'note': '品川区南大井1-22-1', 'location': {'lat': 35.594342, 'lon': 139.736671}} {'name': 'みなみ児童遊園', 'address': '東京都品川区南大井1-13-8', 'note': '品川区南大井1-13-8、南大井1-12-9', 'location': {'lat': 35.595655, 'lon': 139.737085}} {'name': '旗岡児童遊園', 'address': '東京都品川区旗の台3-6-12', 'note': '品川区旗の台3-6-12', 'location': {'lat': 35.604515, 'lon': 139.708511}} {'name': '上神明児童遊園', 'address': '東京都品川区二葉4-26-11', 'note': '品川区二葉4-26-11、二葉4-3-15', 'location': {'lat': 35.602209, 'lon': 139.714168}} {'name': '豊町5丁目児童遊園', 'address': '東京都品川区豊町5-14-3', 'note': '品川区豊町5-14-3', 'location': {'lat': 35.604875, 'lon': 139.720592}} {'name': '立会川児童遊園(その4)', 'address': '東京都品川区旗の台3-7', 'note': '品川区旗の台3-7地先、旗の台3-7-3-9地先', 'location': {'lat': 35.604264, 'lon': 139.706422}} {'name': '立会川児童遊園(その2)', 'address': '東京都品川区旗の台2-7', 'note': '品川区旗の台2-7地先、旗の台2-7-2-1地先', 'location': {'lat': 35.606224, 'lon': 139.704371}} {'name': '立会川児童遊園(その3)', 'address': '東京都品川区旗の台3-11', 'note': '品川区旗の台3-11地先、旗の台3-11-3-10地先', 'location': {'lat': 35.60551, 'lon': 139.704621}} {'name': '豊町児童遊園', 'address': '東京都品川区豊町4-1-12', 'note': '品川区豊町4-1-12', 'location': {'lat': 35.60722, 'lon': 139.7192}} {'name': '伊藤児童遊園', 'address': '東京都品川区西大井6-17-9', 'note': '品川区西大井6-17-9', 'location': {'lat': 35.600127, 'lon': 139.715824}} {'name': '京浜運河緑道公園', 'address': '東京都品川区八潮1', 'note': '品川区八潮1、八潮5', 'location': {'lat': 35.597029, 'lon': 139.748997}} {'name': 'しながわ区民公園屋外プール', 'address': '東京都品川区勝島3-2-2', 'note': '野球場、庭球場ディキャンプ場あり', 'location': {'lat': 35.593391, 'lon': 139.739778}} {'name': 'しながわ区民公園', 'address': '東京都品川区勝島3-2-2', 'note': '品川区南大井2-3-17品川区勝島3-2-2', 'location': {'lat': 35.592575, 'lon': 139.738882}} {'name': '森前公園', 'address': '東京都品川区西大井1-1-10', 'note': '品川区西大井1-1-10', 'location': {'lat': 35.600512, 'lon': 139.721299}} {'name': '弁天通り公園', 'address': '東京都品川区中延5-3-8', 'note': '品川区中延5-3-8', 'location': {'lat': 35.603752, 'lon': 139.708745}} {'name': '庚申公園', 'address': '東京都品川区中延5-13-17', 'note': '品川区中延5-13-17', 'location': {'lat': 35.60169, 'lon': 139.708685}} {'name': '都立京浜運河緑道公園', 'address': '東京都品川区八潮1-', 'note': '品川区八潮1、八潮5', 'location': {'lat': 35.596782, 'lon': 139.748972}} {'name': '都立大井ふ頭中央海浜公園', 'address': '東京都品川区八潮4-1-19', 'note': '東京都品川区八潮4-1-19', 'location': {'lat': 35.592501, 'lon': 139.752291}} {'name': '旗の台東広場', 'address': '東京都品川区旗の台4-12-16', 'note': '品川区旗の台4-12-16', 'location': {'lat': 35.60214, 'lon': 139.70387}} {'name': 'ごこう公園', 'address': '東京都品川区中延5-9-23', 'note': '品川区中延5-9-23区へ寄付された用地を公園として整備。周辺に花壇を施したシンプルなつくり。', 'location': {'lat': 35.602062, 'lon': 139.706797}} {'name': 'ゆたか南公園', 'address': '東京都品川区豊町6-29-2', 'note': '品川区豊町6-29-2住宅の密集著しい地区の中にあるため、広場スペースが広く、防災機能を多く取り入れている。', 'location': {'lat': 35.604056, 'lon': 139.716339}} {'name': '西大井四丁目特定児童遊園', 'address': '東京都品川区西大井4-23-12', 'note': '品川区西大井4-23-12', 'location': {'lat': 35.592907, 'lon': 139.719381}} {'name': '南大井四丁目特定児童遊園', 'address': '東京都品川区南大井4-6', 'note': '品川区南大井4-6-20', 'location': {'lat': 35.596968, 'lon': 139.736822}} {'name': '源氏前特定児童遊園', 'address': '東京都品川区中延6-4', 'note': '品川区中延6-4-8', 'location': {'lat': 35.602272, 'lon': 139.712272}} {'name': '大井二丁目特定児童遊園', 'address': '東京都品川区大井2-5-15', 'note': '品川区大井2-5-15', 'location': {'lat': 35.604237, 'lon': 139.727564}} {'name': '西大井六丁目特定児童遊園', 'address': '東京都品川区西大井6-5-5', 'note': '西大井6-5-5', 'location': {'lat': 35.60112, 'lon': 139.718687}} {'name': '西大井三丁目特定児童遊園', 'address': '東京都品川区西大井3-4-14', 'note': '品川区西大井3-4-14', 'location': {'lat': 35.596942, 'lon': 139.723904}} {'name': '豊四中央防災広場', 'address': '東京都品川区豊町4-17-1', 'note': '品川区豊町4-17-1', 'location': {'lat': 35.606713, 'lon': 139.720686}} {'name': 'しながわ花海道水辺広場', 'address': '東京都品川区東大井1-13', 'note': '品川区東大井1-13先外勝島運河周辺の防潮堤上部。', 'location': {'lat': 35.598598, 'lon': 139.741246}} {'name': '上蛇広場', 'address': '東京都品川区二葉4-3-10', 'note': '二葉4-3-10', 'location': {'lat': 35.602658, 'lon': 139.714435}} {'name': 'ゆたか防災広場', 'address': '東京都品川区豊町6-11-1', 'note': '豊町6-11-1', 'location': {'lat': 35.605595, 'lon': 139.717657}} {'name': '西大井六丁目ふれあい広場', 'address': '東京都品川区西大井6-3-1', 'note': '西大井6-3-1', 'location': {'lat': 35.601176, 'lon': 139.716261}} {'name': '中延みちしるべ防災広場', 'address': '東京都品川区中延5-12-11', 'note': '住所\u3000中延5-12-11', 'location': {'lat': 35.601184, 'lon': 139.70821}} {'name': '二葉中央のんき通り広場', 'address': '東京都品川区二葉3-17-14', 'note': '住所\u3000二葉3-17-14', 'location': {'lat': 35.604129, 'lon': 139.721404}}

ちなみに条件を3.5キロにすると以下のような結果になり、きちんと空間検索の条件がきいていることがわかります。

{'name': 'しながわ水族館', 'address': '東京都品川区勝島3-2-1しながわ区民公園内', 'note': '品川区勝島3-2-1しながわ区民公園内電話:3762-3431 交通: JR大井町駅から無料バス開館時間: 午前10時〜午後5時(入館は午後4時30分まで) 休館日: 毎週火曜日、1月1日 *春休み、ゴールデンウィーク、夏休みは営業', 'location': {'lat': 35.588634, 'lon': 139.737364}} {'name': '大井海岸公園', 'address': '東京都品川区南大井3-27-5', 'note': '品川区南大井3-27-5', 'location': {'lat': 35.588463, 'lon': 139.733647}} {'name': '大井坂下公園', 'address': '東京都品川区南大井6-23-11', 'note': '品川区南大井6-23-11', 'location': {'lat': 35.589516, 'lon': 139.730786}} {'name': '西大井四丁目特定児童遊園', 'address': '東京都品川区西大井4-23-12', 'note': '品川区西大井4-23-12', 'location': {'lat': 35.592907, 'lon': 139.719381}}

さいごに

いかがでしたでしょうか?Elasticsearch を使えば 位置情報データの可視化や検索などが簡単にできます。Elasticsearch は年々人気が増しているサービスなので、今後のアップデートに関しても注目ですね。本日は以上です。