Thursday, October 19, 2017

Leaflet-Mapbox-GL HOWTO: Filtering features on the map

My series on Mapbox GL API combined with Leaflet using leaflet-mapbox-gl.js continues.

Previously we created a function selectRegionByName() which would switch between a choropleth view and a plain-outline view, and zoom the map to a given region. Let's expand on that:

  • When we zoom in to a region, we toggle between choropleth and simple outline, and also zoom to the given area.
  • Now we also want to show the waterways and the gauges within that region, but not in other regions so as to minimize the visual noise.

Filtering Is Easy

Quite simply, this is done with setFilter() which you've already seen. In our case, the gauge points and waterway lines had already been tagged with a region attribute so filtering is quite easy. You should be able to patch these into selectRegionByName() with no difficulty.
// region selected// filter gauges and waterways layers, to match this regionMBOVERLAY._glMap.setFilter('gauges', ["==", 'region', name]);MBOVERLAY._glMap.setFilter('waterways', ["==", 'region', name]);

// null selected// filter gauges and waterways layers, to match no region at allMBOVERLAY._glMap.setFilter('gauges', ["==", 'region', 'Nonexistent']);MBOVERLAY._glMap.setFilter('waterways', ["==", 'region', 'Nonexistent']);

There's really not a lot to say about it. When an area is selected, set the filters to match only features in that region. When no area is selected, set an impossible filter.

New Filters Replace Old Filters

The real caveat here is that these filters replace your own filtering in the Style. If the gauge locations were already filtered in your Style, to show only those with "Status = Operational" then guess what? You just blew away that filter in favor of this new one: it's showing all gauges in the region regardless of their Status field.

This can mean some duplication of effort, adding the filter in your Style via Studio, then remembering to replicate that filtering here in the client side code in addition to the new region filter. An alternative, would be to use getFilter() to fetch the current set of filters, then add/remove the ones relevant to the region.

getFilter() returns the list of the filters currently applied to the layer. MBOVERLAY._glMap.getFilter("gauges") would return something like this, indicating a filter that matches ALL of the following filters, a filter clause by region and a filter clause by status.
[ "all", ['==', 'region', 'Northeastern Watershed'], ['==', 'Status', 'Operational'] ]
What we would want here, is to remove the region filter and leave all the rest, including that all at the start and any other clauses that aren't for region. And here we are:
// add a region filter// to whatever other filters were in placevar filters = MBOVERLAY._glMap.getFilter("gauges");filters.push([ '==', 'region', name ]);MBOVERLAY._glMap.setFilter('gauges', newfilters);

// clear the region filter// but re-apply any other filters previously in placevar filters = MBOVERLAY._glMap.getFilter("gauges");filters = filters.filter( (f) => { return f[1] != 'region' });MBOVERLAY._glMap.setFilter('gauges', filters);
This technique is a bit more work to set up, compared to the first example which is copy-paste in 20 seconds. But if you are already using filtering on your layers, this technique would preserve the filters already defined in the Style while also allowing you to define them client-side.

Of course, it's all dependent on your use case. If your filtering needs are fairly complex, or you're creating a whole filtering UI anyway, maybe you'd do better to define all your filters client side and keep them all in one place. 

No comments:

Post a Comment