Search code examples
javascriptajaxrestleafletgeojson

Leaflet js - memory leak accessing GeoJSON REST Api


With Leaflet, I need to refresh the features displayed after map movements (pan, zoom).

I access a GeoJSON REST Api thus:

function getFeatures( map, layer, table, user ) {
  var bounds = map.getBounds();
  var style = layer.module.spatial.style || undefined;

  var params = {
    geojson: true,
    bbox: bounds.toBBoxString(),
    srs: 'EPSG:4326',
    limit: 200 // limit returned features
  }; 

  fetch ('api/organisations/' + user.organisation + '/objects/' + table + '?' 
    + new URLSearchParams (params))
  .then( response => response.json())
  .then( data => {
    layer.addData (data)
  })
  .catch( err => {
    console.log("Fetch error on table " + table + ': ' + err);
  });
}

export default function(options, map, L, user) {
  var layer = L.geoJson(null, { /*... styling, popup ...*/ });

  map.on('moveend', function(evt) {
    var map = evt.target;
    getFeatures(map, layer, options._id, user );
  });
}

/*
 * ... in calling script:
 */
map.addLayer(layer);

All works fine, but memory usage (measured in Chrome devtools) rockets steadily upwards, and performance becomes poor.

I imagine that I need to be freeing something, presumably in my "moveend" handler, but have been unable to locate any examples suggesting what I should do.

I've tried map.removeLayer(layer) and map.addLayer(layer) either side of layer.addData (data), this made no difference.

What to do?


Solution

  • inspecting leaflet.js doc, layer seems to have a remove method. But in order to invoke it, you will likely need to get a reference to the geojson layer object. So perhaps something like the following?

    function getFeatures( map, layer, table, user ) {
      var bounds = map.getBounds();
      var style = layer.module.spatial.style || undefined;
    
      var params = {
        geojson: true,
        bbox: bounds.toBBoxString(),
        srs: 'EPSG:4326',
        limit: 200 // limit returned features
      }; 
    
      fetch ('api/organisations/' + user.organisation + '/objects/' + table + '?' 
        + new URLSearchParams (params))
      .then( response => response.json())
      .then( data => {
        layer.addData (data)
      })
      .catch( err => {
        console.log("Fetch error on table " + table + ': ' + err);
      });
    }
    
    let layer // not a huge fan of using var, but I believe you can use var here if you insist
    
    export default function(options, map, L, user) {
      if (!!layer) {
        layer.remove()
      }
      layer = L.geoJson(null, { /*... styling, popup ...*/ });
    
      map.on('moveend', function(evt) {
        var map = evt.target;
        getFeatures(map, layer, options._id, user );
      });
    }
    
    /*
     * ... in calling script:
     */
    map.addLayer(layer);
    

    You may need to do some debouncing, since getFeatures etc is an async call, and you could have some race condition shenanigans.