「東京ドーム何個分」がわからない自分のために東京ドームの大きさを体感できるWebサイトを作った
この記事はOpenStreetMap Advent Calendar 2024 20日目の記事です。
みなさんは東京ドームへ足を運んだことはあるでしょうか?
東京ドームは野球やライブが行われる施設であると理解していますが、私は野球ファンというわけでもなく、ライブに通うほどの音楽好きでもないので、数ヶ月前電車に乗っている際に、後楽園駅付近で見えた丸みを帯びた建物がそうなのかな?程度の認識しかありません。
一方、日本では広大な敷地を東京ドームの個数で表現する文化が存在し、ある程度浸透しています。
しかし、行ったことも周りを歩いたこともなく、それっぽい存在を見たことしかない私が、面積の表現として東京ドーム何個分という大きさの表現は理解が難しいことは共感していただけるかなと思います。
そこで、行ったことも周りを歩いたこともない田舎者である私が東京ドームのサイズ感を理解するために「東京ドームn個分」という言葉を実感できるよう、地図上に東京ドームをペタペタしたり、ドラッグして動かしてみることで、馴染みのある場所と大きさを比較できるサイトを作ってみることにしました。
実際のサイトとGitHubのリポジトリはこちらです:
使い方
はじめに貼ったリンクからアクセスすると以下のような地図が大きく表示された画面が出てきます。
この画面の左下にある「追加する」という緑色のボタンを押すことで、表示されている地図の中央に東京ドームを出現させることができます。
あとは、地図に出現した東京ドームをマウスなどでドラッグして思い思いの場所と比較してみてください。
※現状ではスマートフォンで東京ドームを動かすことができません。PCで操作してください。
どのように実装しているか
主に利用したライブラリは以下の通りです:
Webフロントエンドのフレームワーク: Svelte, SvelteKit
地図の描画: MapLibre GL JS, svelte-maplibre-gl
CSSフレームワーク: TailwindCSS
地物の演算?: Turf.js
ここからはSvelteに触ったことがあり、MapLibreを少し触ったことある人向けにどのように実装したかについて説明しようかなと思います。
今回はプログラムを逐一説明するのではなく、MapLibreを使って地図を表示する、Sourceを使ってGeoJSONを読み込む、などの普遍的、基礎的な部分に関しては他の記事や公式サイトに譲って、このサイトを実装するにあたって特別実装した部分について主に説明しようと思います。
東京ドームの形を取得する
まずは東京ドームの形をどこからか取ってくる必要があります。
今回はOpenStreetMap(OSM)から取得することにしました。
OSMから特定の地物の形を取得する方法として、overpass turboを経由して取得しました。単に著者がこれ以外の方法が思い浮かばなかったためです[1]。
overpass turboというサイトは、overpass APIというサービスのWebフロントエンドです。overpass APIはOSMに登録されているデータを独自言語(Overpass QL)などによってフィルタリングすることができるサービスです。
今回は、取得したい地物が1つで、かつ明確に決まっているので、OSMが東京ドームに与えているIDをそのままoverpass turboに伝えて取得しようかなと思います。
まずはOSMのサイトで東京ドームに与えられているIDを取得します。
右上にあるテキストボックスか「東京ドーム」と検索し、出てきたものをクリック。
クリックして表示されたページの、大きい文字で括弧書きされている数字がOSMが東京ドームに与えているIDです。このページにおける「20028328」がそれに該当します。
そのままoverpass turboに「OSMのこのIDの地物を取ってきて」というクエリを書きます。クエリとしては以下のように入力しました。
[out:json][timeout:25];
nwr(id: 20028328);
out geom;
これはOSMでのIDが「20028328」である地物を取得して表示するようなOverpass QLになります[2]。
overpass turboが出力するJSONは独自のschemaを持っています。しかし、MapLibreで扱うには別のフォーマットへ変換する必要があったり、TypeScript上で型安全に扱うには型を定義する必要があったりと少々面倒です。そこで、MapLibreでも扱いやすく、TypeScriptの型定義も存在するGeoJSON形式で出力してもらいましょう。
左上から「エクスポート」を選択し、「GeoJSON」の横にある「ダウンロード」ボタンを押すとダウンロードできます。
環境構築
Svelteのファイルを作成する際はnpx sv create
を実行します。私は普段pnpmを使っているのでpnpm dlx sv create
を実行します。ここで私はTailwindCSSを使うよう選択しました。
使用するパッケージをここでインストールします。
pnpm i -D svelte-maplibre-gl @turf/center @turf/helpers
取得した東京ドームのデータはstatic/tokyodome.json
に置きます。
src/route/+page.svelte
を最低限以下のように編集します。
<script lang="ts">
import { base } from '$app/paths';
import { MapLibre, NavigationControl, ScaleControl, GeolocateControl } from 'svelte-maplibre-gl';
</script>
<div class="fixed bottom-0 top-14">
<MapLibre
class="h-full w-screen"
style="https://tile.openstreetmap.jp/styles/osm-bright-ja/style.json"
zoom={15}
center={{ lng: 139.751965, lat: 35.705514 }}
bind:map
>
<NavigationControl />
<ScaleControl />
<GeolocateControl trackUserLocation={true} />
</MapLibre>
</div>
その他、東京ドームのデータをfetchしてstateに入れる処理を$effect
内に記述します。
地図の中心に東京ドームを出現させる
このサイトでは「追加する」というボタンを押したとき、表示されている地図の中心付近に東京ドームを表示させることができます。この機能がどのように実装されているかについて説明します。
まず、構成される点を「正規化」した東京ドームのデータを用意します。ここで、「正規化」とは座標系原点付近に地物を移動させることをそう呼称することにします(GIS系の用語に詳しくないのでそのように表記させてもらいます)。
具体的な手順としては以下の通りです:
- (事前に)東京ドームを構成する複数の点から中点を生成する
- (事前に)中点を座標原点としたときの各点の座標を計算しておく
- (ボタンが押されたら)地図の中心座標を取得し、2で生成した東京ドームの座標と足し合わせて、SourceとLayerを設定する
それぞれ1つ1つ説明します。
まず、東京ドームを構成する複数の点から中点を生成する方法です。
複数の点を入力したときにその点たちの中点を求めてくれる関数はturf.jsにあります。また、turf.jsには中点を取得する関数が幾つかあるようですが、今のところ、center
を使ってみています。
次に、「中点を座標原点としたときの各点の座標を計算しておく」という点ですが、1で求めた中点を原点とするので、各点の座標から中点座標のぶんだけをそれぞれ引いてあげればよいことになります。
最後に、「地図の中心座標を取得し、2で生成した東京ドームの座標と足し合わせて、SourceとLayerを設定する」という点です。まず、「地図の中心座標を取得」という点ですが、MapLibreでは今表示されている地図の中心座標を取得するメソッドがMap
クラスに.getCenter()
メソッドとして実装されているのでこれを使います。次に、2で作成した東京ドームの各点の座標に地図の中心座標を足し合わせます。
3つ目に、生成したGeoJSONをMapLibreのSource、Layerとして設定します。ここでLayerを2つ設定しているのは、東京ドーム自体の形を表示するLayerと外枠を別の色の線で表現するLayerを作っているためです。
イベントハンドラなどもここで設定していますが、このイベントハンドラはすぐ次の章で説明します。
出現させた東京ドームをドラッグできるようにする
出現させた東京ドームを移動させようとドラッグしても現状のままでは地図を移動させるモーションになってしまい、東京ドームを移動させることはできません。東京ドームがドラッグされた時に地図が優先して移動されるのを無効化し、移動している最中は東京ドームをマウスが動くさきへ追従する、という挙動を、イベントハンドラで行う必要があります。
実装するにあたって使用したMapLibreのイベントはmousedown
、mousemove
、mouseup
の3つです。
-
mousedown
とは、地図上でマウスなどのポインティングデバイスのボタンが押された場合に発火するイベントです。 -
mousemove
は地図上でマウスなどのポインティングデバイスが移動した時に発火するイベントです。 -
mouseup
はマウスなどのポインティングデバイスのボタンが離された場合に発火するイベントです。
https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapEventType/
「東京ドームがドラッグされた時に地図が優先して移動されるのを無効化し」という点は、MapLibreのMapクラスにある.dragPan.disable()
というメソッドを呼ぶことで地図が動かなくなります。これをマウスのボタンが押されたタイミングで作動させればよいことになります。さらに、そのままだと東京ドームを動かし終えた後に地図を動かすことができないので、mouseup
イベントが発火したタイミングで.dragPan.enable()
を呼び出してあげる必要があります。
次に「移動している最中は東京ドームをマウスが動く先へ追従する」という処理ですが、これは「動かす対象」と「マウスの現在位置」を保持し、計算することで解決できます。mousedown
が発火したタイミングで「動かす対象」(仮にAという変数とします)と、「発火したタイミングでクリックされた座標」(仮にBという変数とします)をsvelteのstateとしてそれぞれ保持しておきます。mousemove
が発火したタイミングで、(イベントハンドラの引数から取得できる)「動かした先の座標」から、「保持している座標」(変数B)を引き、その差を対象の地物(変数A)の座標に足し合わせて、変数Aを更新します。
mousemove
は小さな動きでも発火するので、これを繰り返すことで、マウスの動きに追従することができます。
しかし、mousemove
イベントは単にマウスを動かしているだけで発火してしまいます。そこで、東京ドームをドラッグしている状態かどうかを見極めるために、フラグでその状態を管理しなければいけません。mousedown
でフラグを立てて、mousemove
での処理の前にフラグが立っているかどうか確認し、mouseup
でフラグを下ろす処理をそれぞれ書く必要があります。
おわりに
これで東京ドームのサイズ感がわからなかった自分にもある程度のサイズ感が掴めるようになりました。
今後としては、OSMのエディタであるiDで建物やエリアを描くように作った図形の面積を「東京ドーム何個分です」と計算してくれる機能を付けると、より実感できるのかなと思っています。
さらに、東京ドームの他にも、面積を表現していることはわかるものの、ピンとはこない言葉としてテニスコートやサッカーコートなどがありますが、こちらについても対応できるとおもしろいのかなと思っています。
-
overpass turboは大量のデータを扱うことができないのですが、今回は東京ドームのデータだけ取得したいのでこの制限にはあたらないというのも真面目な理由の1つとしてあります。 ↩︎
-
詳細なことは https://wiki.openstreetmap.org/wiki/JA:Overpass_API/Overpass_QL を読んでいただきたいのですが、nwrという語はnode、way、relationというOSMにおける地物の形の種類の頭を1文字を取った語で、「node、way、relationの種類を問わない」という意味を持ちます。 ↩︎
Discussion