ジオコーディングツールは難しい住所をどこまで解析できるのか
おはようございます。メディア研究開発センターの新妻です。
つい先日、日本の住所を扱うことの難しさがインターネットで話題になってましたね。
日本の住所は、県、市区町村、町名、字、番地…と書き方のルールが一見存在しているように見えて、ルールをはみ出るような例外が非常に多く、ルールで処理するのが比較的難しいという問題があります。
(※日本の住所以外はルールに基づいているからこう言っているのではなく、筆者は日本の住所しか知らないだけです。)
そのため、ルールでサクッと処理すればいいじゃないか、という意見に対して下記のような記事が書かれたり、話題になったりしました。
これらの記事を読んでいる中で、あるひとつの疑問が浮かび上がりました。既存の代表的な住所正規化ツール、ジオコーディングツールは、このような難易度の高い住所に対してどんな結果を出すんだろうかと。
地理情報を取り扱うシステムを構築する場合には、それらのツール・サービスを使うことは必須な選択となり、それらを扱う際に際どい入力に対してどのような振る舞いをするのかを前もって把握することで、事前に対応できるようになると思ったからです。
気になってしまったのであれば、実際に動かしてみるしかないでしょ!ということで試してみました。
試してみたツール・サービス
Google Places API
GoogleがGoogle Map PlatformでAPIとして提供しているジオコーディング等をおこなってくれるクラウドサービス。
カジュアルにジオコーディングを試してみるならば真っ先に選択肢に上がる代表的なツールだと考えられそうです。
Nominatim
OpenStreetMapの住所や地点を検索できるジオコーディングAPIです。
OpenStreetMap Foundationが提供するサーバーを利用する場合は、無料で提供されていますが、リクエスト回数などを配慮して利用することが求められています。
jageocoder
情報試作室の相良さんによって開発されたジオコーダで、テキスト地名解析ツールPyGeoNLPに含まれているライブラリです。
東京大学空間情報科学研究所の 「CSV アドレスマッチングサービス」と国土地理院の 「地理院地図」 で使われている C++ ジオコーダを Python に移植したものとのことです。
国土地理院地図API
国土地理院によって運営されているジオコーディングAPIです。
APIに住所の文字列のクエリを含めてリクエストを投げれば、結果が返ってくる簡潔さが魅力的です。
IMI情報共有基盤 住所変換コンポーネント
経済産業省が提供していた住所正規化ライブラリです。
現在は経産省のリポジトリはアーカイブされているのですが、forkされて保守されており下記のGitHubおよびURLで公開されています。
今回はこのyprestoさんが公開しているvercelのAPIを使わせていただきました。
https://github.com/ypresto/imi-enrichment-address
@geolonia/normalize-japanese-addresses
geoloniaによってOSSとして公開されている住所正規化ライブラリです。経産省のIMIコンポーネントツールにインスピレーションを受けて開発されたそうです。
とても細かいところまで考慮して正規化をしているため、コードを読むだけでも日本の住所を扱う際の参考になりそうです。
TypeScriptで開発されており、npmで配布されています。
今回はJSPyBridgeでPythonから無理やり動かしてます。
住所の正規化ツールとジオコーディングサービスが混じっていますが、場所を同定できているのかを基準に見ていきたいと思います。
やってみた結果
今回はGoogle Colabを使って比較をおこなったのですが、その時のnotebookを下記のリポジトリで公開しています。
また、実行結果は次のdataclassに格納してpprintで表示しています。
@dataclass
class Address:
title: str
postal_code: Optional[str] = None
pref: Optional[str] = None
city: Optional[str] = None
town: Optional[str] = None
addr: Optional[str] = None
coords: Optional[Tuple[float, float]] = None
raw: Dict[str, Union[Optional[str], Optional[float], int]] = field(default_factory=dict)
正解の基準は住所の綴りや「郵便番号検索」によって得られる郵便番号と「国勢調査町丁・字等別境界データセット」の地域の代表点となる座標データにより判断したいと思います。
代表点は複数の丁目がある場合は、原則として一丁目を代表点とします。
また、県、市区町村等のカラムは、結果が分割されて返ってくる場合に限って入力をしています。
①新潟県新潟市北区東栄町
この住所の難しさは、同じ綴りで読み方の違う住所である「新潟県新潟市北区東栄町(とうえいちょう)」「新潟県新潟市北区東栄町(ひがしさかえまち)」の二つが存在しているというものです。
それぞれのサービスでは、どちらの住所が出てくるんでしょうか。
とうえいちょうの場合は「950-3323」で、「北緯37.95255度, 東経139.186812度」
ひがしさかえまちの場合は「950-3104」で、「北緯37.913138度, 東経139.222354度」
となるようです。
Google Places API
[ Address(title='日本、〒950-3323 新潟県新潟市北区東栄町',
postal_code='本',
pref=None,
city=None,
town=None,
addr=None,
coords=(37.9141691, 139.2249976),
raw={ 'formatted_address': '日本、〒950-3323 新潟県新潟市北区東栄町',
'geometry': { 'location': { 'lat': 37.9141691,
'lng': 139.2249976},
'viewport': { 'northeast': { 'lat': 37.9160303,
'lng': 139.2301354},
'southwest': { 'lat': 37.9109394,
'lng': 139.2197158}}},
'icon': 'https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png',
'icon_background_color': '#7B9EB0',
'icon_mask_base_uri': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet',
'name': '東栄町',
'place_id': 'ChIJl-jCrzQzi18RgC-45VA0XXo',
'reference': 'ChIJl-jCrzQzi18RgC-45VA0XXo',
'types': ['sublocality_level_2', 'sublocality', 'political']})]
こちらは郵便番号や座標から「とうえいちょう」が判定されているようです。
Nominatim
[ Address(title='日本、〒950-3102 新潟県新潟市北区東栄町',
postal_code='950-3102',
pref='新潟県',
city='新潟市',
town='北区',
addr='東栄町',
coords=('37.952531', '139.183275'),
raw={}),
Address(title='日本、〒950-3393 新潟県新潟市北区朝日町一丁目メタセコイア通り東栄町',
postal_code='950-3393',
pref='新潟県',
city='新潟市',
town='北区',
addr='朝日町一丁目メタセコイア通り東栄町',
coords=('37.9158362', '139.2264205'),
raw={})]
二つの候補があるがどちらも郵便番号はどちらかの東栄町のものではなさそう。けれども、最初の一つ目の結果は座標が「とうえいちょう」にあるため、こちらが判定されたと考えていいのかも。
jageocoder
[ Address(title='〒950-3104 新潟県新潟市北区東栄町',
postal_code='〒950-3104',
pref='新潟県',
city='新潟市',
town='北区',
addr='東栄町',
coords=(37.95252990722656, 139.1832733154297),
raw={ 'fullname': ['新潟県', '新潟市', '北区', '東栄町'],
'id': 16990092,
'level': 5,
'name': '東栄町',
'note': 'aza_id:0050000/postcode:9503104',
'priority': 2,
'x': 139.1832733154297,
'y': 37.95252990722656})]
こちらは郵便番号と座標から「ひがしさかえまち」の方が出ていますね。
国土地理院地図API
[ Address(title='新潟県新潟市北区東栄町',
postal_code=None,
pref=None,
city=None,
town=None,
addr=None,
coords=(37.913074, 139.222626),
raw={ 'geometry': { 'coordinates': [139.222626, 37.913074],
'type': 'Point'},
'properties': {'addressCode': '', 'title': '新潟県新潟市北区東栄町'},
'type': 'Feature'})]
国土地理院地図APIの方は郵便番号が出てこないのですが、座標から推測すると「とうえいちょう」
IMI 住所変換コンポーネント
[ Address(title='新潟県新潟市北区東栄町',
postal_code=None,
pref='新潟県',
city='新潟市北区',
town='東栄町',
addr='新潟市北区東栄町',
coords=None,
raw={ '@type': '住所型',
'区': '北区',
'市区町村': '新潟市',
'市区町村コード': 'http://data.e-stat.go.jp/lod/sac/C15101',
'町名': '東栄町',
'表記': '新潟県新潟市北区東栄町',
'都道府県': '新潟県',
'都道府県コード': 'http://data.e-stat.go.jp/lod/sac/C15000'})]
IMI 住所変換コンポーネントは、返ってくる結果に郵便番号や座標がないため絞り込めなさそうです。
@geolonia/normalize-japanese-addresses
[ Address(title='新潟県新潟市北区東栄町',
postal_code=None,
pref='新潟県',
city='新潟市北区',
town='東栄町',
addr='',
coords=(37.952531, 139.183275),
raw={ 'addr': '',
'city': '新潟市北区',
'lat': 37.952531,
'level': 3,
'lng': 139.183275,
'pref': '新潟県',
'town': '東栄町'})]
こちらは座標が含まれているので、座標から推測をすると「ひがしさかえまち」の方が出ているようです。
「とうえいちょう」 2個
「ひがしさかえまち」 3個
不明 1個
という結果になりました。
「ひがしさかえまち」が返ってくる方が優勢になっていそうですね。
②浦安市舞浜2-11
これはディズニーアンバサダーホテルの住所で、この住所の難しさは「2-11」の部分にあります。
2-11の2は、なんと丁目ではなくいきなり番地からはじまるのです。
つまり、この住所は「千葉県浦安市舞浜2番地11号」を指しているのです。
一方で、「千葉県浦安市舞浜二丁目11番」も存在しており、こちらを返すのも不正解というわけではないようです。
これは冒頭に言及したこの記事によれば、舞浜には住居表示を実施している地域とそうでない地域があるためで、前者は丁目から始まるが後者は番地から始まるとのこと。
Google Places API
[ Address(title='日本、〒279-0031 千葉県浦安市舞浜2丁目11',
postal_code='本',
pref=None,
city=None,
town=None,
addr=None,
coords=(35.6412904, 139.8810483),
raw={ 'formatted_address': '日本、〒279-0031 千葉県浦安市舞浜2丁目11',
'geometry': { 'location': { 'lat': 35.6412904,
'lng': 139.8810483},
'viewport': { 'northeast': { 'lat': 35.64259277989272,
'lng': 139.8824143298927},
'southwest': { 'lat': 35.63989312010728,
'lng': 139.8797146701073}}},
'icon': 'https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png',
'icon_background_color': '#7B9EB0',
'icon_mask_base_uri': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet',
'name': '日本',
'place_id': 'ChIJdaPal2x9GGARpqGaRejiggo',
'reference': 'ChIJdaPal2x9GGARpqGaRejiggo',
'types': ['sublocality_level_4', 'sublocality', 'political']})]
Google Places APIは舞浜2丁目11の方が出てきました。
Nominatim
[]
Nominatimは結果が出てきませんでした。
Nominatimは23区などの都市は正確な情報が返ってくるイメージがあるのですが、前回に引き続いてこちらは難しかった模様。
jageocoder
[ Address(title=' 千葉県浦安市舞浜二丁目11番',
postal_code=None,
pref='千葉県',
city='浦安市',
town='舞浜',
addr='二丁目11番',
coords=(35.641178131103516, 139.88087463378906),
raw={ 'fullname': ['千葉県', '浦安市', '舞浜', '二丁目', '11番'],
'id': 11645817,
'level': 7,
'name': '11番',
'note': '',
'priority': 3,
'x': 139.88087463378906,
'y': 35.641178131103516})]
jageocoderも2丁目11。
国土地理院地図API
[ Address(title='千葉県浦安市舞浜二丁目11番',
postal_code=None,
pref=None,
city=None,
town=None,
addr=None,
coords=(35.641178, 139.880875),
raw={ 'geometry': { 'coordinates': [139.880875, 35.641178],
'type': 'Point'},
'properties': {'addressCode': '', 'title': '千葉県浦安市舞浜二丁目11番'},
'type': 'Feature'})]
国土地理院地図APIも同じく2丁目11。
IMI 住所変換コンポーネント
[ Address(title='千葉県浦安市舞浜2番地11号',
postal_code=None,
pref='千葉県',
city='浦安市',
town='舞浜',
addr='舞浜2番地11号',
coords=None,
raw={ '@type': '住所型',
'号': '11',
'市区町村': '浦安市',
'市区町村コード': 'http://data.e-stat.go.jp/lod/sac/C12227',
'町名': '舞浜',
'番地': '2',
'表記': '浦安市舞浜2-11',
'都道府県': '千葉県',
'都道府県コード': 'http://data.e-stat.go.jp/lod/sac/C12000'})]
IMI 住所変換コンポーネントは2番地11。
@geolonia/normalize-japanese-addresses
[ Address(title='千葉県浦安市舞浜二丁目11',
postal_code=None,
pref='千葉県',
city='浦安市',
town='舞浜二丁目',
addr='11',
coords=(35.641821, 139.882566),
raw={ 'addr': '11',
'city': '浦安市',
'lat': 35.641821,
'level': 3,
'lng': 139.882566,
'pref': '千葉県',
'town': '舞浜二丁目'})]
こちらは2丁目11。
ということで、集計をしたら
「舞浜二丁目11」 4個
「舞浜2番地11」 1個
という結果になりました。
二つの住所システムが混ざっていることが前提である場合は、どっちに寄せるといいんでしょうね。候補を二つに絞って複数個返すとか?
基本的に建物まで特定する必要がある場合は「二丁目11」は、さらに号が含まれるはずなので、「2-11」は基本的に「2番地11」と考えてもよい、というふうに判定しても良いのかもしれませんね。
余談ですが「千葉県浦安市舞浜2番地11号」でそれぞれの解析器を実行をしたら、次の4つは丁目で返しませんでした。
jageocoder (番地まで)
国土地理院 (番地まで)
IMI 住所変換コンポーネント
@geolonia/normalize-japanese-addresses
③長野県下田市2丁目4-26
こちらの住所は字がなく、下田市の次にいきなり丁目から始まるパターン。
そのため、市区町村名の次は町名、そして字・町丁というルールで解析をすると失敗したりします。
Google Places API
[ Address(title='日本、〒415-0022 静岡県下田市二丁目4−26',
postal_code='本',
pref=None,
city=None,
town=None,
addr=None,
coords=(34.6741088, 138.9444326),
raw={ 'formatted_address': '日本、〒415-0022 静岡県下田市二丁目4−26',
'geometry': { 'location': { 'lat': 34.6741088,
'lng': 138.9444326},
'viewport': { 'northeast': { 'lat': 34.67552247989272,
'lng': 138.9456550798927},
'southwest': { 'lat': 34.67282282010728,
'lng': 138.9429554201072}}},
'icon': 'https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png',
'icon_background_color': '#7B9EB0',
'icon_mask_base_uri': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet',
'name': '〒415-0022 静岡県下田市二丁目4−26',
'place_id': 'ChIJD8E9Qb9YF2ARRbpkSe1estY',
'reference': 'ChIJD8E9Qb9YF2ARRbpkSe1estY',
'types': ['premise']})]
郵便番号や座標が合っているため、うまく解析できていそう。
Nominatim
[]
今回もNominatimは結果が出てきませんでした。
jageocoder
[ Address(title=' 静岡県下田市二丁目4番26号',
postal_code=None,
pref='静岡県',
city='下田市',
town='二丁目',
addr='4番26号',
coords=(34.674163818359375, 138.94419860839844),
raw={ 'fullname': ['静岡県', '下田市', '二丁目', '4番', '26号'],
'id': 20824155,
'level': 8,
'name': '26号',
'note': '',
'priority': 4,
'x': 138.94419860839844,
'y': 34.674163818359375})]
問題なく解析されていそう。
国土地理院地図API
[ Address(title='静岡県下田市二丁目4番26号',
postal_code=None,
pref=None,
city=None,
town=None,
addr=None,
coords=(34.674164, 138.944199),
raw={ 'geometry': { 'coordinates': [138.944199, 34.674164],
'type': 'Point'},
'properties': {'addressCode': '', 'title': '静岡県下田市二丁目4番26号'},
'type': 'Feature'})]
国土地理院地図APIも座標を確認すると問題なく解析できていそう。
IMI 住所変換コンポーネント
[ Address(title='静岡県下田市',
postal_code=None,
pref='静岡県',
city='下田市',
town=None,
addr='静岡県下田市',
coords=None,
raw={ '@type': '住所型',
'メタデータ': {'@type': '文書型', '説明': '該当する町名が見つかりません'},
'市区町村': '下田市',
'市区町村コード': 'http://data.e-stat.go.jp/lod/sac/C22219',
'表記': '静岡県下田市2丁目4-26',
'都道府県': '静岡県',
'都道府県コード': 'http://data.e-stat.go.jp/lod/sac/C22000'})]
IMI 住所変換コンポーネントは下田市までは解析できたが、以降は「該当する町名が見つかりません」というメッセージが返ってきている。
@geolonia/normalize-japanese-addresses
[ Address(title='静岡県下田市二丁目4-26',
postal_code=None,
pref='静岡県',
city='下田市',
town='二丁目',
addr='4-26',
coords=(34.673976, 138.944139),
raw={ 'addr': '4-26',
'city': '下田市',
'lat': 34.673976,
'level': 3,
'lng': 138.944139,
'pref': '静岡県',
'town': '二丁目'})]
ということで、今回は
Googla Places API
jageocoder
国土地理院API
@geolonia/normalize-japanese-addresses
では、うまく解析できました。
他にもいろいろ難しい住所はいっぱいありますが、力尽きたのでここまで。
解析が難しい住所のテストデータ
jageocoderの作者である相良さんが解析の難しい住所のデータを集めて公開されているようです。
今回は使用しなかったのですが、興味がある方はぜひ使ってみてはどうでしょうか。
ということで、おしまい。
(メディア研究開発センター・新妻巧朗)