JR東日本の路線を1本の線に繋ぐまでに踏んだ罠を全部書く

路線図を描くのに鉄道の線形データが要るようになって、国土数値情報のN02を落としてきた。国土交通省が出してる鉄道データで、線形と駅の座標が両方入ってる。これを路線ごとのKMLに書き出して、Googleマイマップに突っ込めば終わりだと思ってた。

終わらなかった。東北新幹線に29.6kmの直線が生えたり、山手線が23.5kmになったり、京葉線の本線が途中で別の線に取られたりして、まともに1本の線になるまで丸一日かかった。同じことをやる人のために、詰まった場所を全部書いておく。

入手

ダウンロードは国土数値情報のN02のページから⁽¹⁾。年度ごとにファイルが分かれてて、URLの年度部分を差し替えて404かどうか見るだけで最新が分かる。サイズは15MBくらい。

ここで最初に得をした。zipの中にUTF-8のGeoJSONが同梱されてる。N02のフォルダを開くとShapefileとGMLとGeoJSONが入ってて、GeoJSONはそのままPythonの標準ライブラリで読める。GDALもpyshpも要らない。Windows機にogr2ogrを入れる作業から解放されるので、これは覚えておいて損がない。

属性

路線も駅も属性の付き方は同じで、N02_001が鉄道区分、N02_002が事業者種別、N02_003が路線名、N02_004が事業者名になる。駅にはさらにN02_005の駅名とN02_005cの駅コードが付く。

鉄道区分は11が普通鉄道で、23がモノレールなど。事業者種別は1が新幹線、2がJR在来線、3が公営、4が民営、5が三セクだ。事業者名は正式名称なので、JR東日本を引くときは東日本旅客鉄道で探すことになる。

路線名の癖

ここで1回つまずいた。路線名が戸籍上の呼び方で入ってる。東北本線が東北線、奥羽本線が奥羽線と、本が落ちる。

もっと厄介なのは、運転系統の名前が存在しないことだ。宇都宮線も埼京線も上野東京ラインも湘南新宿ラインもN02には無い。全部それぞれの戸籍上の路線に吸収されてる。山形新幹線と秋田新幹線も同じで、奥羽線と田沢湖線の中に入ってる。新幹線という独立した路線があると思ってたんだけど、法令上のミニ新幹線は在来線の改軌なので、データもそうなってる。

もうひとつ、同じ駅が区間ごとに複数のフィーチャに分かれてる。だから駅を数えるときはN02_005cの駅コードで統合しないと重複する。JR東日本の駅は生で1804件あって、統合すると1767件になった。40件近くずれるので、駅数を数えるだけの用途でも引っかかる。

海沿いを走る単線の線路と集落
地図の上では1本の線でも、現地ではこういう単線が海際を通っている。道南いさりび線の車窓から。

線が繋がらない

本番はここから。線形は細切れの線分の集まりで入ってるので、路線ごとに繋いで1本にしたい。端点が一致するものを繋げばいいと思うでしょう。繋がらない。

元データの端点は完全一致しない。上下線が別の線形で入ってる区間の駅では、隣接する区間の端点が微妙にずれてる。実測すると上越線の敷島で0.9m、中央線の立川で30m、東海道線の横浜で35mずれてた。完全一致で繋ぐと本線がそこでぶつ切りになる。

で、許容を50mにすると繋がる。ただし70mから300m離れた孤立端点は側線や引上線の行き止まりなので、ここまで拾うと本線じゃないものが混ざる。50mというのはこの2つの間で決めた値だ。

それとT字の接続がある。ある線分の途中の頂点に、別の線分の端点が乗ってる場所だ。常磐線のJヴィレッジがこれで、端点同士しか見ないと永久に繋がらない。他の線分の端点が乗ってる頂点で、あらかじめ線分を割っておく必要がある。

本線をどう選ぶか

繋がるようになっても、次はどれが本線かという問題が出てくる。支線や貨物線や上下線分離区間があるので、線分の集合を素直に歩くと本線が途中で支線に逸れる。

しかもファイル内の線分の順番は路線に沿っていない。隣り合う線分が続けて並んでる割合を数えたら15.3%しかなかった。つまりリスト順で最初のものから歩き始めるという規則は実質ランダムで、京葉線の二俣新町では本線の続きが先に別のチェーンに取られてしまった。

試行錯誤の末に落ち着いたのは、線分をグラフとして扱って、行き止まりのノードの対のうちその路線の駅を最も多く通る最短経路を本線にするという決め方だった。

距離が最大の経路を選ぶ方式だと失敗する。東海道線では貨物線の末端である羽沢横浜国大に終点が落ちるし、支線が両端で本線と繋がって環になってる中央線では、支線の中間の小野に終点が落ちる。駅数を基準にすると旅客の本線が勝つ。中央線の辰野支線も、みどり湖経由の11.2kmのほうが辰野経由の25.9kmより短いので、最短路がみどり湖を通って辰野支線は別チェーンに落ちる。狙いどおりだ。

残りの線分は貪欲に歩く。分岐では、鋭角に折れない候補のうち進行方向にいちばん揃うものを繋ぐ。向きを取る範囲は接点から150mにした。1辺だけで向きを決めると数mのノイズで誤判定する。どの線分から始めるかで結果が変わるので、全部から試し歩きして最長になるチェーンを採用してる。

