いわゆる「逆ジオコーディング」と呼ばれる機能ですが、きっかけはこれら2つの記事です。
Solr や Elasticsearch でも同じことができるのでは、という事で Elasticsearch でやってみました。
は、
をご覧ください。
とほぼ同じですが、「世界測地系緯度経度・G-XML形式」ではなく、 「世界測地系緯度経度・shape形式」 を使います。
一応再掲すると、
Elasticsearch へ投入できるデータ形式は JSON なので、ダウンロードした Shape形式のデータを JSON 形式の地理空間拡張である GeoJSON 形式に変換します。
スクリプトを書いてもできますが面倒なので、便利なオンラインツールに頼ることにします。
手順は、
ダウンロードした ZIP ファイルを解凍すると、「xxx.json」ファイルが見つかります。それをテキストエディタで開くと、features
以下に、住所エリアの情報が1行ずつ出力されています。
試しに1行取り出して、JSON を整形(見やすいよう適宜省略)してみると次のようになります。
//town.json
{
"type": "Feature",
"properties": {
"KEN_NAME": "愛知県",
"GST_NAME": "名古屋市",
"CSS_NAME": "中区",
"MOJI": "本丸",
…省略…
},
"geometry": {
"type": "Polygon",
"coordinates": [
[[136.895888, 35.187236],
[136.897375, 35.187357],
…中略…
[136.895888, 35.187236]]
]
}
}
これは「愛知県名古屋市中区本丸」のデータですね。
properties
以下は、この住所エリアの属性情報を示しています。
geometry
以下が、この住所エリアの位置情報(ポリゴン)を示しています。
Elasticseach はスキーマフリーですが、位置情報のところだけは明示的に宣言しないといけないらしいので、下のようなコマンドを実行して定義します。
あ、ここでは、
としています。
「Index や Type って何?」という方は
をどうぞ。
ではコマンドです。
curl -XPUT 'http://localhost:9200/towns/'
curl -XPUT 'http://localhost:9200/towns/town/_mapping' -d '
{
"town" : {
"properties": {
"geometry": {
"type": "geo_shape",
"tree": "quadtree",
"precision": "1m"
}
}
}
}'
properties.geometry
は、「geo_shape」 として扱う事を宣言しています。他の2つの設定は、インデックスの種類と精度を意味しますが、よく分かってません。
で勉強しましょう。
さて、いよいよこの「1行1JSON」のデータを、1行ずつ、Elasticsearch に投入します。 先の xxxx.json を置換なり何なりを駆使して、スクリプトにしちゃうのがてっとり早いでしょう。(json ファイルは Shift-jis なので、UTF-8 に変換しておきましょう。)
1行のデータを投入するコマンドは次のようになります。
curl -XPUT 'http://localhost:9200/towns/town/1' -d '{
"type": "Feature",
"properties": {
"KEN_NAME": "愛知県",
"GST_NAME": "名古屋市",
"CSS_NAME": "中区",
"MOJI": "本丸",
…省略…
},
"geometry": {
"type": "Polygon",
"coordinates": [
[[136.895888, 35.187236],
[136.897375, 35.187357],
…中略…
[136.895888, 35.187236]]
]
}
}'
towns/town/1
の最後の「1」のところは、連番にする必要があります。(オートインクリメントとかないのかな?)
全件を PUT すつスクリプトファイルは、下の画像のような感じになると思います。(データのライセンスがどうか分からないのでスクリプトファイル自体を公開するのはやめておきます)
これをTerminal で実行すると、数分かからずに Elasticsearch にデータが投入完了します。
curl -XGET 'http://localhost:9200/towns/town/1'
などを実行すれば、正しくデータが登録できたか確認できます。
ついに来ました。 では、緯度経度を与えて住所が返ってくるところ、やってみましょう。
Elasticsearch では検索クエリも JSON で書きます。
例えば、大須観音駅 らへんの緯度経度(35.1613077,136.898282)の住所で検索する場合は、次のようにします。
curl -XPOST 'http://localhost:9200/towns/town/_search' -d '{
"query": {
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_shape": {
"town.geometry": {
"shape": {
"type" : "envelope",
"coordinates" : [[136.898282, 35.1613077], [136.898282, 35.1613077]]
}
}
}
}
}
}
}'
geo_shape
フィルタを使い、条件に envelople(左上〜右下の領域) を指定します。今は「点」での検索をしたいので、左上、右下に同じ座標を指定します。
注意点は、 「経度, 緯度」 の順であることです。
※ geo_distance
というフィルタもありますが、こちらはデータが「点」専用なようで、今回のような「ポリゴン」には使えませんでした。
さて、上のコマンドを実行すると、下のような結果が得られます。(整形、省略済)
{
"took":1,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"failed":0
},
"hits":{
"total":1,
"max_score":1.0,
"hits":[{
"_index":"towns",
"_type":"town",
"_id":"29",
"_score":1.0,
"_source" : {
"type": "Feature",
"properties": {
"KEN_NAME": "愛知県",
"GST_NAME": "名古屋市",
"CSS_NAME": "中区",
"MOJI": "大須2丁目",
…以下省略…
はい、「緯度経度(35.1613077,136.898282)」の住所は「愛知県名古屋市中区大須2丁目」であることが取得できました。
いかがでしょうか、Elasticsearch でも逆ジオコーディングの実装が、簡単にできることが分かりました。
PostGIS、MongoDB との対比では、
あたりがメリットでしょうか。
逆にデータの取り込みはひと工夫必要で、PostGIS の方が簡単です。 このデータ用の River plugin を作ればよいのでしょうが、方法がさっぱり…。
最後に、
総務省のデータを使って、緯度経度から市区町村の何丁目までを取り出す
実は、データをダウンロードするのが、一番手間がかかります・・・。
でも言われていますが、まったくその通りです。 1市区町村ずつダウンロードとか正直やってられません。
総務省でも国土交通省でもどちらでも良いのですが、 「全国の大字境界+属性データを一括入手する方法」 を用意して欲しいものです。