この山手線 3D ジオラマは、地図の上を鉄道模型が走ります。ただ適当に走るのではなく、実際の線路の形をなぞって、内回りと外回りがすれ違いながら走るようにできています。
公開してから何度か「地図データに線路が入っているんですか?」と聞かれました。答えは いいえ です。このアプリで一番時間を吸い取られたのは、3D でも WebGL でもなく、地図の上に見えない線路を手で引く作業でした。今回はその話を書きます。
1. 「線に沿って動かす」のは難しくない。難しいのは「その線」
地図上で何かを線に沿って動かす処理そのものは、ライブラリが面倒を見てくれます。このアプリでも turf.js を使っていて、座標の配列を turf.lineString() に渡せば GeoJSON の線になりますし、隣り合う点の距離と方位は turf.distance() と turf.bearing() で取れます。あとは経過時間から進んだ距離を求め、線上のどこにいるかを引けば、車両は線に沿って進みます。
つまり技術的な壁は、動かす処理の側にはありません。壁は 「その線をどこから持ってくるのか」 の方にあります。
地図ライブラリも経路探索 API も、線路そのものの形を渡してはくれません。経路探索の類は道路や徒歩が主戦場で、鉄道は「駅と駅を結んだ概略」として扱われるのが普通です。それでも地図上に何かを走らせることは一応できますが、出てくるのはだいたいの位置です。駅の手前で線路から数十メートル浮いた場所を走る模型は、ジオラマとしては成立しません。
2. 公開されている路線データが使えなかった理由
路線の緯度経度を公開しているデータもあります。最初はそれで済ませるつもりでした。しかし地図に重ねた瞬間に諦めました。線路とズレるのです。それも、見なかったことにできる程度のズレではありませんでした。
そしてもうひとつ、決定的な問題がありました。
公開データの多くは、上り線と下り線を区別していない。路線に 1 本だけ、どちらにも寄らない概略の線が引かれている。
路線図として形を示すだけなら、これで十分です。線の役割が「この路線はここを通る」を伝えることだからです。しかしこのジオラマでは、内回りと外回りの編成がすれ違うことが見どころのひとつになっています。線が 1 本しかないということは、内回りと外回りが同じ線路の上を重なって走るということで、すれ違いようがありません。複線を複線として走らせるには、最初から線が 2 本必要でした。
ズレていて、しかも 1 本しかない。結論は単純でした。自分で引くしかない。
3. 1,644 点を手で置く
やったことは、言葉にすると身も蓋もありません。地図を拡大して線路を表示し、その上を 1 点ずつ、見えない線でなぞっていく。内回りを 1 周、外回りを 1 周。ひたすらそれだけです。
最終的に出来上がったデータがこれです。
- 内回り:866 点(
jt_east_yamanote_in.js) - 外回り:778 点(
jt_east_yamanote_out.js) - 駅:30 点(
jt_east_yamanote_stations.js)
線路だけで 1,644 点。1 周 34.5 キロに対して、平均すると 40 メートルおきに 1 点を置いた計算になります。恐ろしく時間がかかりました。3D モデルを動かすコードを書いていた時間より、この作業の方がはるかに長かったはずです。
中身はこういう、ただの座標の羅列です。
window.yamanoteInLine = [
[139.766828, 35.681988],
[139.766991, 35.682348],
[139.767158, 35.682682],
...
];
見ての通り、技術的に高度なことは何ひとつありません。根気だけが要求されるデータです。
4. 点の置き方が、走りの質を決める
ただの単純作業かというと、そうでもない部分がありました。点をどこに、どれくらいの間隔で置くかが、そのまま走行の見え方に効いてきます。
このアプリでは、車両の向き(方位)を隣り合う 2 点から turf.bearing() で求めています。つまり 点の並びがそのまま車両の姿勢になるということです。
ここから導かれる結論は単純で、直線区間は点が粗くても破綻しませんが、カーブで点を粗く置くと、車両の向きがカクッと折れます。曲線を少ない点で近似すれば折れ線になるので、当然といえば当然です。結果として、カーブでは点を密に、直線では粗く、という置き方に落ち着きました。平均 40 メートルという数字は、あくまで均した値です。
もうひとつ効いてくるのが、駅の位置です。駅の座標(30 点)とのユークリッド距離を見て停車判定をしているため、線路の線が実際の線路からズレていると、ホームではない場所で停まることになります。ここも手で引く精度がそのまま体験に出る部分でした。
5. 答え合わせ
手で引いた線が実際どれくらいの精度なのか、自分でも気になったので、あとから測ってみました。座標を順に結んで、ハバサインの公式で総延長を出すだけです。
- 内回り:34.5 km
- 外回り:34.5 km
山手線の営業キロは 34.5 km です。目視でなぞっただけの線が、1 周分積み上げた結果として実際の営業キロに一致しました。正直、ここまで合うとは思っていませんでした。地味な作業でしたが、報われた気がした瞬間です。
まとめ:次は、もっと短い路線で
この記事で言いたかったことは、ひとつだけです。地図上を「それらしく」動かすことと、「線路の上を」動かすことの間には、ライブラリでは埋まらない距離がある。その距離を埋めたのは、アルゴリズムでも API でもなく、ひたすら点を置き続けた時間でした。
山手線でこの仕組みができたので、他の路線バージョンも作ってみたいと思っています。ただ、次にやるなら もう少し距離の短い路線にするつもりです。1 周 34.5 キロを複線で引く作業を、もう一度最初からやる気力は、正直なところ今はありません。
実際に走っているところは シミュレーター で見られます。カーブを曲がるときの車両の向きに、この 1,644 点が効いていると思って眺めてもらえると嬉しいです。