はてだBlog(仮称)

私的なブログど真ん中のつもりでしたが、気づけばWebサイト系のアプリケーション開発周りで感じたこと寄りの自分メモなどをつれづれ述べています。2020年6月現在、Elasticsearch、pandas、CMSなどに関する話題が多めです。...ですが、だんだんとより私的なプログラムのスニペット置き場になりつつあります。ブログで述べている内容は所属組織で販売している製品などに関するものではなく、また所属する組織の見解を代表するものではありません。

駅データ.jpを使って路線図を描いてみる

全国の路線をカバーしていて、比較的使いやすい元データで、ある程度結果データ量がコンパクトになると思われるような駅・路線データはないかな〜、それを使って(データ量のトレードオフとして駅と駅の間は直線でもよいので)路線図をplotツールなどで可視化したいな(なんでGeoJSONとか扱えたら良いな)〜 と思って、Pandas、GeoPandas、shapelyを使ってチャレンジしてみました。

こんな感じ f:id:azotar:20200422223352p:plain

ある緯度経度の地点の集合データがある場合に、これをインプットにshapelyを使って、GeoPandas、またそれに非常に結びつきの強い、シェープデータ や GeoJSONデータを生成するには... という例の入り口部分のサンプルになっているかと思います。

駅データ.jp(の接続駅)と今回のアプローチ

ekidata.jp

「接続駅」という、隣り合った駅の関係を表すデータが提供されています。

山手線でいうと

駅コード1 駅コード2
新宿駅の駅コード 代々木駅の駅コード
代々木駅の駅コード  原宿駅の駅コード

... のようになっています。

また、駅コードに対応する駅の情報と座標も提供されているので、駅コードと結合して、「新宿駅の座標と代々木駅の座標」を得るとともに、これらを結ぶ直線(shapely.geometry.LineString)を形成、あとはGeoPandasのdissoleveで路線ごと(例. 山手線ごと)に直線を結合してやればどうにかなるのではという考え方で進めています。

サンプルコード

規約に同意して駅データ.jpをダウンロードします。 駅データ 無料ダウンロード 『駅データ.jp』

joinYYYYMMDD.csvなどのファイルがZIPファイルに含まれます。 これらを読み込んで、データ加工します。

import pandas as pd
from shapely.geometry import LineString
import geopandas as gpd
from matplotlib import pyplot as plt

l =['company20200309.csv',
    'join20200306.csv',
    'line20200306free.csv',
    'station20200316free.csv']
df = {}
for f,k in zip(l,list('cjls')): #中途半端な手抜き。頭文字です。
    df[k] = pd.read_csv(f,dtype=str)
df['c']
df['j']
df['s']

# 接続駅情報をもとに、2つの駅を結ぶデータを作成 ----------

# 接続駅情報に駅情報(座標)を対応づける
_joins = pd.merge(pd.merge(df['j'],
    df['s'],
    left_on='station_cd1',right_on='station_cd',how='inner',suffixes=('','_r')),
    df['s'],
    left_on='station_cd2',right_on='station_cd',how='inner',suffixes=('','_r2'))
col = ['line_cd', 'station_cd1', 'station_cd2','station_name','lon', 'lat','station_name_r2', 'lon_r2', 'lat_r2']
joins = _joins[col].copy()
joins

def to_linestring(s):
    """
    2つの座標のポイントを受け取って、shapelyのLineStringオブジェクトを生成
    """
    s11,s12,s21,s22 =map(float,[s['lon'],s['lat'],s['lon_r2'],s['lat_r2']])
    return LineString(  [[s11,s12],[s21,s22]])

# geometryカラムにLineStringオブジェクトを格納
joins['geometry'] = joins.apply(to_linestring,axis=1)

# geometry情報(shapelyのLineString)を持つPandas.DataFrameをインプットに GeoPandas.GeoDataFrameを生成する

gdf = gpd.GeoDataFrame(joins)
ax = gdf.plot(color='#ffcccc',linewidth=1.2)
# ここまでで、うまくいっているかの確認のため、matplotlibでデータの可視化をしてみる。
plt.show()

# GeoJSONファイル(ここでは、一旦 文字列型の変数に格納するところまで)('line_cd'でdissolveすることで、1路線1レコードのデータになる)
gdfgeojson = gdf.dissolve(by='line_cd').to_json()

※ REPLなどでコピペすると変数の中身を確認しやすいように、df['c']などの何もしない式を入れてあります。つまり冗長です。

Shapely

Shapelyは、PythonGISを取り扱うためのライブラリのひとつです。私は詳しくないのですが、GIS系の解説記事をネットで公開されている方のページなどをみるとかなりメジャーなライブラリのようです。

(あまりはっきりしたことは覚えていないのですが)GeoPandasをインストールすると依存関係で一緒にインストールされたような気がします。間違ってたらごめんなさい。

pypi.org

関連ページ

GeoPandasについてはこの記事で使用したメソッドなどについて補足してあります。

itdepends.hateblo.jp

また、うちでGeoJSONと称して、似たテーマの記事をパラパラと書いています。本ページでは...GeoJSONに今のところひとこともふれていませんが...

もしご興味があればごらんください。

itdepends.hateblo.jp