都道府県・都市・町情報をテーブル管理したい


はじめに

都道府県・都市・町情報をサービスなどで管理するには、一般的にはGoogle Maps APIなどの力を借りてなるべく自分たちで実装しなくて済むようにするのが一番だと思います(住所などの情報は、頻繁ではないものの更新されていくものなので)

ただ、自前でテーブルを作ったりしないと検索機能をつくれなかったり、機械学習の特徴量として位置情報を利用したかったりなどの場合にはどうしても必要になるタイミングがあると思います。

政府統計の境界データ

政府統計のe-Statというサイトがあり、ここでは各町毎の境界データがダウンロードできます。

今回は東京都全域の境界データをみていきます。

shapeファイル

まずzipを解凍すると、見た事のない拡張子が並んでますが、これはすべて必要みたいです。

これらを総称してshapeファイルという(嘘だったらすみません)みたいですが、今回はこのファイルをPythonで扱いたいので必要なパッケージをインストールしていきます。

pip install pyshp shapely

もっと楽にデータの中身をみたい、という方はshapeファイルをpandasのデータフレームのように扱えるgeopandasというパッケージもインストールしておくと便利だと思います。

pip install geopandas

試しにgeopandasでshapeファイルを読み込んでみましょう。

import geopandas as gpd

df = gpd.read_file('h27ka13.shp')

こんな感じのデータになっていると思います。


ざっくりと必要なカラムについての説明をすると、

  • PREF: 都道府県ID(2桁)
  • CITY: 都市ID(3桁)
  • S_AREA: 町ID(6桁)
  • PREF_NAME: 都道府県名
  • CITY_NAME: 都市名
  • S_NAME: 町名
  • geometry: 境界データ(Polygon)

です。(おそらくS_NAMESはSTREETのSだと思われます)

地域情報テーブル

そのため、サービスとしてテーブルをつくるのであれば、

  • prefecturesテーブル
  • citiesテーブル
  • streetsテーブル

を用意すれば良いかと思われます。

あとは、ある地点の緯度経度からprefecture_id, city_id, street_idを導くことができればすべての住所がテーブル管理できます。

prefecture_id, city_id, street_idはそれぞれ桁数が2, 3, 6と決まっているのでこれらを文字列として連結したものを緯度経度から取得できればいいことになります。(これをAddressCodeと呼ぶことにします)

緯度経度と各テーブルの紐付け

ある緯度経度がある町に含まれているということは、その町の境界データgeometryに含まれていることと同値なので、AddressCodeを緯度経度から取得するスクリプトをつくりました。

import shapefile
from shapely.geometry import shape, Point


class AddressCodeGetter:
    def __init__(self, shp_path: str, encoding='cp932'):
        """shapeファイルを指定して読み込む."""
        r = shapefile.Reader(shp_path, encoding=encoding)
        self.shapes = r.shapes()
        self.records = r.records()

    def getAddressCode(self, latitude: float, longitude: float):
        """指定した緯度経度を含むPolygonを見つけて、そのAddressCodeを返す."""
        point = Point(longitude, latitude)
        AddressCode = ''
        for i in range(len(self.shapes)):
            polygon = shape(self.shapes[i])
            if polygon.contains(point):
                AddressCode = ''.join(self.records[i][1:4]) # PREF, CITY, S_AREAを文字列連結
                break
        return AddressCode


code = AddressCodeGetter('h27ka13.shp')
# 六本木ヒルズ森タワー(東京都港区六本木6丁目)
code.getAddressCode(35.660477, 139.729356) # '13103017006'
# 東京都庁(東京都新宿区西新宿2丁目)
code.getAddressCode(35.689604, 139.692305) # '13104094002'
# 皇居(東京都千代田区千代田)
code.getAddressCode(35.685756, 139.752292) # '13101014000'
# 渋谷スクランブルスクエア(東京都渋谷区渋谷2丁目)
code.getAddressCode(35.658778, 139.702487) # '13113010002'

これによって、経度経度からその地点のprefecture_id, city_id, street_idが求められることになりました。

例えば東京都庁の場合は、AddressCodeが13104094002なので

{
  "prefecture_id": 13,
  "city_id": 104,
  "street_id": 94002
}

となります(street_idは094002なので数値化すると94002となる)

おわりに

ある地点のAddressCodeを取得する際、緯度経度から取得するやり方ではなく住所から文字列操作によって行うやり方もありますが、これは結構大変です。意外と町名が最近変わっていたり、市町村合併されていたり、ユーザが入力した住所に旧字体が使われていたために認識できなかったり..など

特にユーザ入力させた住所をテーブルと紐付けるときには、Goole Maps APIから帰ってきた緯度経度を用いてAddressCodeを算出するなどして既存のAPIをうまく併用しながら開発すると快適なGeoライフが送れると思います!