途中で試して駄目だったのが、分岐で相互に最良かどうかを見る方式だ。合流点では同じ路線のもう片方の線路がライバルとして数えられて僅差で負けるので、駅ごとに線が切れる。平行してるものを除外する閾値を入れても、浅く合流する支線と大きく開く上下線分離を同じ角度基準では区別できなかった。

検算

繋いだ結果が正しいかは、営業キロと突き合わせると分かる。

東北線の盛岡から東京までが534.7kmで営業キロが535.3km、常磐線の日暮里から岩沼までが343.1kmで営業343.7km、奥羽線の福島から青森までが483.7kmで営業484.5km。八戸線と飯山線と只見線と五能線は0.1%以内で一致した。ここまで合えば繋ぎ方は正しい。

合わないときの読み方も要る。磐越西線が会津若松で2本に割れるのはスイッチバックで進行方向が反転するからで、これは正しい挙動だ。信越線が3本になるのは三セク転換で路線自体が分断されてるから。山手線が20.6kmではなく23.5kmになるのは、山手貨物線が同じ路線名で入ってるからだった。

新幹線を検算するときは実キロではなく営業キロと比べること。東北新幹線が674.9km、上越新幹線が大宮から新潟まで269.5kmになる。

あともうひとつ効く検算があって、隣接する点の間の距離の最大値を、元データのそれと突き合わせる。線分を繋ぐときに反転の条件を取り違えると、経路が折り返して長い直線が生える。最初にやったときは東北新幹線に29.6kmの飛びが出た。直したら元データと同じ1.84kmに収まって、この最大値は吾妻線の区間だった。値が跳ねてたら繋ぎ方を疑えばいい。

KMLの落とし穴

最後にKMLで書き出すところでもう1回引っかかった。Google Earthでは普通に開くのに、Googleマイマップにインポートすると弾かれる。

原因はStyle要素の中の順番だった。OGC KML 2.2のスキーマでは、IconStyle、LabelStyle、LineStyle、PolyStyleの順が固定されてる。LineStyleを先に書くと厳格なインポータで落ちる。Google Earthは通してしまうので手元では気付けない。

検証はlxmlでスキーマを当てればできる。OGCからogckml22.xsdとatom-author-link.xsdを落として、ogckml22.xsdの中にあるxAL.xsdの絶対URLを手元のファイルに書き換えれば、XMLSchemaで検証が通る。

ついでに実用的な話をすると、線と点は別ファイルに分けたほうが事故が少ない。点が大量にあると地図の上で線が埋もれて、点しか読み込まれてないように見える。スタイルのIDもASCIIにしておくほうが安全だ。

駅データの扱い

線形の話ばかり書いたけど、駅のほうにも使いどころがある。

駅は点ではなく線分で入ってる。ホームの範囲を線で持ってるので、駅の位置として1点を取りたいなら中点なり端点なりを自分で決める必要がある。この仕様のおかげで、駅の構内が路線のどの区間に当たるかが分かるという利点もある。

本線を決めるときに駅数を基準にしたのも、この駅データがあるから成立してる。線分と駅の位置関係を見れば、その経路がいくつの駅を通るか数えられる。線形だけのデータだと、どれが旅客の本線かを機械的に決める根拠が無い。

駅コードで統合しないと重複する話は前に書いたとおりだけど、統合するときも注意が要る。同じ駅名でも別会社なら別の駅なので、駅名だけで統合すると新宿や横浜がおかしなことになる。駅コードで統合するのが正しい。

OSMではなくN02を使う理由

鉄道の線形ならOpenStreetMapから取る手もある。実際そっちのほうが手軽だ。

それでもN02を選んだのは網羅性のためだった。OSMは編集する人がいる場所は異常に詳しくて、いない場所は雑になる。都市部の駅は改札の位置まで入ってるのに、地方の廃止直前の路線が抜けてたりする。密度が場所によって違うデータは、全国を一律に処理したいときに困る。

N02は行政が全国を同じ基準で作ったデータなので、粗いなら全国一様に粗い。属性の付き方も全レコードで統一されてる。今回みたいに全路線を機械的に処理したい用途では、この一様さが効く。

逆に、特定の1駅の構内図が欲しいみたいな用途ならOSMのほうが圧倒的に上だ。用途で使い分けるのが正解だと思う。

なぜ路線別に分けたか

出力を路線ごとのKMLに分けたのは、マイマップのレイヤ上限があるからだ。1つの地図に載せられるレイヤ数には制限があるので、全路線を1ファイルに入れると扱いにくい。

それと乗りつぶしの用途では、路線単位でオンオフしたい。乗った路線を消していくとき、ファイルが分かれてるほうが管理しやすい。最初は全部1ファイルにしたんだけど、4147線分を1枚に載せると重くて操作にならなかった。

分けるときのファイル名は路線名そのままにしてある。戸籍上の名前なので東北線とか奥羽線という並びになるけど、慣れれば問題ない。むしろ運転系統で分かれてないぶん、路線の全体像が掴みやすい面もある。

結果

最終的にJR東日本の69路線、4147線分を路線別のKMLに書き出せた。営業キロとの一致で検算済みなので、地図に重ねても変な飛びは出ない。

やってみて思ったのは、公的なオープンデータは中身が正確なのと、そのまま使えるのは別だということだ。N02のデータ自体に間違いは無い。ただ、線分の並び順にも端点の一致にも、こっちが期待していい保証は何も無かった。使う側で繋ぎ方を決めて、営業キロで検算するところまでやって、ようやくデータになる。