Symbol LayerのアイコンクリックでPopupを表示する
はじめに
この記事ではMarkerとPopupを連動させる方法について解説しました。一方で、Pinの表示方法はレイヤーとMarkerどちらを使うのがよい?では表示するポイントの数が多いときにはMarkerが適してないことがわかりました。そこでこの記事ではポイントの数が多いときにレイヤーを使ってPopupと連携する方法について解説します。
レイヤーとPopupの連携
サークルレイヤーやシンボルレイヤーで表現されているポイントはクリックイベントを持ちません。つまり、単体ではそのポイントがクリックできたかどうかを知ることはできません。そこで、queryRenderedFeatures
をつかってクリックされた場所を検索し、ポイントを表現しているFeatureが見つかった場合にPopupを表示するという方法で代替します。
queryRenderedFeaturesとは
Mapbox GL GS(およびモバイルSDK)にはqueryRenderedFeatures
というメソッドが実装されています。これは指定したポイントや領域(Bounding Box)に対し、地図上のFeatureを検索するメソッドです。例えば道路上のポイントに対して実行すると、道路のレイヤーに関する情報が取得できます。
使用するコード
以前作成した以下のParty Parrotを使用します。
コードの解説は以下をご参照ください。
実装
load
イベントのコールバックの一番最後の部分に以下のコードを挿入します。
map.on("load", () => {
...中略...
map.on("click", (e) => {
const result = map.queryRenderedFeatures(e.point, { layers: ["parrot"] });
if (result.length === 0) {
return;
}
const popup = new mapboxgl.Popup({ offset: 20 })
.setLngLat(e.lngLat)
.setText(result[0].properties.name)
.addTo(map);
});
});
queryRenderedFeatures
の第一引数に検索する場所、第二引数にオプションを指定します。ここではparrot
レイヤーのみを検索するようにしています。何もなければ空配列が返ってくるのでlength
でチェックしています。検出された場合には1つ目のname
プロパティをPopupとして表示します。
結果は以下のとおりです。
Popupはどのように管理されているか
ポイントの数が増えたときにPopupの数がそれに応じて増えるとパフォーマンスの懸念が生じます。そこで、どのようにPopupが管理されているか確認しておきます。
まず、addTo
の中でMapオブジェクトに追加する処理が行われます。_addPopup
が行う処理はMapオブジェクトが管理するPopup配列へのPopupオブジェクトの追加です。また、Popupが非表示になる際、_onClsoe
が実行されます。ここでremove
が実行され、作成したエレメントの削除(content、container)およびMapオブジェクトの配列からの削除が行われます。
デフォルトではcloseOnClick
がtrue
なので、Popup外がクリックされるとPopupが消えます。これは地図のクリックイベント(正確にはpreclick
イベント)に_onClose
の実行を割り当てることで実現しています。つまり、デフォルトではPopupは常に最大1個しか表示されません。
以上より、一つのPopupを表示すると現在表示されるPopupは非表示となります。その際、HTMLエレメントが削除され、Mapオブジェクトからも開放されるためパフォーマンスに影響を及ぼすことはありません。ただし、closeOnClick: false
とした場合やコードからPopupを複数作成した場合には、同時に複数のPopupを開いた状態にできるため大量に表示するとパフォーマンス上の懸念があります。
レイヤーIDを指定したイベント
Map
クラスのon
メソッドにはレイヤーIDを第二引数に指定するバージョンがあります。click
イベントにおいて第二引数を指定すると、クリックしたポイントにそのレイヤーのフィーチャーが存在するときのみコールバックが呼ばれます。さらにコールバック関数の引数のfeatures
にqueryRenderedFeatures
の結果が格納された状態となります。詳細はこちらをご参照ください。
そのためイベントの処理は以下のように、より簡潔に記述できます。
map.on("click", "parrot", (e) => {
const popup = new mapboxgl.Popup({ offset: 20 })
.setLngLat(e.lngLat)
.setText(e.features[0].properties.name)
.addTo(map);
});
結果は以下のとおりです。
やっぱりもっとParty!
ポイント数が増えても大丈夫です。
まとめ
レイヤーに対してPopupを使用できることがわかりました。これで状況に応じてMarker+Popup、レイヤー+Popupが使い分けられそうです。また、似たようなテクニックがモバイルSDKでも使用可能です。モバイルSDKにはPopupがないため、View Annotationを使用します。詳細は以下のExampleをご参照ください。
Android:
iOS:
Discussion