Monday, October 30, 2017

Mapbox Vector Tiles + Leaflet: Bugs and Workarounds

A few weeks ago, I posted about my first experiences with Mapbox's vector tile service and the Leaflet adapter for it. A lot of things went well and the end result was quite nice, and I covered a lot of how-to items for what worked well.

But a couple of items were buggy, or needed workarounds.

Published Changes Not Saving


I mentioned this previously, but continue to replicate it. I make changes to my Style in Mapbox Studio, hit Publish, then wait patiently for the changes to show up in the browser. Sometimes it works almost instantly, sometimes I have to wait 10 minutes, and sometimes it just doesn't happen.

I hit the /styles/v1/ endpoint in my debugger, and see that they really are serving up the old colors, are not yet including my new layer, are still showing the old name for the layer, and so on.

Workaround: go into Mapbox Studio again, make some change and then undo it, and hit Publish again. Sometimes a third time is required.

Clicking and mouseover effects


The queryRenderedFeatures() method accepts a Mapbox GL Point as the query coordinates, not a L.LatLng since it's not Leaflet. The programming to convert a L.Map click event into a Mapbox GL Point and then perform a query, is as follows:
MAP.on('click', function (e) {
    const canvas   = MBOVERLAY._glMap.getCanvasContainer();
    const rect     = canvas.getBoundingClientRect();
    const glpoint  = new mapboxgl.Point(e.originalEvent.clientX - rect.left - canvas.clientLeft, e.originalEvent.clientY - rect.top - canvas.clientTop);
    const features = MBOVERLAY._glMap.queryRenderedFeatures(glpoint, { layers: INTERACTION_LAYERS });
    // now go do something with the resulting "features" list
});

Maps becoming desynchronized on a zoom change


When the L.Map changes zoom level, e.g. any time we call a fitBounds() or setView() the Mapbox GL layer would fall out of sync. The vector layer would repaint, but often not at the same center, so would be offset from the rest of the map. This would happen pretty reliably too, with only moments of testing required.

The workaround here was a bit of a hack: any time the map becomes idle after changing, have it call setCenter() on its own center after a brief timeout. This seems to trigger a repaint of the Mapbox GL layer, bringing it into sync with the rest of the map.
MAP.on('moveend', function () {
    if (! MAP.overlay._glMap) return; // map's not ready yet e.g. startup
    const center = MAP.getCenter();
    setTimeout(function () {
        MAP.overlay._glMap.setCenter(center);
    }, 250);
});
Again, an ugly hack but a lot less ugly than what it solves!

Race conditions on startup


Outside of the map is a list of hydrology regions and clicking one will trigger some behaviors on the map. Some of these behaviors include changing some filters on the Mapbox GL layer. Problem is, the Mapbox GL layer can take several seconds to initialize. If someone clicks during this time, all sorts of funky hell breaks loose in the console because MBOVERLAY._glMap is not yet defined, the Style has not yet loaded, and so on.

The workaround is not surprising: GL Map's load event, which fires after the map has first loaded and completed its first painting of the layer. Now you know that MBOVERLAY._glMap is defined and ready!
MAP.overlay = L.mapboxGL({
    accessToken: OVERLAY_ACCESSTOKEN,
    style: OVERLAY_MAPBOXURL,
    pane: 'overlayPane',
}).addTo(MAP); 

MBOVERLAY._glMap.on('load', function () {
    // addSource() and addLayer()
    // and additional UI event handlers for folks who won't wait before clicking
});

What Went Well?


Most things worked well! Those were the focus of the previous articles. :)

Hooray Mapbox, for a really excellent product which allows for filtering and re-styling in ways that would traditionally have required a WMS server.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.