Esri / esri-leaflet-vector

Display ArcGIS Online vector basemaps w/ Esri Leaflet
51 stars 53 forks source link

Add `vectorTileLayer` events API to access underlying data #101

Open jwasilgeo opened 3 years ago

jwasilgeo commented 3 years ago

We can gain access to the underlying data attributes rendered by mapbox-gl-js. The purpose of this enhancement is to define the API we create around L.esri.Vector.vectorTileLayer to enable it.

For example, here is a rough prototype code snippet of how this actually could work in our MapBoxGLLayer.js.

// this._map is the "outer" LeafletJS map instance

// this._glMap is a mapbox-gl-js map instance,
// which is the workhorse under the hood of esri-leaflet-vector

// 1. wire up a LeafletJS map click event callback
// 2. and pass the clicked coords to this._glMap.queryRenderedFeatures()
this._map.on('click', (e) => {
  // https://docs.mapbox.com/mapbox-gl-js/api/map/#map#queryrenderedfeatures
  const features = this._glMap.queryRenderedFeatures([e.latlng.lng, e.latlng.lat]);
  console.log(features);
});

With that in mind, I'd like help in defining what the API of L.esri.Vector.vectorTileLayer would look like.

  1. Idea: First investigate what happens when we try out some of the available methods on Leaflet's Layer, since vectorTileLayer directly inherits from that class. https://leafletjs.com/reference-1.7.1.html#layer

    For example, what does the following do and does it provide any useful information? How would we design an API that internally deals with the mapbox-gl-js map instance and returns any clicked features?

    myVectorTileLayer.on('click', (e) => console.log(e));
  2. Idea: Do we allow developers to attach/detach their own event handlers, and we just help return the feature data provided by this._glMap.queryRenderedFeatures()? This would be similar to esri-leaflet's FeatureLayer events

    ... L.esri.FeatureLayer also fires the following MouseEvents click, dblclick, mouseover, mouseout, mousemove, and contextmenu and the following the PopupEvents popupopen and popupclose

  3. Idea: Do we provide other instance methods such as bindPopup similar to esri-leaflet's DynamicMapLayer?

The 2nd idea sounds like a straightforward and incremental enhancement that would provide flexibility to developers. The 3rd--bindPopup--seems like a nice UI convenience but is quite specific in scope and Leaflet devs can still define a popup themselves on an interaction event.

@patrickarlt @gavinr @dhatcher

jwasilgeo commented 2 years ago

This is a good starting place for what works today, as an example of what a developer can do today in their own consumer code to gain access to the mapbox-gl-js instance and call other methods, such as queryRenderedFeatures.

Ideally we'd still find a middle path where a consumer-dev would just wire up a simple click handler (or mousemove, or other documented user interactions available in LeafletJS) on their own VTL layer instance and we take care of things like: the overhead of communicating between LeafletJS and mapbox-gl-js, cleanly turning on and off their event handlers, etc.

I just pushed a new draft sample page in branch 101-click-handler to start investigating what our final API might look like.


const layer = L.esri.Vector.vectorTileLayer(... ... ...);

// EARLY PROTOTYPE PROOF-OF-CONCEPT:
// We can wire up a LeafletJS Map click hander (or mousemove, etc.),
// and then pass through the event.latlng info to the mapbox-gl-js instance, currently deeper down our VTL.
// There we have access to the `queryRenderedFeatures` method which gives us the client-side GeoJSON feature info we want.

map.on("click", (e) => {
  // https://docs.mapbox.com/mapbox-gl-js/api/map/#map#queryrenderedfeatures

  // A: example of passing in the clicked location
  // const features = layer._mapboxGL._glMap.queryRenderedFeatures([e.latlng.lng, e.latlng.lat]);

  // B: example of passing in a simple bbox extent around our clicked location
  // const features = layer._mapboxGL._glMap.queryRenderedFeatures([[e.latlng.lng - 1, e.latlng.lat - 1], [e.latlng.lng + 1, e.latlng.lat + 1]]);

  // C: example of not passing any clicked location which returns ALL features in the current map extent
  const features = layer._mapboxGL._glMap.queryRenderedFeatures();
  console.log(features);
});