📍

緯度経度からの住所検索!〜日本の住所に絶望し、希望を見つけるまで〜

に公開


この記事は LayerX Tech Advent Calendar 2025 12 日目の記事です。

前回は@tigerさんslack-blockbookというslack appのUI確認を爆速にするライブラリについての記事でした。


こんにちは。株式会社LayerXソフトウェアエンジニアのyataです。

皆さん、もちろん緯度経度は好きですよね?

今回は、緯度経度と住所を用いた開発に取り組んだ際のお話です。

「緯度経度から住所を割り出す」。

一見簡単そうに見えるこの要件の中で、日本の住所に絶望し、そこから希望を見つけるまでの物語をお届けします。

やりたかったこと

今回実装したのは、大まかに説明すると「デバイスの位置情報を記録し、画面上から確認することができる」という機能です。

緯度経度を取得すること自体は、Web API である Geolocation API を使えば簡単に実現できます。しかし、画面に 35.6329, 139.8804 と表示されても、パッと見て「ディズニーランドだ!」と判断するのは非常に難しいです。

という訳で、緯度経度を取得することに加えて緯度経度から、画面上でパッと見で場所を理解できる機能を実装要件に追加して開発をスタートしました。

開発にあたって満たすべき要件は以下の通りです。

  1. デバイスから緯度経度を取得できること
  2. 画面上でパッと見て場所が分かること
  3. 可能な限り低コストであること

3 に関してはここまで触れてきませんでしたが、今回の開発において非常に重要な要素です。
詳細は割愛しますが、この機能は存在していること自体に価値がある機能であり、お客様へのヒアリングでも突き詰めた精度が求められている訳ではないことが分かりました。

そのため、工数やコストを極力削ぎ落とす代わりにお客様に一刻も早く使っていただける状態にすることを優先するとチームで合意し、そのためにどの様な手段を用いるかを検討しました。

それでは次のセクションから、実際にどの様な手段を用いて位置情報取得を実装していったのかを見ていきましょう。

検討 ①:地図で出すか?住所で出すか?

まず最初に、要件2 である「画面上でパッと見て場所が分かること」を満たすために、UI の表示方法として2つの選択肢を検討しました。

案1:地図を表示する

Google Maps などを埋め込んで、地図から確認できる方法です。

  • Good:
    • ビジュアルで分かりやすい
    • 周辺情報も見ることができる
  • Bad:
    • 画面スペースを取る
    • 縮尺によっては結局どこか分からない

案2:住所(文字)を表示する

「東京都中央区...」という文字列を表示する方法です。

今回は完全な住所ではなく丁目レベル(東京都中央区築地一丁目など)までを表示することとしています。
Geolocation API自体の精度にばらつきがあるので、正確な住所を表示することで逆に混乱を招きかねないという意図です。

  • Good:
    • 省スペースで、リスト表示でも邪魔にならない
  • Bad:
    • 土地勘がない場所だと、文字だけ見てもピンとこない

結論:住所(文字)を表示する
今回は 案2を採用しました。理由は、地図 API(Google Maps Platform など)の従量課金が高額になるリスクを避けたかったこと、そして一覧性を重視したためです。

Google Maps を埋め込む場合、単一のピンを刺すだけなら Maps Embed API を用いて無料で実現することができるのですが、今回の場合複数の位置情報を取得するという要件のため、画面上に大量の地図が表示されることになってしまうので断念しました。(複数ピンを刺せる Static Maps API などはいい値段がするし、画面上に表示する度に課金対象になってしまうことが想定されるためです)

ただ住所のみだと、「パッと見で分かる」という要件を完全に満たせない可能性があった(全く知らない住所を見ても意味不明)ので、住所をクリックすると外部リンクで Google Maps へ飛ぶという仕様でカバーすることにしました。

検討 ②:どうやって緯度経度から住所を検索(逆ジオコーディング)するか?

晴れて住所を表示することが決まったので、続いて Geolocation API から取得した緯度経度を元に住所を検索する手段を検討しましょう。

緯度経度から住所を割り出す処理を「逆ジオコーディング」と呼ぶのですが、世の中には多くの逆ジオコーディング API が存在しています。

今回は以下の主要なものを比較検討してみました。

サービス 国土地理院 API Google Maps Geocoding API OpenCage Geocoding API HERE Geocoding & Search
金額 ✅ 無料 ❌ 高め ($5 / 1,000 req) 安い
(small プランで月 2 万 req 程度なら約 1 万円〜)
⚠️ 普通
(月 3 万 req まで無料、以降有料)
キャッシュの可否 ❓ 不明 ❌ 禁止 ✅ 無期限に可能 ⚠️ 30 日間可能
信頼性 ❌ 非公式・保証なし ✅ 問題なさそう ✅ 問題なさそう ✅ 問題なさそう
使いやすさ ❌ 都道府県などが別取得 ✅ 一括取得可 ✅ 一括取得可 ✅ 一括取得可

