I recently was optimising the performance of a Leaflet-based map that rendered TopoJSON layers via Omnivore. The layers were a visualisation using the ABS’s Postal Areas, and while there was only a single TopoJSON file, this resulted in a number of feature layers being displayed, with each bound to their own data. The data for each feature layer could change depending on what filters were set (these filters were displayed in a left panel).
In the original implementation, whenever a change was made to a filter, the entire TopoJSON layer was removed, and then re-joined to the data set and rendered:
var dataLayer; function renderDataLayer() { var prevLayer = dataLayer; dataLayer = L.geoJson(null, { filter: filter, style: style, onEachFeature: onEachFeature }); omnivore .topojson('postal_areas.topojson', null, dataLayer) .on('ready', function() { // Remove previous layer when current layer is ready to avoid flickering if (prevLayer) { prevLayer.remove(); } }) .addTo(map); }
Using Chrome’s “Record JavaScript CPU Profile”, it clearly showed the code invoking Omnivore was the problem:
Changing the render function to (1) iterate over the existing layers and (2) change the style of the layer, made the map feel a lot more responsive.
var dataLayer = omnivore.topojson('postal_areas.topojson', null, dataLayer); function renderDataLayer() { dataLayer.eachLayer(function(featureLayer) { featureLayer.setStyle(style(featureLayer.feature)); }); }
One of the disadvantages was that we can’t use the filter function on the geoJson
object anymore, since in order for a style change to occur, the feature layer must be present (although it can be hidden). This potentially can lead to slowness when dragging or zooming a map.
Another related disadvantage is hiding layers both visually and from mouse events. Setting the opacity
and fillOpacity
of the layer to 0 will take care of the first, while this CSS will prevent the mouse cursor from changing when hovering over a hidden layer:
/* Don't show pointer on hidden layers */ .leaflet-pane > svg path.leaflet-interactive[stroke-opacity="0"][fill-opacity="0"] { pointer-events: none; }
Leave a Reply