Isochrone maps are wonderful ways to communicate accessibility area. Route360° creates wonderful isochrones. These isochrones are polygons, growing larger in size with the time of travel. As each polygon represents a reachable area, they are, by nature, overlapping. In leaflet (and google maps) the polygons can be displayed as SVG, which offers some benefits for displaying these overlapping polygons.

In MapbogGL, polygons are currently based on GeoJSON, and the rendering is done by webGL. Although webGL has some ways to deal with polygon overlap priority, MapboxGL does not (yet) expose these functionalities. So what do we do?

With SVG, the draw order preserves the overlapping hierarchy, while putting the polygons into a <g></g> group element allows the opacity to be controlled for all polygons simultaneously. On top of that, SVG edge-weight is used to make the polygons appear to scale, but without redrawing - nice!

In MapboxGL, however, These polygons display as overlapping, which isn’t great! There are some options though…

If we add extrusion to the polygons, each polygon shares the same height origin, which makes inner polygons extrude through the outer polygons.

 // height stops function
function getHeightStops(travelTimes, heightFactor) {
    return [
        [travelTimes[0], travelTimes.length * (10 * heightFactor)],
        [travelTimes[travelTimes.length - 1], travelTimes.length * heightFactor]
    ]
}

// color stop function
function getColorStops(times) {
    var colors = r360.config.defaultTravelTimeControlOptions.travelTimes.map(function(time, idx) {
        return [times[idx], time.color];
    });
    return colors;
}

// add
map.addLayer({
    'id': 'polygons',
    'type': 'fill-extrusion',
    'source': {
        'type': 'geojson',
        'data': geojsonPolygons
    },
    'layout': {},
    'paint': {
        'fill-extrusion-base': 0,
        'fill-extrusion-height': {
            'property': 'time',
            'stops': getHeightStops(travelTimes, 2)
        },
        'fill-extrusion-color': {
            'property': 'time',
            'stops': getColorStops(travelTimes)
        },
        'fill-extrusion-opacity': .5
    }
});

Which gives a nice looking visualization, especially with high pitch.

That approach can look a bit funny without map pitch, and with extrusion, you can’t really put it under other layers - the roads, for example. In the second version, I use TurfJS to cut each polygon from its larger neighbor.

// color stop function
function getColorStops(times) {
    var colors = r360.config.defaultTravelTimeControlOptions.travelTimes.map(function(time, idx) {
        return [times[idx], time.color];
    });
    return colors;
}

var polySort = geojsonPolygons.features.sort(function(a, b) {
    return a.properties.time < b.properties.time;
})

var clippedPolys = [];

polySort.forEach(function(polygon, idx) {
    var isLast = idx == (polySort.length - 1);
    var previous =polySort[idx-1];
    if (previous){
        var clip = turf.difference(previous, polygon);
        clippedPolys.push(clip);
    } if (isLast) {
        clippedPolys.push(polygon);
    }
});

// put 'em back together
var donuts = turf.featureCollection(clippedPolys);

map.addLayer({
    'id': 'polygons',
    'type': 'fill',
    'source': {
        'type': 'geojson',
        'data': donuts
    },
    'layout': {},
    'paint': {
        'fill-color': {
            'property': 'time',
            'stops': getColorStops(travelTimes)
        },
        'fill-opacity': .5
    }
}, 'highway_path');

Which gives a really nice look when flat, and under the roads.

Updated: