Skip to content

Instantly share code, notes, and snippets.

@pospi
Last active September 23, 2016 16:13
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save pospi/85c31530c4a93cf091ce to your computer and use it in GitHub Desktop.
Save pospi/85c31530c4a93cf091ce to your computer and use it in GitHub Desktop.
Handle multiple geoJSON layers in a Leaflet.js angular directive
/**
* Service to manage geoJSON layering with Leaflet.js' angular directive, which only allows 1 set of geoJSON data.
*
* Assuming you have a leaflet directive with its 'geojson' attribute set to `geojson`, usage is as follows:
* var layers = new GeoJSONLayers();
*
* layers.addLayer('myLayer', geoJSON, function(feature) { return { fillColor: '#00F' }; });
* $scope.geojson = layers.get();
*
* layers.removeLayer('myLayer');
* $scope.geojson = layers.get();
*/
angular.module('MyApp').factory('GeoJSONLayers', [
function() {
var handler = function()
{
this.clear();
};
handler.prototype.clear = function()
{
this.json = {
type : "FeatureCollection",
features : []
};
this.layerStyles = {};
};
handler.prototype.addLayer = function(layerID, geoJSON, styleCallback)
{
this.layerStyles[layerID] = styleCallback;
// tag features with their assigned layer
geoJSON.features.forEach(function(feature, i) {
feature.properties.__LAYERID__ = layerID;
});
// merge into current objects
Array.prototype.push.apply(this.json.features, geoJSON.features);
};
handler.prototype.removeLayer = function(layerID)
{
var feats = this.json.features,
i = 0;
delete this.layerStyles[layerID];
// remove relevant geoJSON objects as well
for (; i < feats.length; ++i) {
feature = feats[i];
if (feature.properties.__LAYERID__ == layerID) {
feats.splice(i, 1);
--i;
}
}
};
handler.prototype.__handleStyle = function(feature)
{
if (feature.properties['__LAYERID__'] === undefined) {
return {};
}
return this.layerStyles[feature.properties.__LAYERID__](feature);
};
// return geoJSON data for assignment to scope
handler.prototype.get = function()
{
var self = this;
return {
data: this.json,
style: function(feature) {
return self.__handleStyle.call(self, feature);
},
resetStyleOnMouseout: true
};
};
return handler;
}
]);
@absolutelynotjames
Copy link

@pospi this is really helpful - thanks for sharing. How would I go about adding in support for pointToLayer, and onEachFeature options? I shamefully hacked them in to the return statement and they seemed to work, but I'm not sure how I would go about adding them to the prototype template. Thanks again!

@absolutelynotjames
Copy link

Edit: @pospi I've made a bit of progress, I got pointToLayer working:

handler.prototype.addLayer = function(layerID, geoJSON, styleCallback, pointToLayerCallback, onEachFeatureCallback)

this.pointToLayer = pointToLayerCallback;

        handler.prototype.__handlePointToLayer = function(feature, pointToLayer)
        {
            return this.pointToLayer(feature).options;
        }
                pointToLayer: function(feature, latlng) {
                    return L.marker(latlng, self.__handlePointToLayer.call(self, feature, latlng));
                },

I can't get this to work for onEachFeature though. Any advice? Thank you!

@pospi
Copy link
Author

pospi commented Mar 29, 2016

I've had a brief flick through that old codebase and found a comment near my assignment of onEachFeature that says "we can't use delegated events on the whole layer, because MultiPolygons are disassociated from their features that way".. I think that might be the reason yours is having issues, perhaps Leaflet does weird stuff to the function context internally?

In any case I found my layer loading function inside the map component, it's just a standard call to L.geoJSON() with an onEachFeature option. The key is probably that my onEachFeature is a generator function that returns a new function for every layer it's initialised on:

function configureFeature(layerId)
{
    return (function() {
        var theLayer = layerId;
        return function(feature, layer) {
            layer.on('mouseover', someHandler);
        };
    })();
}

You can then create different logic for your layers by switching on theLayer. Hope that helps a little bit!

@absolutelynotjames
Copy link

@pospi Thanks for the response - that's actually exactly the problem I ran into.

I'm using the angular leaflet wrapper, so I just did:


  angular.extend($scope, {
    geojson: {
      data: (myGeoJsonLayer.get()).data,
      pointToLayer: (myGeoJsonLayer.get()).pointToLayer,
      onEachFeature: function (feature, layer) {
        layer.bindPopup('Hey, listen!');
      }
    },

Thanks for the solution, I can't believe it was so simple.

@absolutelynotjames
Copy link

By the way - if anyone is interested here is my fork that supports the pointToLayer option. Thanks 👍

https://gist.github.com/absolutelynotjames/c7688054e1325128438e2cef1686eea0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment