Mapbox でのピン表示について考える

Mapbox で地図上にいわゆる「ピン」を表示する、またピンをクリックすると「吹き出し」を表示させる、[Google Maps SDK ではマーカー](https://developers.google.com/maps/documentation/android-sdk/marker?hl=ja) と呼ばれる機能を実現する方法がいくつか（2つ）あります。それぞれの特徴と使い分けについて考えます。

# 1. マーカー(Marker)

一つ目は Mapbox でも Marker と呼ばれる機能です。

以下は、Mapbox のドキュメントへのリンクです。

- [既定のマーカー(Add a default marker)](https://docs.mapbox.com/mapbox-gl-js/example/add-a-marker/)
- [ドラッグ可能なマーカー(Create a draggable Marker)](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-marker/)
- [アニメーションするマーカー(Animate a marker)](https://docs.mapbox.com/mapbox-gl-js/example/animate-marker/)
- [カスタムアイコンを持ったマーカー(Add custom icons with Markers)](https://docs.mapbox.com/mapbox-gl-js/example/custom-marker-icons/)
- [マーカーにポップアップを追加(Attach a popup to a marker instance)](https://docs.mapbox.com/mapbox-gl-js/example/set-popup/)

Mapbox の「マーカー」は、Google Maps などのそれと同じく、色や形状が変更でき、吹き出し(ポップアップ)を関連付けることができ、ドラッグ可能にすることができます。

# 2. シンボルレイヤー(Symbol Layer)

二つ目の選択肢は Symbol Layer です。
が、実際には Symbol Layer という機能名があるわけではありません。

まず Mapbox には Layer という PhotoShop などのペイントソフトと似たような概念があります。複数の Layer が積み重なって、「地図」が描画されます。

背景地図も「ひとつの Layer」であり、[Mapbox で OpenStreetMaps を表示するだけ](https://zenn.dev/amay077/articles/17b3b359ec2883f7fd4d) の ``index.js`` を見ると ``styles: []`` であることが分かります。

Layer には "fill", "line", "symbol", "circle", "heatmap", "fill-extrusion", "raster", "hillshade", "background" の種類( [``type``](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#type) )があり、上記の背景地図は "raster" です。

Symbol Layer とは、「type が ``symbol`` である Layer」のことです。

Layer に表示するデータを示すものが「ソース(Source)」です。ソースにも様々な種類がありますが、最もよく使われるが GeoJSON です。

Symbol Layer を表示するのに必要なデータは、GeoJSON.Point の地物群ということになります。

以下は、Mapbox のドキュメントへのリンクです。

- [シンボルを追加(Add an icon to the map)](https://docs.mapbox.com/mapbox-gl-js/example/add-image/)
- [属性値に応じてスタイルを変化させる(Generate and add a missing icon to the map)](https://docs.mapbox.com/mapbox-gl-js/example/add-image-missing-generated/)
- [移動するシンボル(Animate a point)](https://docs.mapbox.com/mapbox-gl-js/example/animate-point-along-line/)
- [ルート上を移動するシンボル(Animate a point along a route)](https://docs.mapbox.com/mapbox-gl-js/example/animate-point-along-route/)
- [エフェクト付きのシンボル(Add an animated icon to the map)](https://docs.mapbox.com/mapbox-gl-js/example/add-image-animated/)
- [シンボルをクリックしたらポップアップを表示させる(Display a popup on click)](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/)
- [ドラッグ可能なシンボル(Create a draggable point)](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/)
- [シンボルの画像を動的に生成(Add a generated icon to the map)](https://docs.mapbox.com/mapbox-gl-js/example/add-image-generated/)

一見すると、Marker と同じことが行えるように見えます。
「同じことが行えるかどうか？」ならば Yes ですが、その実装には多くのコード量を伴うものもあります。

例えば、「シンボルをクリックしたらポップアップを表示させる」を行いたい場合、Marker では ``.setPopup()`` の1行で済みますが、[Symbol Layer の場合](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) は、「Symbol がクリックされたというイベントハンドラで、Popup を表示する」というコードをわざわざ記述しなくてはなりません。「クリック可能を示すマウスカーソルの変更」も同じくです。

[ドラッグ可能なシンボル(Create a draggable point)](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) も、そのコードを見れば相当に面倒なことを行っていることがわかるでしょう。

以上から、Symbol Layer は、Marker よりは自由度が少なく、より「地図データ」に近いものと言えそうです。

# Marker と Symbol Layer の使い分け

## 選択状態を示すなら Marker

最も一般的なユースケースだと思います。
「地図上のスポットをクリックしたら吹き出しが表示される」という事を簡単に実現したいならば、Marker がよいです。

## データ量が多い場合は Symbol Layer

地図データに近い存在である「Symbol Layer」は、Marker よりも大量のレンダリングを高速に行うことができます。

これは Marker と Symbol Layer のレンダリングのされ方の違いに起因します。

Marker は、HTML要素がそこに配置されます。つまり Marker の追加＝DOM Tree への任意のHTMLタグの追加、となります。

![](https://storage.googleapis.com/zenn-user-upload/xodzdc7h4jd5woe3p7xlkbcj6lfg)

一方で Symbol Layer では、HTML要素は生成されません。Symbol は canvas に直接描画されています。

![](https://storage.googleapis.com/zenn-user-upload/lfiv6suhmeqc8kkhpr6tlydkl5b0)

DOM Tree を変更するよりも Canvas に WebGL で描画する方が一般的には高速になりますから、Symbol Layer の方が大量のデータを高速に描画できるということになります。

次の DEMO は、大量の Marker または Symbol を追加して描画する例です。
2000個くらいになると、Marker の方がモタつきを感じるようになりますが Symbol はあまり変化を感じません。
10000個以上では、Marker はほぼフリーズ状態ですが、Symbol の方はまだ耐えられる印象です。

- https://jsfiddle.net/amay077/sxf8uq56/

## 頻繁なアニメーションが伴う場合は Symbol Layer

複数の移動体をリアルタイムトラッキングするなど、画面の書き換えなアニメーションが頻繁に発生するユースケースでも、描画コストの低い Symbol Layer が適しています。


## 複雑かつ動的なピンを表示したいなら Marker

前項で示した通り、Marker は HTML要素 であり、Symbol Layer は canvas への直接描画です。
Symbol Layer へ描画するピンの形状や色は、事前に ``map.addImage()`` で登録しておく必要があります。また、登録できるのは基本的には画像のみです。

Marker は HTML要素ですから表現の自由度は高いです。例えば地図上に直接グラフのようなものを表示したい場合は、Marker の方が適しているでしょう。ただし、前述のように描画コストには注意する必要があります。

次の DEMO は、Marker の見た目に HTML の Table を使用した例です。

- https://jsfiddle.net/amay077/yLm0rb3p/

## データを種類毎に管理したいなら Symbol Layer

「コンビニ群」「レストラン群」「ガソリンスタンド群」など、ピンデータを何らかのカテゴリ毎に管理したい場合は、Symbol Layer が向いていると思います。
それぞれを Layer/Source とすれば、Layer 単位での表示・非表示切り替え、描画順序(手前/奥)の切り替えが Style により容易にできます。
ここまで来るとデータ量は多くなりそうですから、必然的に Symbol Layer にせざるを得ないとも言えます。

# One more thing...

さらに大量の地点情報を表示したい場合、「タイル画像を生成してしまう」という選択肢もあります。この場合、静的なデータを生成することになるので頻繁に変化するコンテンツには向きませんが、一定間隔でデータを更新するバッチ処理などで済む場合は有用と思われます。

MapBox では、Mapbox Tiling Service(MTS)」というサービスを提供しており（現在は public beta）、タイル画像、しかもベクトルタイルデータを作成するツールキットが提供されるようです。筆者もまだ使ったことがないので、使う機会があったら記事を書きたいと思います。

- [Mapbox Tiling Service | Mapbox](https://www.mapbox.com/mts)

# まとめ

「データとしての地点情報」ならば、Symbol Layer にしておくのが良いかと思います。
その中で、特定の機能（選択状態や強調状態）を示すものだけを Maker とするのが良いと思います。
Google Maps SDK には Symbol Layer(というか Layer) という概念がないので、知らないと大量の Marker を登録してしまう事になりそうですが、それは避けた方が良いと思います。