Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?
@aerialist

いざとなれば会社に通える田舎を探す(その1駅情報取得)

背景

コロナで在宅勤務が続いているため、都会で手狭な家に住んでいるのが苦しくなってきました。週に数日や月に数日しか出社しないなら、通勤に片道2時間ぐらいかかる田舎で暮らすことも現実的かも。(定期代がフルに支給されない)新幹線通勤までも視野に入れれば結構遠くに住めるかも?

でもどの地域を狙うのといいのか?

しばらく前に「東工大の一限に間に合う範囲の地図を作ってみました」というのが話題になってました。

同じような地図を作ってみたいと思い、会社の最寄り駅に平日朝9時に到着するための乗車時間を地図にマッピングしてみました。

手順

  1. 日本全国の駅名と緯度経度を取得
  2. 各駅から最寄り駅までの経路を検索し所要時間を取得
  3. 各駅の位置と所要時間を地図にプロット

これらをできるだけオープンソースのツールか、フリーミアムのサービスの無料枠でなんとかしたい。

今回は駅情報取得について。

経路検索サービスの検討

今回一番ネックになりそうなのが経路検索のところ。hideki@鉄分多め さんの「Google Directions APIで乗り換え案内情報を取ろうとしたが。。。?」ノートを参考に検討。
Google Mapsの路線検索が使えないのは残念。

有料路線検索サービス提供業者の中で、駅すぱあとジョルダンNavitimeは無料枠や無料APIの用意があるようです。

今回はユーザー登録後すぐにアクセスキーを送ってきてくれた「駅すぱあとWebサービス フリープラン」を利用することにします。

駅情報取得

Pythonを利用。外部ライブラリとしてrequestとpandasを使います。

ekispert-get-all-stations.py
import requests
import pandas as pd

# security tokens are defined in secret.py file
# ekispert_token = ""
from secret import ekispert_token

ekispert_endpoint = 'http://api.ekispert.jp'
eki = '/v1/json/station'