比較の結果、OpenCage Geocoding APIを採用することにしました。
決め手は以下の点です。

  • とにかくコスパが良い
  • キャッシュが無期限で許可されている
    • 一定期間運用すればキャッシュでリクエストをほぼ0にできる
    • コスト的にもユーザー体験的にもGood!
  • ソフトレートリミットで安心
  • ちゃんと全てのデータを構造的に取得することができる

これで安価かつ便利に実装できて、めでたしめでたし。

【悲報】日本の住所、難しすぎる問題

とてもスムーズな意思決定でそのまま開発終了!という訳にはいきませんでした。

問題は、開発も中盤に差し掛かって来た時に発覚します。

それは住所のレイヤー(階層)が揃わないという問題です。

逆ジオコーディング API は通常、国・都道府県・市区町村などをそれぞれのフィールドに分けて返却してくれます。API 選定の時に何件か試してみた時点では気付いていなかったのですが、改めて見てみると API から返ってくる住所の精度にかなりバラつきがあることが分かりました。

// OpenCage Geocodeing APIの返り値サンプル(一部抜粋)
  "components": {
    "city": "浦安市",
    "continent": "Asia",
    "country": "日本",
    "country_code": "jp",
    "neighbourhood": "舞浜三丁目",
    "postcode": "279-8529",
    "province": "千葉県",
    "quarter": "舞浜",
    "road": "浦安市道幹線6号",
    "state": "千葉県",
    "theme_park": "東京ディズニーランド"
  },

例えば「〇丁目」という情報がフィールド A に格納されている場合もあれば、フィールド B に格納されている場合もある、といった感じでした。

「安いやつ選んだから API の精度が低いのでは?」と思われるかもしれませんが、どちらかというと日本の住所システムが難しすぎるという点が大きいのではないかと考えています。

以下の画像の様な構造になっている住所を綺麗に構造化することは現実的に可能なのでしょうか?


