ペペロン頭脳

ソフトウェアエンジニアのメモ的なアレ。

OSMデータから簡易的な逆ジオコーダをつくる

緯度経度から都道府県・市区町村を取りたいんだけど、それ系のAPIに払う予算はなくて… → そのくらいならオープンデータとオープンソース組み合わせて作れるんじゃね?

というのを前職でやったのを思い出したので大まかな構図だけ記しておく。 もう手元にないし、納品しちゃったコードなので流石に転載はできないが…

用意するデータ

github.com

このツールと手順を使わせてもらった。

  1. planetレベルのデータは必要ないしDLしたらえらいことになるので、必要な国の.pbfだけDLする
  2. Goの実行環境を作る
  3. READMEに書いてあるとおり順番に変換していく
    • コマンドラインオプション--keepは日本が対象なら--keep="place=region =cityだけでいいはず (都道府県・市区町村)
    • ちょい古いツールなので、おそらく最新のOSMフォーマットに対応できてないところがある。各ステップでちょくちょくエラーが出るので、メッセージを読みながらソースをいじって該当する箇所をコメントアウトしたりとか見様見真似で修正入れたりとかなんとかしたはず。(一番肝心なのに情報なくてスマソ)
  4. 最終的に行政界ポリゴンが1行1レコードで入ったJSONLが得られる

逆ジオコーダの実装

大したデータ量ではないので、DBを使わず全部メモリに持つアプローチで実装していた。 やりたいこととしては以下の通り

  • R-Treeを使ってざくっと対象を絞り込む
  • 境界付近で重複ヒットし得るので、その後ポリゴン内外判定でちゃんと絞り込む

セットアップ処理

というかウォームアップ処理というか。 起動したら以下の処理を実行する。この処理を実行している間は逆ジオコーディングを実行できないものとする。

  1. 上記で得たJSONLを読み込み、1行ずつGeoJSONとしてパースする
  2. R-Treeを用意する
  3. 1.でパースしたGeoJSONをR-Treeに登録していく
    • keyは言わずもがなポリゴンの外接矩形 xmin, xmax, ymin, ymax (longitude=x, latitude=yでそのまま放り込む)
    • valueにfeature丸ごと突っ込んでおく (ここから行政界のレベルや名称を取得するため)
    • マルチポリゴンは内包ポリゴン全部を同様に登録する

以上で逆ジオコーダの検索Ready状態となる。

検索処理

緯度経度をパラメータとして、

  1. まずR-Treeに対して検索を掛ける
    • 最初に用意したデータによるが、region, city それぞれの行政レベルのレコードがヒットする
    • R-Treeのキーはポリゴンの外接矩形なので、行政界の境界付近では複数の同レベルレコードがヒットし得る (ポリゴンの外でも、外接矩形の内部であればヒットしてしまう)
  2. ヒットしたポリゴン群に対し内外判定を掛ける。パラメータ緯度経度が内側にあるポリゴンだけにフィルタする
  3. データと処理が想定通りならば、ここで region, city が1件ずつヒットしているはず。それぞれの名称を返して処理終了。

用意したデータの領域外でヒットしないとか、検索結果に region, city が揃わないとかの場合はよしなにハンドリングしてください。

パフォーマンスとか

TypeScriptで実装した際、セットアップ処理に1-3秒、検索は数万件/秒数百件/秒のオーダーでいけた記憶。 セットアップもこの程度なら大した問題ではないし、検索はやはりオンメモリなので速い。
5カ国含めてcountryも持たせる運用でもセットアップ10秒くらいだったはず。
R-Treeをシリアライズしておけばもっとセットアップ速くなるだろうけど、十分なパフォーマンスなのでそこまで頑張らなかった。

R-TreeとかGeoJSONのパースとかはライブラリに任せて、コード量は200行未満だった気がする。

注意

たしか上記の方法で得たポリゴンには陸地しか含まれておらず、海上の地点で検索すると結果ナシとなった気がする。 海上県境なんかをちゃんと判定したい場合はそれ用のデータが必要です。

あと肝心なところ、逆ジオコーディング結果をUIとかドキュメントに出す時ちゃんとOSMのクレジット表記は忘れずにな!!

エンジョイ。