3

I am working on a Rails/PostGIS app and doing a typical ajax call to retrieve GeoJSON within the map viewport bounds. Whenever the map is shifted or zoomed all of the markers are removed and then added again according to the new bounds. The problem I have with this is that it creates a flicker effect, as well as closes popups(especially annoying when a marker is close to the bounds and readjusts them when the popup opens, closing it immediately). I am wondering if there is a standard approach to filtering out duplicate features. This way nonidentical markers can simply be added to the GeoJSON layer instead of recreating the entire layer each time, and existing markers that are now outside of the bounds can be removed. I am having a hard time looking for examples as I am not familiar enough with Javascript to write a solution. I had originally been using Google Maps API but switched to Leaflet hoping it would be easier to figure out. http://labs.easyblog.it/maps/leaflet-layerjson/ is an example of the ideal behavior, but I had a hard time trying to implement this with GeoJSON.

The code:

var geojsonLayer;

var addGeoJSONLayer = function(geoJSON) {

if(geojsonLayer) {
  geojsonLayer.clearLayers();
}

geojsonLayer = new L.GeoJSON(geoJSON, {
    onEachFeature: function(feature, layer){
        layer.bindPopup("<a data-no-turbolink='true' href="+ feature.properties.id + ">" + feature.properties.name + "</a>");
        }

}).addTo(map);

geojsonLayer.addData(geoJSON);

};

$.getJSON('/summits.geojson/?bbox=' + map.getBounds().toBBoxString() + '', addGeoJSONLayer);

map.on('moveend', function(event){
    if(map.getZoom() >= 10) {
        $.getJSON('/summits.geojson/?bbox=' + map.getBounds().toBBoxString() + '', addGeoJSONLayer);
    }
});

I have also used the leaflet-ajax plugin hoping it would solve this, but it produces the same results as above. I know it has a "refilter" method but I am not sure how to use it, or if it can even be used to solve the problem. Any help whatsoever would be greatly appreciated!

3

The way I dealt with this a long time ago (back before ArcGIS Feature Server) was to create a simple unique integer hash for each feature from a specific source (can be as simple as a unique identifier) and then store information in an array indexed on that unique integer hash. If the incoming feature is not stored in the array already at its position, then you add it to the map and store it at the position. If it is already stored, then it is already on the map and you do nothing with it. When you remove a feature from the map, you remove the object occupying that feature's position in the array.

This reduces a potential search to a much less costly array index lookup (especially if your hash function is as simple as accessing one property).

Sample code here (mapdijits.GraphicsManagerOpt.add):
http://maps.stlouisco.com/police/beta/mapdijits/GraphicsManagerOpt.js

Obviously this code is for ArcGIS javascript API, but the concept should be adaptable to Leaflet.

Edit: Just thought I would add that databinding in D3 might make this problem much easier. If you are inserting the GeoJSON into an array based on an integer hash, you can then bind just data bind that array as path information and your map updates. Here is a crude example that takes a long time to load because I am bringing in a big hunk of GeoJSON to run it:
http://maptest.stlouisco.com/d3/map.html

  • The sample code is way over my head for now, but D3 looks really cool and I am excited to check it out. There is a lot of example info online about it, I am surprised I have not come across it by now. Thanks for the heads up! – Albert Herrera Jul 3 '13 at 4:28
3

I guess the answer is: in leaflet there is no standard approach yet for what you are trying to do. Not sure, but I think that what you want to achieve is referenced in this open ticket.

Having said this, have a look at this jsfiddle. I have got round the problem of changing data within a single geoJSON layer through adding a layer for every data point. Not sure how this would scale well with big amounts of data though. I whipped it up in a very short time using underscore.js and it is a bit inefficient and possibly with bugs, but it seems to do the work. Apologies if the code looks a bit complex (it could be simplified) and messy. Hope it helps.

Edit: implementing the above using D3 (as suggested by @blord-castillo) would be a nice solution. I wrote this blog post recently, it might help for some ideas.

  • Thank you for the direction. I can sort of understand what is happening here and I will try to plug it in and play around with it. I have never used underscore though, or even heard of D3 for that matter. Have a lot of brushing up to do! Someone else recommended I try to adapt the Leaflet Vector Layers plugin to work with ajax/geoJSON. – Albert Herrera Jul 3 '13 at 4:21
0

I came back to this a year later and was able to solve it easily. I stored the current markers ID property in an array. Whenever the GeoJSON is requested the L.GeoJSON filter is used to check if the ID property of each incoming marker is in the array, returning true or false depending. When new GeoJSON is added, the markers in a GeoJSON group layer are checked to see if they are out of the map bounds and removed if true. The marker ID is also removed from the ID array. So on each 'moveend' it checks for out of bounds markers, updates the ID array, checks IDs in the array and only adds IDs that don't exist in it already before adding the layers.

  • Would it be possible to post a full example of this logic? I have been trying to do the same, without luck. Thanks! – Jeremy Jan 12 '15 at 2:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.