国土地理院の住所検索APIと逆ジオコーダーの使い方:表記ゆれと複数候補を実測
国土地理院の住所検索API(住所→緯度経度)と逆ジオコーダー(緯度経度→住所)を2026年9月14日に実測。応答の構造、漢数字・全角・旧地名での結果の違い、複数候補の扱い、muniCdの読み方、公式な位置づけと利用規約、AIエージェントで住所を検証する設計まで。
国土地理院の住所検索APIは、住所の文字列を渡すと候補ごとの緯度経度をGeoJSONで返し、逆ジオコーダーは緯度経度から市区町村コードと町字名を返します。 キーは不要ですが、返る座標は入力した住所の正確な地点とは限らず、表記によっては都道府県の代表点まで粗くなります。取引先の住所をAIエージェントで正規化・検証するなら、候補が1件で番地まで一致し、逆ジオコーダーの市区町村とも合ったときだけ自動で通す設計が安全です。
この記事は、2026年9月14日にNode.js(依存なし)から合計19回リクエストした結果です。市区町村コードから天気予報の地域へ進む方法は、気象庁の天気予報JSONをAIエージェントで使うで扱っています。
先に押さえること
- 住所検索は候補の配列を返す。 各候補は
geometry.coordinates(経度、緯度の順)とproperties.titleを持ちます。0件でも HTTP 200 の[]です。 - 表記ゆれにはかなり強い。 漢数字・全角数字・都道府県の省略・「霞ヶ関」と「霞が関」は、同じ1件に収束しました。
- それでも粗い一致がある。 「永田町1-7-1」は3件、旧市名の「浦和市」を含む住所は「埼玉県」の代表点1件でした。
- 逆ジオコーダーで照合する。
muniCdを市区町村名に直し、候補の住所と食い違わないか確かめます。
国土地理院が示している前提
APIの説明ページがない代わりに、地理院地図のリポジトリ(gsi-cyberjapan/gsimaps)のIssueに、情報普及課のアカウントによる回答が残っています。
- 2015-03-23Issue #29 への回答地名検索機能等は主に地理院地図からの利用を想定。長期提供は限らず、仕様や利用方法は予告なく変更しうる
- 2022-06-28Issue #111 への回答結果は街区・大字町丁目・市区町村レベルの代表地点の場合があり、正確な地点とは限らない
- 2022-08-04Issue #113 への回答リクエスト数の具体的な制限値は設けていない。前提は #29 と同じ
- 2025-11-20コンテンツ利用規約の改正サイトのコンテンツにPDL1.0を適用する現行版
回答は、住所の検索に東京大学CSISのシンプルジオコーディングを使っていると説明し、そちらの利用も勧めています。同サービスは住所などを経緯度に変換してXMLで返し、参加規約への同意が利用の条件です。
2つのエンドポイントと応答の形
| 機能 | URL | 入力 | 応答 |
|---|---|---|---|
| 住所検索 | https://msearch.gsi.go.jp/address-search/AddressSearch?q= | q(URLエンコードした住所) | GeoJSON Feature の配列 |
| 逆ジオコーダー | https://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress | lat(緯度)、lon(経度) | results に muniCd と lv01Nm |
どちらも Content-Type: application/json; charset=utf-8 で、キーなしのGETで返りました。「東京都千代田区永田町1-7-1」を住所検索に渡した応答です。
[ { "geometry": { "coordinates": [139.744385, 35.677414], "type": "Point" }, "type": "Feature", "properties": { "addressCode": "", "title": "東京都千代田区永田町一丁目7番" } }]その座標を逆ジオコーダーに渡した応答です。
{"results":{"muniCd":"13101","lv01Nm":"永田町一丁目"}}押さえる点は5つです。
coordinatesは経度、緯度の順。 逆ジオコーダーはlatとlonを別々に受けるので、取り違えに注意します。titleは入力の写しではない。 「1-7-1」が「一丁目7番」になり、号が落ちました。addressCodeは住所の候補では空文字。 「府中市」の検索に混ざった施設の候補には13206のようなコードとdataSource: "3"がありました。定義は公式資料で確認できていません。- 逆ジオコーダーは町字まで。 返るのは
muniCdとlv01Nmだけです。 - 失敗が失敗の形をしていない。 空の
qは HTTP 200 の[]、海上(緯度33.0・経度137.0)や韓国ソウル(緯度37.5665・経度126.978)の座標、lonを省いたリクエストは HTTP 200 の{}でした。
表記ゆれで結果はどう変わるか
同じ住所を書き方だけ変えて渡しました(2026-09-14、住所検索12回)。
| 入力 | 件数 | 返った title |
|---|---|---|
| 東京都千代田区永田町1-7-1 | 1 | 東京都千代田区永田町一丁目7番 |
| 東京都千代田区永田町一丁目7番1号 | 1 | 同上 |
| 東京都千代田区永田町1-7-1(全角) | 1 | 同上 |
| 千代田区永田町1-7-1(都道府県なし) | 1 | 同上 |
| 永田町1-7-1(市区町村なし) | 3 | 埼玉県秩父市永田町1番7号/東京都千代田区永田町一丁目7番/静岡県富士市永田町一丁目7番地 |
| 東京都千代田区霞ヶ関1-3-1 | 1 | 東京都千代田区霞が関一丁目3番 |
| 東京都千代田区霞が関1-3-1 | 1 | 同上 |
| 茨城県つくば市北郷1番 | 1 | 茨城県つくば市北郷1番地 |
| 埼玉県浦和市高砂3-15-1(旧市名) | 1 | 埼玉県 |
| 埼玉県さいたま市浦和区高砂3-15-1 | 1 | 埼玉県さいたま市浦和区高砂三丁目15番1号 |
| 府中市 | 7 | 東京都府中市/広島県府中市/施設5件 |
| (空文字) | 0 | なし([]) |
一番危ないのは旧市名です。「浦和市」は muni.js の市区町村表にない名前で、住所検索は「埼玉県」だけに一致しました。ところが返った座標は、新しい表記「さいたま市浦和区高砂3-15-1」の結果から約160mしか離れていません。逆ジオコーダーに通すと muniCd は 11107(さいたま市浦和区)、町字は「高砂三丁目」で、座標だけを見ると正しく一致したように見えます。 一致の粒度は title から判定します。
市区町村を省いた「永田町1-7-1」は3県に分かれ、「府中市」は東京都と広島県の2件に施設5件が混ざりました。候補の1件目を採る実装は、ここで別の住所を確定させます。
逆ジオコーダーのmuniCdを読む
muniCd は市区町村を表す5桁のコードで、応答に名前は入りません。地理院地図が読み込む https://maps.gsi.go.jp/js/muni.js(2026年9月14日の取得分で1,919件)で名前に直せます。
GSI.MUNI_ARRAY["13101"] = '13,東京都,13101,千代田区';GSI.MUNI_ARRAY["8220"] = '8,茨城県,8220,つくば市';GSI.MUNI_ARRAY["11107"] = '11,埼玉県,11107,さいたま市 浦和区';- キーは先頭の0を落とした形。 逆ジオコーダーの
08220は表では"8220"なので、String(Number(muniCd))で合わせます。 - 政令市の区は全角スペース入り。 「さいたま市 浦和区」は、住所と比べる前にスペースを除きます。
- muni.js も地理院地図の内部ファイル。 形式が変わりうる前提で使います。自治体コードの正式な一覧は総務省の「全国地方公共団体コード」です。
このコードは気象庁の予報区にもつながります。気象庁 area.json の市区町村単位のコードは千代田区が 1310100、つくば市が 0822000 で、先頭5桁が muniCd と一致しました。
住所の検証をAIエージェントに任せる設計
住所検索の結果をそのまま正規化済みの住所として書き戻すと、号の脱落・同名・旧地名の代表点がデータに入ります。候補を4つの状態に分ける判定関数を書きました。
// gsi-check.mjs — 依存なし。住所検索の候補を判定し、逆ジオコーダーの市区町村コードで照合する// 使い方(オンライン): node gsi-check.mjs "東京都千代田区永田町1-7-1"// 使い方(保存済み応答で判定だけ): node gsi-check.mjs --offlineimport { readFile } from 'node:fs/promises';
const UA = 'gsi-check-example/0.1 (+https://codeagent.jp/)';const SEARCH = 'https://msearch.gsi.go.jp/address-search/AddressSearch?q=';const REVERSE = 'https://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress';const MUNI = 'https://maps.gsi.go.jp/js/muni.js';
const toHalf = (s) => s.replace(/[0-9]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xfee0));
// muni.js の行 GSI.MUNI_ARRAY["13101"] = '13,東京都,13101,千代田区'; を Map にするfunction parseMuni(js) { const map = new Map(); for (const m of js.matchAll(/MUNI_ARRAY\["(\d+)"\]\s*=\s*'([^']+)'/g)) { const [, pref, , city] = m[2].split(','); map.set(m[1], { pref, city: city.replace(/[\s ]/g, '') }); } return map;}
// 返ってきた title がどの粒度の代表点かを推定する(ヒューリスティック)function levelOf(title, muniNames) { const t = toHalf(title); if (/^(北海道|東京都|京都府|大阪府|.{2,3}県)$/.test(t)) return 'prefecture'; if (muniNames.has(t)) return 'municipality'; if (/\d+号$/.test(t)) return 'go'; if (/\d+番地?$/.test(t)) return 'banchi'; return 'town';}
function classify(input, features, muni) { const muniNames = new Set([...muni.values()].map((v) => v.pref + v.city)); // 今回の応答では施設名の候補だけ addressCode が空でなかった(公式な定義は未確認) const addr = features.filter((f) => f.properties.addressCode === ''); const others = features.filter((f) => f.properties.addressCode !== ''); const candidates = addr.map((f) => ({ title: f.properties.title, lon: f.geometry.coordinates[0], lat: f.geometry.coordinates[1], level: levelOf(f.properties.title, muniNames), })); let status = 'matched'; if (candidates.length === 0) status = 'not_found'; else if (candidates.length > 1) status = 'ambiguous'; else if (!['go', 'banchi'].includes(candidates[0].level)) status = 'coarse'; return { input, status, candidates, otherTitles: others.map((f) => f.properties.title) };}
function reverseCheck(candidate, reverse, muni) { const r = reverse.results; if (!r) return { consistent: false, reason: 'no_address' }; // 海上・国外など const m = muni.get(String(Number(r.muniCd))); // "08220" は muni.js では "8220" if (!m) return { consistent: false, reason: 'unknown_muniCd', muniCd: r.muniCd }; return { consistent: toHalf(candidate.title).startsWith(m.pref + m.city), muniCd: r.muniCd, pref: m.pref, city: m.city, lv01Nm: r.lv01Nm, };}
async function getJson(url) { const res = await fetch(url, { headers: { 'User-Agent': UA } }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`); return res.json();}
if (process.argv[2] === '--offline') { const raw = (n) => readFile(new URL(`./raw/${n}`, import.meta.url), 'utf8'); const muni = parseMuni(await raw('muni.js')); const cases = [ ['s01', '東京都千代田区永田町1-7-1', 'r01'], ['s05', '永田町1-7-1'], ['s07', '埼玉県浦和市高砂3-15-1', 'r05'], ['s10', '府中市'], ['s11', ''], ]; for (const [file, input, rev] of cases) { const result = classify(input, JSON.parse(await raw(`${file}.txt`)), muni); if (rev && result.candidates.length === 1) { result.reverse = reverseCheck(result.candidates[0], JSON.parse(await raw(`${rev}.txt`)), muni); } console.log(JSON.stringify(result)); }} else { const input = process.argv[2]; const muni = parseMuni(await (await fetch(MUNI, { headers: { 'User-Agent': UA } })).text()); const result = classify(input, await getJson(SEARCH + encodeURIComponent(input)), muni); if (result.status === 'matched') { const c = result.candidates[0]; result.reverse = reverseCheck(c, await getJson(`${REVERSE}?lat=${c.lat}&lon=${c.lon}`), muni); } result.retrievedAt = new Date().toISOString(); console.log(JSON.stringify(result, null, 2));}APIの呼び出しは前節の実測で済ませてあるので、判定は保存した応答に対して --offline で実行しました(追加のリクエストなし)。見やすく改行し、ambiguous の行は座標を省いています。
$ node gsi-check.mjs --offline{"input":"東京都千代田区永田町1-7-1","status":"matched", "candidates":[{"title":"東京都千代田区永田町一丁目7番","lon":139.744385,"lat":35.677414,"level":"banchi"}], "reverse":{"consistent":true,"muniCd":"13101","pref":"東京都","city":"千代田区","lv01Nm":"永田町一丁目"}}{"input":"永田町1-7-1","status":"ambiguous", "candidates":[{"title":"埼玉県秩父市永田町1番7号","level":"go"},{"title":"東京都千代田区永田町一丁目7番","level":"banchi"},{"title":"静岡県富士市永田町一丁目7番地","level":"banchi"}]}{"input":"埼玉県浦和市高砂3-15-1","status":"coarse", "candidates":[{"title":"埼玉県","lon":139.649017,"lat":35.857033,"level":"prefecture"}], "reverse":{"consistent":false,"muniCd":"11107","pref":"埼玉県","city":"さいたま市浦和区","lv01Nm":"高砂三丁目"}}{"input":"府中市","status":"ambiguous", "candidates":[{"title":"東京都府中市","level":"municipality"},{"title":"広島県府中市","level":"municipality"}], "otherTitles":["府中市役所","府中市役所","府中市民病院","府中市美術館","府中市郷土の森博物館"]}{"input":"","status":"not_found","candidates":[],"otherTitles":[]}旧市名のケースは、座標が近くても status: "coarse" と consistent: false の二重で止まりました。粒度の判定は title の末尾を見るだけの簡単なものなので、自社の住所データで外れ方を確かめてから使ってください。
エージェントへの指示は次の4行です。自動で通すのは、候補1件・番地以上・逆ジオコーダーの市区町村と一致、の3つがそろったときだけにします。
- 住所の確定は geocode_address の status が matched かつ reverse.consistent が true のときだけ- ambiguous は候補を番号付きで示して止まる。候補から推測で選ばない- coarse / not_found は「旧地名・誤記の可能性」として差し戻す。座標が近くても採用しない- 元の住所文字列は書き換えず、正規化後の表記と座標は別の列に入れるツール側では呼び出しを直列にして同じ住所の結果をキャッシュし、入力の文字列、返った title、座標、粒度、muniCd、取得日時、取得元URLを別々に保存します。住所はクエリとして国土地理院のサーバーへ送られるので、個人の自宅住所などを流す場合は社内の取り扱いルールも確認してください。出典の持たせ方はMCPの出典情報を欠落させないoutputSchema設計、上限と待機の置き場所は公共データAPIをMCP化する設計パターンにまとめています。
まとめ
- 住所検索はGeoJSON Featureの配列で、
coordinatesは経度・緯度の順。titleは代表点の住所で、号が落ちることがある - 漢数字・全角・都道府県の省略・霞ヶ関は同じ1件に収束した。市区町村の省略は3県に分かれ、旧市名は都道府県の代表点になった
- 逆ジオコーダーは
muniCdと町字名だけで、海上・国外でも HTTP 200 の{}。muniCdは muni.js で名前に直し、先頭の0に注意する - 地理院地図向けの機能で長期提供の保証はない。自動で通すのは1件・番地以上・市区町村一致のときだけにする
取引先を法人番号で特定してから所在地を照合する流れなら、法人番号システムWeb-APIの使い方と組み合わせられます。応答が正常な形のまま別の結果を返す落とし穴は、国会会議録検索APIの使い方で見た無効な院名の黙殺と同じ種類です。
この記事の情報・検証メモ
- 公開日
- 情報確認
- 参考リンク
- 8件
- 更新性
- 定期更新
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。
一次情報・参考リンク
- gsi-cyberjapan/gsimaps Issue #29: 地理院地図の地名検索APIを独自システムに組み入れてよいでしょうか。 https://github.com/gsi-cyberjapan/gsimaps/issues/29
- gsi-cyberjapan/gsimaps Issue #111: 検索機能について https://github.com/gsi-cyberjapan/gsimaps/issues/111
- gsi-cyberjapan/gsimaps Issue #113: ジオコーディングAPIのリクエスト制限について https://github.com/gsi-cyberjapan/gsimaps/issues/113
- 国土地理院情報普及課公式GitHubアカウント運用方針 https://www.gsi.go.jp/kohokocho/kohokocho40339.html
- 国土地理院コンテンツ利用規約(令和7年11月20日改正) https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html
- デジタル庁: 公共データ利用規約(第1.0版) https://www.digital.go.jp/resources/open_data/public_data_license_v1.0
- 地理院地図 muni.js(市区町村コード表、2026-09-14取得) https://maps.gsi.go.jp/js/muni.js
- 東京大学CSIS: シンプルジオコーディング https://geocode.csis.u-tokyo.ac.jp/home/simple-geocoding/