← 読みもの

地図APIを取り払って世界地図を自分で描いた

カウチコロンブスは、はじめGoogle Maps JavaScript APIの上に作られていました。いまは地図を自分で 描いています。なぜ取り払い、何に置き換えたのかを書いておきます。

取り払った理由

Maps JavaScript APIで地図を一度表示することは、ページ読み込みごとに課金される項目 です。 月の無料枠があり、個人プロジェクトがそれを超えることは滅多にありませんが、問題は上限ではなく 構造のほうでした。このサービスで地図が果たしている仕事が、それだけの重さを正当化できていなかった のです。

必要だったのは、この程度でした。

  • 世界の陸の塊を暗い色で塗る
  • 国境線を細く引く
  • 訪れた国だけ別の色で塗る
  • その上に経路の曲線と飛行機の形を載せる

道路も、衛星写真も、リアルタイム交通も、拡大して見る店舗情報も使いません。それなのに、そのために 数百KBのSDKを受け取り、タイルを要求し、ページ読み込みごとに課金カウンターを上げていました。 おまけに大圏距離の計算のためにgeometryライブラリを呼ぶので、旅程を作るたびにMaps JSを丸ごと 受け取る必要がありました。

何に置き換えたか

図形 はNatural Earthを使います。パブリックドメインで、TopoJSONに圧縮された形が公開されて います。このプロジェクトが使う二つはこうです。

ファイル 原本 gzip
countries-110m.json(低解像度) 105KB 38KB
countries-50m.json(高解像度) 739KB 225KB

低解像度を先に描いて地図がすぐ出るようにし、高解像度は後ろで受け取って静かに差し替えます。 はじめから高解像度だけを受け取ると、遅い回線ではしばらく空白の画面が残ります。

投影 と 経路の計算 は d3-geo が担います。正距円筒図法で緯度経度を画面座標へ移し、 geoPath でキャンバスに直接引きます。大圏距離と補間も同じライブラリで行います。Maps JSはいま、 内蔵辞書にない場所を検索するときだけ読み込まれます。

描画 はキャンバス2Dです。SVGで国のポリゴンを数百個DOMに載せると、アニメーションのフレーム ごとにそれを再計算することになります。キャンバスは一度に全部引いて、一度に消します。高解像度の 画面のために devicePixelRatio を掛けますが、2で打ち切ります。3倍で描くとピクセル数が2.25倍に 増えるのに、目では区別がつきません。

移すときに引っかかったこと

陸を塗るときに mesh を使ってはいけません。 TopoJSONには merge と mesh があり、mesh は 線(MultiLineString)を返します。見た目には国の形がすべて出るので正しそうに見えるのですが、その線を fill すると、大陸を横切るくさび形のしみができます。塗りつぶしには、国々を一つの面に統合する merge を使わねばなりません。mesh は国境線を引くときだけ使います。

正距円筒図法は高緯度を横に引き伸ばします。 伸びる比率は緯度のコサインの逆数です。

緯度 横の伸び
0°(赤道) 1.00倍
37.57°(ソウル) 1.26倍
60° 2.00倍
80° 5.76倍

だからソウルからヨーロッパへ向かう大圏経路が、地図の上で大きく上へふくらんで見えます。線が 曲がったのではなく、地図がその場所を引き伸ばしたのです。

残った費用

地図そのものは、いまや無料です。有料で残っているのは目的地の検索だけで、それも366か所の内蔵辞書と 34,000か所の静的インデックスを先に探したあとで、ようやく外部APIへ渡ります。ほとんどの検索は そこまで行きません。

地図APIが悪いという話ではありません。経路案内やリアルタイム交通が必要なら、自分で作る理由は ありません。ただ、必要なものが「暗い背景に大陸の形」であるなら、公開された図形と20行ほどの キャンバスのコードで十分です。