from collections import MutableMapping 
def flatten(d, parent_key='', sep='_'):
    """
    flatten dictionary
    https://stackoverflow.com/questions/6027558/flatten-nested-dictionaries-compressing-keys
    """
    items = []
    for k, v in d.items():
        new_key = parent_key + sep + k if parent_key else k
        if isinstance(v, MutableMapping):
            items.extend(flatten(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

offset = 1
#print("offset: {}".format(offset))
payload = {'key': ekispert_token, 
           'offset': offset,
           'limit': 100,
           'gcs': 'wgs84'}
r = requests.get(ekispert_endpoint+eki, params=payload)
result_all = r.json()
offset += 100
n_max = int(result_all['ResultSet']['max'])
#print("Max: {}".format(n_max))

while offset <= n_max:
    #print("offset: {}".format(offset))
    payload = {'key': ekispert_token, 
               'offset': offset,
               'limit': 100,
               'gcs': 'wgs84'}
    r = requests.get(ekispert_endpoint+eki, params=payload)
    result_all['ResultSet']['Point'].extend(r.json()['ResultSet']['Point'])
    offset += 100

stations_flat = [flatten(result) for result in result_all['ResultSet']['Point']]
df = pd.DataFrame(stations_flat)

df['GeoPoint_longi_d'] = df['GeoPoint_longi_d'].apply(float)
df['GeoPoint_lati_d'] = df['GeoPoint_lati_d'].apply(float)

fname = "ekispert_stations_" + result_all['ResultSet']['engineVersion'] + ".pkl"
df.to_pickle(fname)

一部補足説明

offset = 1
#print("offset: {}".format(offset))
payload = {'key': ekispert_token, 
           'offset': offset,
           'limit': 100,
           'gcs': 'wgs84'}
r = requests.get(ekispert_endpoint+eki, params=payload)
result_all = r.json()
offset += 100
n_max = int(result_all['ResultSet']['max'])
#print("Max: {}".format(n_max))

とりあえず一度駅情報を100件分取得。'gcs'を'wgs84'としているのは、地図へのプロットをmapboxのサービスを利用するつもりなので。
2020年1月11日時点では全部で9,274件分のデータが存在するらしい。

while offset <= n_max:
    #print("offset: {}".format(offset))
    payload = {'key': ekispert_token, 
               'offset': offset,
               'limit': 100,
               'gcs': 'wgs84'}
    r = requests.get(ekispert_endpoint+eki, params=payload)
    result_all['ResultSet']['Point'].extend(r.json()['ResultSet']['Point'])
    offset += 100

残りデータをオフセットしながら取得。駅の情報はr.json()['ResultSet']['Point']にリスト形式で存在するので、最初に取得したresult_all['ResultSet']['Point']のリストに追加(extend)していく。

stations_flat = [flatten(result) for result in result_all['ResultSet']['Point']]

駅情報は入れ子構造の辞書形式になっている。扱いづらいので、flatten関数にかけて単純な辞書形式に変換しておく。

flatten前

{'Station': {'code': '26242',
  'Name': '相生(兵庫県)',
  'Type': 'train',
  'Yomi': 'あいおい'},
 'Prefecture': {'code': '28', 'Name': '兵庫県'},
 'GeoPoint': {'longi': '134.28.26.46',
  'lati': '34.49.5.91',
  'longi_d': '134.474019',
  'lati_d': '34.818309',
  'gcs': 'wgs84'}}

flatten後

{'Station_code': '26242',
 'Station_Name': '相生(兵庫県)',
 'Station_Type': 'train',
 'Station_Yomi': 'あいおい',
 'Prefecture_code': '28',
 'Prefecture_Name': '兵庫県',
 'GeoPoint_longi': '134.28.26.46',
 'GeoPoint_lati': '34.49.5.91',
 'GeoPoint_longi_d': '134.474019',
 'GeoPoint_lati_d': '34.818309',
 'GeoPoint_gcs': 'wgs84'}
df = pd.DataFrame(stations_flat)
df['GeoPoint_longi_d'] = df['GeoPoint_longi_d'].apply(float)
df['GeoPoint_lati_d'] = df['GeoPoint_lati_d'].apply(float)

Pandas.DataFrameオブジェクトを作成し、緯度経度情報を文字列からfloatに変換。

fname = "ekispert_stations_" + result_all['ResultSet']['engineVersion'] + ".pkl"
df.to_pickle(fname)

バージョン名をつけてpickle形式で保存。

分析

せっかくなので全国の駅情報をちょっと覗いてみる。

df = pd.read_pickle("ekispert_stations_202101_02a.pkl")

先程保存したファイルからpandas.DataFrameを読み戻す。

df[df['Station_Type']=='train'].groupby('Prefecture_Name').count()['Station_code'].sort_values(ascending=False).plot(kind='bar', figsize=(15,5))

train.png

鉄道の駅の数を都道府県別に並べてみた。当然東京・大阪・愛知の大都市圏が多いですね。そんな中北海道も面積が大きいからか駅数が多い。(でも人は少ないだろうから経営は大変だろうなぁ)

df[df['Station_Type']=='plane'].groupby('Prefecture_Name').count()['Station_code'].sort_values(ascending=False).plot(kind='bar', figsize=(15,5))

plane.png

空港の数でみると、北海道が多いのは納得。東京が10個もあるのかと一瞬驚きますが、大島や三宅島なども東京都だからですね。

pt = df.groupby(['Prefecture_Name', 'Station_Type']).count()['Station_code'].unstack(fill_value=0).stack()
pt[pt==0].index

逆に空港がゼロの県はこれら。

MultiIndex([( '三重県', 'plane'),
            ( '京都府', 'plane'),
            ( '埼玉県', 'plane'),
            ( '奈良県', 'plane'),
            ( '山梨県', 'plane'),
            ( '岐阜県', 'plane'),
            ( '栃木県', 'plane'),
            ( '滋賀県', 'plane'),
            ('神奈川県', 'plane'),
            ( '福井県', 'plane'),
            ( '群馬県', 'plane')],
           names=['Prefecture_Name', 'Station_Type'])

全駅をmapboxを使って地図上にプロットしてみる。

from mapboxgl.utils import df_to_geojson, create_color_stops
from mapboxgl.viz import CircleViz
# security tokens are defined in secret.py file
# mapbox_token = ""
from secret import mapbox_token
points = df_to_geojson(df, 
                      properties=['Station_code', 'Station_Name', 'Station_Type', 'Station_Yomi', 'Prefecture_code', 'Prefecture_Name'],
                      lat='GeoPoint_lati_d',
                      lon='GeoPoint_longi_d',
                      precision=6)
map_center = df[df['Station_Name']=='日本橋(東京都)'].to_dict('records')[0]
viz = CircleViz(points,
                access_token=mapbox_token,
                height='400px',
                radius=10,
                stroke_color='black',
                opacity=0.6,
                label_property='Station_Name',
                center=(map_center['GeoPoint_longi_d'],map_center['GeoPoint_lati_d']),
                style='mapbox://styles/mapbox/outdoors-v11',
                zoom=3,
                )
viz.create_html("all_stations.html")
viz.show()

mapboxgl-jupyterを使うとたったこれだけのコードでぐりぐりできるマップが作れちゃいます。

all_stations.png

こちらにHTML置いておいたのでぐりぐりしてみてください。

Powered by 駅すぱあとWebサービス

2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
2
Help us understand the problem. What is going on with this article?