住所から最寄駅をGeoPyで調べる

住所から最寄駅をGeoPyで調べる

公開日: 2022-10-26

はじめに


ある場所に行くにはどこが最寄りになるのかなど、住所から最寄り駅を知りたい場面があったりします。
この記事では著者がとあるチェーン店が何駅にあるか調べる際に使用した方法を紹介します。

調べる方法はいくつかありますが、今回はPythonのGeoPyを使って、緯度経度から駅までの距離を計算して最寄駅を取得します。

駅のデータを取得する


駅のデータは駅データ.jpというサイトのデータに緯度経度情報があるのでこちらを利用します。
今回は駅の情報として、駅名、路線名, 鉄道会社を取得してみます。

無料会員登録をしてデータダウンロードから、事業者データ、路線データ、駅データをダウンロードします。
CSVを見てみると、事業者データと路線データはcompany_cdで、路線データと駅データはline_cdで紐づけられるようです。
紐づける方法はデータ分析などでよく使われるPandasを使ってみます。
実行すると駅名, 緯度, 経度, 路線名, 鉄道会社のDataFrame型のデータが取得できます。

import time
import pandas as pd

STATION_CSV = "./input/station20220720free.csv" # 駅データ
LINE_CSV = "./input/line20220720free.csv" # 路線データ
COMPANY_CSV = "./input/company20220401.csv" # 事業者データ

# CSVをPandasのDataFrame型で呼び出す
df_company = pd.read_csv(COMPANY_CSV)
df_line = pd.read_csv(LINE_CSV)
df_station = pd.read_csv(STATION_CSV)

# df_lineを必要なものに絞ってdf_stationとline_cdで紐づける
df_station = pd.merge(df_station,
                      df_line[["line_cd", "company_cd", "line_name"]],
                      how="left",
                      on="line_cd")
# さらにdf_companyを必要なものに絞ってcompany_cdで紐づける
df_station = pd.merge(df_station,
                      df_company[["company_cd", "company_name"]],
                      how="left",
                      on="company_cd")

# 余分なカラムを除外
df_station = df_station[["station_name", "lon", "lat", "line_name", "company_name"]]
# station_name	lon	lat	line_name	company_name
# 0	函館	140.726413	41.773709	JR函館本線(函館~長万部)	JR北海道
# ...

住所から緯度経度を取得する


次に調べたい住所の緯度経度をAPIで取得します。
Google MapのAPIなどいくつかありますが、今回は国土地理院のAPIを使用します。
APIを叩くとGeoJSONが返ってくるので、そこから緯度経度を取得します。
実行すると緯度、経度のリストが取得できます。

import requests
import urllib

GEO_API = "https://msearch.gsi.go.jp/address-search/AddressSearch?q="
address = "東京都品川区上大崎2丁目25−2 新目黒東急ビル"

# addressをURLエンコードする
s_quote = urllib.parse.quote(address)
response = requests.get(GEO_API + s_quote)
lat_lon = response.json()[0]["geometry"]["coordinates"]
# 緯度、経度の順にする
lat_lon.reverse()
# [35.634674, 139.714447]

緯度経度から距離計算して最寄駅を取得する


駅と調べたい住所の緯度経度が取得できたので、geopyで距離計算して最寄駅を求めます。
GeoPyを使うと以下のように簡単に緯度経度から距離を求めることができます。

from geopy.distance import geodesic

# JR目黒駅
lat_lon_1 = [35.633923, 139.715775] 
# 先ほどのaddressの緯度経度
lat_lon_2 = [35.634674, 139.714447]

distance = geodesic(lat_lon_1, lat_lon_2).km
distance
# 0.14632914793881704

距離を取得する方法がわかったので、あとはループさせて各駅からの距離を計算し、最小となる駅を求める形になります。

Pandasのapplyを使って、各行ごとに処理を行って距離をつけます。

# 先ほどのコードを関数化
def get_distance_with_latitude_longitude(lat_lon_1: list[float], lat_lon_2: list[float]) -> float:
    return geodesic(lat_lon_1, lat_lon_2).km

# 駅データのDataFrameにdistanceというカラムで調べたい住所との距離を追加します
df_station["distance"] = df_station.apply(lambda x: get_distance_with_latitude_longitude(target_ll, [x["lat"], x["lon"]]), axis=1)
# DataFrameを一番距離が小さいものに絞ります
nearest_station = df_station[df_station["distance"] == df_station["distance"].min()]
# 必要のないカラムを落とします
nearest_station = nearest_station.drop(["lon", "lat"], axis=1)
nearest_station
# 	station_name	line_name	company_name	distance
# 1287	目黒	JR山手線	JR東日本	0.146329

まとめたコード


上記をまとめると以下のようになります。

import time
import requests
import urllib
from tqdm import tqdm
import pandas as pd
tqdm.pandas()
from geopy.distance import geodesic

STATION_CSV = "./input/station20220720free.csv" # 駅データ
LINE_CSV = "./input/line20220720free.csv" # 路線データ
COMPANY_CSV = "./input/company20220401.csv" # 事業者データ
GEO_API = "https://msearch.gsi.go.jp/address-search/AddressSearch?q="


def get_latitude_longitude_from_address(address: str) -> list[float]:
    """
    住所からlatitude, longitudeを取得
      - return: [latitude, longitude]
    """
    s_quote = urllib.parse.quote(address)
    response = requests.get(GEO_API + s_quote)
    lat_lon = response.json()[0]["geometry"]["coordinates"]
    lat_lon.reverse()
    # ループさせたりして何度も叩く際は待つようにする
    # time.sleep(2)
    return lat_lon


def get_station_latitude_longitude(pref_cd: int = 0) -> pd.DataFrame:
    """
    駅の緯度経度を取得する
      - pref_cd: 対象の駅の都道府県
      - return: pd.DataFrame[駅名, latitude, longitude, 路線名, 鉄道会社]
    """
    df_company = pd.read_csv(COMPANY_CSV)
    df_line = pd.read_csv(LINE_CSV)
    df_station = pd.read_csv(STATION_CSV)
    df_station = pd.merge(df_station,
                          df_line[["line_cd", "company_cd", "line_name"]],
                          how="left",
                          on="line_cd")
    df_station = pd.merge(df_station,
                          df_company[["company_cd", "company_name"]],
                          how="left",
                          on="company_cd")
    if pref_cd >= 1 and pref_cd <= 47:
        df_station = df_station[df_station["pref_cd"] == pref_cd]

    df_station = df_station[["station_name", "lon", "lat", "line_name", "company_name"]]
    return df_station


def get_distance_with_latitude_longitude(lat_lon_1: list[float], lat_lon_2: list[float]) -> float:
    return geodesic(lat_lon_1, lat_lon_2).km


def get_near_station(target_ll: list[float], df_station: pd.DataFrame) -> pd.DataFrame:
    """
    return: pd.DataFrame[駅名, 路線名, 鉄道会社, 距離(km)]
    """
    df_station["distance"] = df_station.progress_apply(lambda x: get_distance_with_latitude_longitude(target_ll, [x["lat"], x["lon"]]), axis=1)
    nearest_station = df_station[df_station["distance"] == df_station["distance"].min()]
    nearest_station = nearest_station.drop(["lon", "lat"], axis=1)
    return nearest_station


def main():
    target_address = "東京都品川区上大崎2丁目25−2 新目黒東急ビル"
    target_ll = get_latitude_longitude_from_address(target_address)
    df_station = get_station_latitude_longitude()
    nearest_station = get_near_station(target_ll, df_station)
    print(f"nearest_station: {nearest_station}")


main()

参考記事



カテゴリ: