Search code examples
javascriptdjango-rest-frameworkleafletgeodjango

How to make Leaflet drop or stop processing markers outside of its bbox?


Recently set up Leaflet map with GeoDjango / rest_framework_gis and have limited results with Pagination, but the result still seems to have Leaflet cumulatively processing every marker it receives, not just what's in view, which lags and eventually crashes browsers. In the comment I was advised to address this with JS. This makes sense as it's front end, but how can we do this?

The JS from this tutorial:

async function render_markers() {
const markers = await load_markers();
L.geoJSON(markers)
    .bindPopup((layer) => layer.feature.properties.name)
    .addTo(map);
}

Can we write some kind of function that'll do something along the lines of if there are over n entries drop the furthest from bbox view?

The specific line seems to be .addTo(map);. Is there some kind of .removeFrom() or similar in JS?


Solution

  • on every move you will add same markers again and again as long as they are in the bounds. At some point there will be to much markers on the map and the browser will crash.

    You need to do following:

    1. Add a check if the zoom is high enough. Don't load the entries below 12. Else the request will maybe to big. But this is optional
    2. load only new markers if they are not already in the loaded bounds.
    3. Stop / abort all running requests
    4. Create an Request and extend the bounds with 10% to request not on every move
    5. After finishing the request: copy all exisiting layers to an array and remove the layers from the array which are in the request result. At the end in the array will be all layers they need to be removed (because we filtered the needed one out). The layers from the request which are not in the existing layer array, are new and needed to be added to the map.

    This code is not tested! I use a different version in my project and this one works. also I think you need to replace the ajax / jquery request with vanilla requests like fetch

    var runningRequests = [];
    var allLayers = [];
    
    function initLoad() {
        // add event listeners to load the new layers
        map.on('moveend',mapMove);
        map.on('zoomend',mapMove);
    
        // load initial the layers
        if (map.getZoom() > 12) {
            loadLayers(map);
        }
    }
    
    function mapMove() {
        // only load new layers if higher then zoom level 12
        if (map.getZoom() > 12) {
            // only load new layers if the map bounds are not in the loaded bounds anymore
            if (areaBounds.contains(map.getBounds()) === false) {
                loadLayers()
            }
        }
    }
    
    function loadLayers() {
        // current bounds extending by 15%
        areaBounds = map.getBounds().pad(0.15);
    
        // abort all running requests
        if(runningRequests.length > 0){
            for (var i in runningRequests) {
                runningRequests[i].abort(); // jquery request abort -> not working for vanilla requests
                delete runningRequests[i];
            }
        }
    
    
        var req = $.ajax(options).done(function(json){  // jquery load request -> not working for vanilla requests
            var layersToRemove = allLayers; // maybe this doesn't break the reference then you need to destroy them
            
            len = json.length;
            
            var layersToAdd = [];
    
            json.forEach((obj)=> {
                var coords = obj.coordinates;
                // filter all layers out, which has the same coordinates as in the json
                var filteredLayers = layersToRemove.filter(function (layer) { return !layer.getLatLng().equals([coords[1], coords[0]]) });
                
                if(filteredLayers.length === layersToRemove.length){
                    // no layer was removed, so we know that it is a new layer
                    layersToAdd.push(obj);
                }
                
                layersToRemove = filteredLayers;
            });
            
            // remove all layers that are not in the result anymore
            layersToRemove.forEach((layer)=>{;
                map.removeLayer(layer);
            }
            
            addLayer(layersToAdd);
    
        });
    
        runningRequests.push(req);
    }
    
    function addLayer(geoJsonLayers){
        
        // the data are in geojson format, so we add them to a featureCollection, so we can load it with L.geoJSON(featureCollection)
        var featureCollection = {
          "type": "FeatureCollection",
          "features": []
        };
        featureCollection.features = geoJsonLayers;
        
        // add layers to the map
        var geoJson = L.geoJSON(featureCollection)
            .bindPopup((layer) => layer.feature.properties.name)
            .addTo(map);
        
        // add the new layers to the allLayers array
        geoJson.getLayers().forEach((layer)=>{
            allLayers.push(layer);
        });
    
    }