Monday, October 16, 2017

Leaflet-Mapbox-GL HOWTO: Toggle between choropleth and boundaries

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

One of the first interesting tasks beyond just seeing the vector tiles on screen, would be to select one of those hydrology regions, then focus the map on it. When no area is selected, a statewide choropleth should indicate some statistical information about each region.
  • One of the datasets (I mean Tilesets) loaded into Mapbox is of the statewide hydrology regions. They each have a statistical field indicating their percentage completeness, and we will want a choropleth representation of it with some known breakpoints.
  • When an area is selected, we want to hide the choropleth and zoom in to that area, and switch over to a second rendition of those polygons: simple thick lines. Thus the choropleth color won't be distracting now that we're looking at a detailed view.
  • An area could be de-selected entirely, at which time we should switch back to choropleth, zoom back to statewide extent, and hide the lines.
  • The bounding box extents for each area, are present here in the client-side code: region name, west, south, north, east. So no special tricks are required there.

Adding Region Polygons to the Style


  • First step of course, was to upload the Tileset into Mapbox. Done.
  • Second, style up the two versions: a plain thick black outline when zoomed in, and a choropleth when zoomed out.
  • Solid black is pretty simple. Just click the layer name, fill in a color. Done.
  • Choropleth is more tedious but not too tough: Select the layer, and for the color hit the expander and select Enable Property Function and select a field. In my case I want to filter by value and I have category breaks already defined: I select # as the data type and Categorical as the classification. I then used "Add Stop" and entered the max values for each break, one by one.
And there we have it, a choropleth based on the completion percentage and a plain black outline. Both are visible in Mapbox Studio this whole time, but on the client side we're going to change that.


Toggling by Region Name


I wrote a function which accepts a region name, to zoom to it and highlight it and all. It also accepts null in order to zoom to the statewide view, selecting no area at all and adjusting the highlights accordingly.

We will expand on this in the coming days. Here's our starting content:
function selectRegionByName (name) {
    if (name) {
        // zoom the map to this region's already-known info
        const rawdata = REGION_INFO[name];
        MAP.fitBounds([[ rawdata.BBOX_S, rawdata.BBOX_W ], [ rawdata.BBOX_N, rawdata.BBOX_E ]]); 

        // suppress the choropleth by setting a filter that matches nothing
        // enable the black borders by not filtering
        MBOVERLAY._glMap.setFilter('waterregions_choro', ['==', 'name', '']);
        MBOVERLAY._glMap.setFilter('waterregions_black', null]);
    }
    else {
        // zoom the map to the fixed bounds of the whole state
        MAP.fitBounds(WHOLESTATE_BOUNDS);

        // allow the choropleth to show, by not filtering it
        // hide the black outlines by impossible filtering
        MBOVERLAY._glMap.setFilter('waterregions_choro', null);
        MBOVERLAY._glMap.setFilter('waterregions_black', [ '==', 'name', '']);
    }
}
The trick to toggling the layers is easy: to hide a layer, apply to it a filter which matches nothing; to show a layer, clear the filters.

It's simple now, but we will expand on this in the next few postings, so that selectRegionByName() does some more interesting behaviors.

Other Approaches to Toggling


To make the layer invisible, we could have tried a few other mechanisms. Which one works best for you, will vary by your use case. Most often, it's a case of "how would this strategy conflict with the existing map style, and cause unintended side effects?"

Filtering


For us, this layer has no filters at all. We either want to show them all or show none of them. As such, doing it by filter had few questions, and no likelihood of conflicting with our styles or layer stacking.

If you do filter your dataset to form a layer, the setFilter() route may not be for you. That use of setFilter() as shown above really does change the layer, so removes all of your filters in favor of your new filters!

addLayer() and removeLayer()


Use MAP._glMap.getLayer() to stow a reference to the choropleth layer and the black-outline layer, then call MAP._glMap.addLayer() and MAP._glMap.removeLayer() as needed. It works, though is a bit more clunky in my opinion.

Mapbox GL JS API does not support a z-index on layers, so the layer order when they are added back into the map, may not be the same as it was previously. They support a before parameter to help with the stacking order, which may work for you as long as you're not renaming layers nor changing their sequence via Mapbox Studio.

Layer Opacity


Another approach would be to alter the layer's color style via setPaintProperty() so as to give it 0 opacity.
MBOVERLAY.setPaintProperty('waterregions_choro', 'fill-opacity', 0);
MBOVERLAY.setPaintProperty('waterregions_choro', 'line-opacity', 0);
MBOVERLAY.setPaintProperty('waterregions_black', 'line-opacity', 1);
This can work well, as long as you remember to address both the stroke and the fill for all appropriate layers, and as long as you know what opacity would be "the default" when making it visible again.

No comments:

Post a Comment