(出典:住所の正規化がいかに難しいかという話 - Kenall Blog

日本の住所は全てが「〇〇県〇〇市〇〇丁目〇〇」のように綺麗なフォーマットで統一されている訳ではありません。郡があったり、政令指定都市があったり、地名の入り組んだ歴史があったりと非常に複雑です。加えて、全世界の住所を広く提供している逆ジオコーディング API であれば尚更難易度が高いのではないでしょうか?

精度の検証結果

ということで、実際どの程度 API の精度が出ているのかを改めて検証し直すことにしました。

ただ、精度検証のためには正解のデータを用意しなければなりません。

何を正とするかは迷ったのですが、流石に国が出している情報が一番正しいだろうということで、国土地理院の位置参照情報という位置情報と住所がセットになっているデータを用いることにしました。

「位置参照情報」は、住所と緯度経度の対応テーブルであり、全国を対象とした大字・町丁目レベル(○○ 町 △ 丁目の代表点)の緯度経度情報を整備したデータと、 都市計画区域相当範囲を対象にした街区レベル(「○○ 町 △ 丁目 □ 番の代表点」)の緯度経度情報を整備したデータの 2 種類があります。(出典: 位置参照情報ダウンロードサイト)

代表点はざっくり「住所の大体真ん中くらいの位置」という意味だと思ってもらえれば大丈夫です。

そんな位置参照情報から各都道府県の住所をランダムで 10 件ピックアップした、合計 470 件の「正解データ」に対し、各 API がどれだけ正確に住所を返せるか検証してみました。(信頼性の問題から国土地理院 API を、コストの問題から Google Maps を除きました)

完全一致率

  • OpenCage: 394/470 件 (83.8%) 一致
  • HERE: 427/470 件 (90.8%) 一致

決して低い訳ではないですが、高いとも言えず、お客様に安定した体験を届けるには少し足りないという印象でした。

他にも細かい問題点はあり、それらを合わせて考えても現状のまま OpenCage を利用するのは難しいのではないかというのが精度検証の結果でした。

【朗報】天啓、来たる

「これは詰んだか...?」と絶望しかけた時、天啓が降りました。

位置参照情報が正しいんだったら、そっから検索すればいいじゃん

国土地理院が公開している「位置参照情報」は、国が公式に出しているデータであり、住所レベルの正確さは保証されていますし、フォーマットも統一されています。

もちろん、「住居表示が実施されていない場所(山奥など)」はデータがない場合もありますが、ユースケースとしてはごく稀であり、その場合は Google Maps への外部リンクで補完すれば事足りると判断しました。

再度比較してみた

OpenCage API HERE Geocoding API Google Maps API 位置参照情報
実装コスト ✅ 低 ✅ 低 ✅ 低 ⚠️ 中
精度 ⚠️ 中(76/470 件不一致) ☑️ 高(43/470 件不一致) -(未検証) ☑️ 高
レスポンスの速さ ✅ 高速 ✅ 高速 ✅ 高速 ⚠️ 遅い(約 1.3 秒)
料金 ✅ 安価 ⚠️ 安価〜中程度 ❌ 高額(際限なし) ✅ 無料
キャッシュの可否 ✅ 無期限可能 ⚠️30 日間可能 ❌ 禁止 ✅ 制限なし

比較の結果、やはり位置参照情報を利用するのが最も良いだろうということになりました。

最終的なアーキテクチャ

という訳で、外部 API を利用せず位置参照情報を用いて検索できる機能を実装しました。

データの準備

国土地理院の位置参照情報から全都道府県の街区レベルデータ(約 2,000 万行!)をダウンロードし、すべて自社のデータベースにインポートし、住所マスタテーブルを作成します。(LOAD DATA FROM S3 を利用したりと地味に大変だった)

空間検索の実装

デバイスの緯度経度をもとに、DB 内で「最も近い場所」を検索します。空間インデックス(Spatial Index)を活用することで、2,000 万件のデータからでも 1 秒前後で検索が可能になりました。

キャッシュ戦略(Geohash)

1 秒前後とはいえ、ユーザーの待ち時間を増加させるのはできる限り避けたかったため、Geohash(地図をグリッドで区切って文字列化したもの)を計算し、「Geohash と住所」のペアをキャッシュテーブルに保存する仕組みにしました。

まず初回の検索時は住所マスタテーブルから最も近い住所を検索し返却します。その際、その位置が所属している Geohash(8 桁。大体 30m 四方くらい。)をキーとするレコードがキャッシュテーブルになければ、その Geohash と住所の組をキャッシュテーブルに保存します。

そうすることで、2 回目以降同じ Geohash 内での検索履歴があれば、キャッシュから即座に住所を返却することができる様になりました。

geohashを使ったキャッシュテーブルのイメージ

最終的な結果

以上のような実装を行ない先ほどと同じ精度検証を行なった結果、精度が100%になりました。
それはもちろん当然ですよね。位置参照情報を正とした状態で位置参照情報を使ったデータの精度を検証したら100%になるに決まっていますから。

ただ、実際日本のいくつかの場所(大きい都市の適当な点)で精度検証をした場合でも大体が90%以上の精度が出ていました。

//ある住所の中の点をランダムで1000個生成して、それが住所検索した時に同じ住所が表示されるか
住所A: 780/1000
住所B: 934/1000
住所C: 1000/1000
住所D: 983/1000
住所E: 987/1000
住所F: 0/1000(Google Maps上では住所が存在している事になっている山奥)
住所G: 921/1000
住所H: 990/1000
余談: なんで2つの方法で精度が異なる値になるの?

読みやすさのために上記の2つの精度検証の差分が何かを省略したのですが、この違いは「位置参照情報の代表点を元データとして利用しているかどうか」です。

最初の方の精度検証は位置参照情報から持ってきたものを元データとして使っている一方で、後者は本当に地図上から適当な点をいくつかとってきて元データとしています。

そのため前者は精度が100%になりますが、後者は100%にはなりません。

APIとあんまり変わってない?

あれ、でもさっきのAPI(HERE)でも91%くらい精度出てたよな?」と思ったかもしれませんが、以下の様な違いがあります。

管理コストの違い

HEREでは30日間のキャッシュのみが許可されているので、かかる金額を抑えるためには30日ごとにキャッシュを削除する必要があります。そのため、毎回自分たちで削除するなりバッチを実行するなりする必要がありますし、キャッシュを削除したことによって住所検索が遅くなる時期が毎月存在してしまいます。

そして最初に選択したOpenCageではコストやキャッシュの問題は解決しますが84%と少し精度が落ちてしまいます。

しかし自分たちでデータベースを持っておけば、位置参照情報が更新される一年に一回のタイミングでマスタを更新すれば済みますし、無期限キャッシュによってリクエスト数も緩やかにしか増加しませんし、DBも既存のインスタンスに乗っかるだけなので金銭的なコストもせいぜいが数十円程度です。

まとめと展望

「外部 API を使えばサクッと終わるだろう」と高を括ってしまっていたタスクでしたが、結果として日本の住所という魔境に絶望しかけ、最終的には希望を見ることができる楽しい開発でした。

結果として、

  • 外部 API コストはゼロ
  • 住所の表記揺れや階層ズレがない
  • レスポンスも高速

という、当初の要件以上の結果になりました。

ここまで読んでいただきありがとうございました!


今後も弊社のアドベントカレンダーは続いていきます!
是非お楽しみにお待ちください 🤲

https://layerx.notion.site/6975c0901ea54ca9b609fafc3e8a35c3?v=2bccdd370bae80579556000cc2afc504

LayerX

Discussion