💬

Symbol LayerのアイコンクリックでPopupを表示する

2023/06/12に公開

はじめに

この記事では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が実行され、作成したエレメントの削除(contentcontainer)およびMapオブジェクトの配列からの削除が行われます。

デフォルトではcloseOnClicktrueなので、Popup外がクリックされるとPopupが消えます。これは地図のクリックイベント(正確にはpreclickイベント)に_onCloseの実行を割り当てることで実現しています。つまり、デフォルトではPopupは常に最大1個しか表示されません。

以上より、一つのPopupを表示すると現在表示されるPopupは非表示となります。その際、HTMLエレメントが削除され、Mapオブジェクトからも開放されるためパフォーマンスに影響を及ぼすことはありません。ただし、closeOnClick: falseとした場合やコードからPopupを複数作成した場合には、同時に複数のPopupを開いた状態にできるため大量に表示するとパフォーマンス上の懸念があります。

レイヤーIDを指定したイベント

MapクラスのonメソッドにはレイヤーIDを第二引数に指定するバージョンがあります。clickイベントにおいて第二引数を指定すると、クリックしたポイントにそのレイヤーのフィーチャーが存在するときのみコールバックが呼ばれます。さらにコールバック関数の引数のfeaturesqueryRenderedFeaturesの結果が格納された状態となります。詳細はこちらをご参照ください。

そのためイベントの処理は以下のように、より簡潔に記述できます。

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:
https://docs.mapbox.com/android/maps/examples/view-annotation-showcase/

iOS:
https://docs.mapbox.com/ios/maps/examples/view-annotation-marker/

GitHubで編集を提案
マップボックス・ジャパン合同会社

Discussion