Leaflet Draw not taking properties when converting FeatureGroup to GeoJson - leaflet

I'm unable to convert my Layer properties into the properties of the GEOJson object using Leaflet(0.7.7)/Leaflet.Draw(latest). My workflow is:
1 Create Map: var map = L.map('#map', options);
2 Create a FeatureGroup: features= new L.FeatureGroup();
3 Add to the Leaflet Map: map.addLayer(features);
4 On the draw:created event, I'm capturing e.layer and adding a bunch of properties:
var layer = e.layer;
layer.properties = { Title: 'Hello' };
features.addLayer(layer);
geo_features = features.toGeoJSON();
However, my geo_features always have empty property attributes in each of the features and I can't figure it out!

iH8's initial answer was almost correct.
To specify properties that will appear in a vector layer's GeoJSON export (i.e. through its .toGeoJSON() method), you have to fill its feature.type and feature.properties members:
var myVectorLayer = L.rectangle(...) // whatever
var feature = myVectorLayer.feature = myVectorLayer.feature || {};
feature.type = "Feature";
feature.properties = feature.properties || {};
feature.properties["Foo"] = "Bar";
Now myVectorLayer.toGeoJSON() returns a valid GeoJSON feature object represented by:
{
"type": "Feature",
"properties": {
"Foo": "Bar"
// More properties that may be pre-filled.
},
"geometry": // The vector geometry
}

A (kind of ugly workaround) is using a L.GeoJSON layer and add the drawn layer's GeoJSON to it by using it's addData method. Afterwards grab the last layer in the L.GeoJSON layer's _layers object. At that point the layer has a valid GeoJSON feature property you can edit:
var geojson = new L.GeoJSON().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: geojson
}
}).addTo(map);
map.on('draw:created', function (e) {
geojson.addData(e.layer.toGeoJSON());
var layers = geojson._layers,
keys = Object.keys(layers),
key = keys[keys.length - 1],
layer = layers[key];
layer.feature.properties = {
'Foo': 'Bar'
};
});

For your L.GeoJSON call include feature callback onEachFeature to options
L.GeoJSON(featureData,{onEachFeature:function(feature,layer){
//console.log(feature,layer);
// do something like
feature.setStyle( convertLayerOptionsFromFeatureProperties( feature.properties ) );
}} )

Related

Changing overlay layers when switching base layer

I have built a leaflet map with two base layers, and each of these base layers will have their own unique points of interest. The points of interest are being stored as geojson that I loop over to create multiple overlays for different categories. So when viewing the default base layer you would see layers for Show All, Cat1, Cat2 etc.
I need a way to be able to attach overlay layers to a base layer, or remove all overlay layers and then load the relevant ones when the base layer changes.
I tried using the following, which worked to switch categories, with the baselayerchange event, but the overlay layers were still displaying when I switched base layers.
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
I've been searching for a couple of days now for an answer to this without any luck, any help is greatly appreciated.
EDIT
Posting additional code for context. This is not the entirety of the code, there are some plugins that I'm not including code for and have excluded definitions for a several variables, but this should provide better insight into how things are working.
//Initialize the map
var map = new L.Map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
crs: crs1848,
attributionControl: false,
layers: [pano1848]
});
//add controls to the map
var layerControl = L.control.layers(null, null, {position: 'bottomleft'}).addTo(map);
//building category layers from geojson
var types = ['African Americans', 'Art Architecture Culture', 'Education Religion Reform', 'Everyday Life', 'Immigrants', 'Science Inventions', 'Transportation Industry Commerce'];
types.forEach(function(type){
var catType = type.replace(/\s/g,"");
var catPoints = L.geoJson(mapData, {
filter: function(feature, layer){
var cat = feature.properties['category'];
return cat.indexOf(catType) >= 0;
},
onEachFeature: function (feature, layer) {
layer.bindTooltip(feature.properties.name);
(function(layer, properties){
//Create Numeric markers
var numericMarker = L.ExtraMarkers.icon({
icon: 'fa-number',
markerColor: 'yellow',
number: feature.properties['id']
});
layer.setIcon(numericMarker);
layer.on('click', function() {
$.ajax({
url:feature.properties['url'],
dataType:'html',
success: function(result){
$('#detailContainer').html(result);
$('#overlay').fadeIn(300);
}
});
});
})(layer, feature.properties);
}
});
layerControl.addOverlay(catPoints, catType);
});
//Base Layer Change Event
map.on('baselayerchange', function(base){
var layerName;
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
if(base._url.indexOf('1848') >= 0){
map.options.crs = crs1848;
map.fitBounds([
crs1848.unproject(L.point(mapExtent1848[2], mapExtent1848[3])),
crs1848.unproject(L.point(mapExtent1848[0], mapExtent1848[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([90112, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
map.addLayer(allPoints);
layerName = '1848 Panorama';
}
else if(base._url.indexOf('2018') >= 0){
map.options.crs = crs2018;
map.fitBounds([
crs2018.unproject(L.point(mapExtent2018[2], mapExtent2018[3])),
crs2018.unproject(L.point(mapExtent2018[0], mapExtent2018[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([49152, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
layerName = '2018 Panorama'
}
miniMap.changeLayer(minimapLayers[layerName]);
//map.setView(map.getCenter(), map.getZoom());
});
You may create global variable call "overlays", and remove it like an example below.
Here is the similar example to illustrate your problem jsFiddle
var overlays = {
'Name 1': catPoints,
'Name 2': catType
};
L.control.layers(null, overlays).addTo(map);
// Whenever you want to remove all overlays:
for (var name in overlays) {
map.removeLayer(overlays[name]);
}

in-place Update Leaflet GeoJSON feature

I was hoping that GeoJSON.addData() would return the newly created subLayer of the GeoJSON object, but it does not. Why Do I need something like this?
(currently using Leaflet 1.0Beta2)
I am using Leaflet GeoJson to show live data in a GeoJSON (point, line, polygon). It is a CRUD interface (Create, Update and Delete). I receive WebSocket messages with GeoJSON data, each one with a GUID.
I the case of a CREATE I just do a GeoJSon.AddData() to the appropriate layer.
But for the UPDATE and DELETE I want a handle for the layer that was added to the GeoJSON so that I can update its location, or update the Geometry. addData is not giving me this handle. And it is really hard to get it from onEachFeature() or from pointToLayer()
Currently, I do have a way that works but ugly. I have to do is search the entire layer with GeoJSon.eachLayer(fn)
whenever an update or delete occurs. It seems a bit expensive.
{even if Leaflet is not truly engineered for this live r/t display of data, it is working, and it seems sad if you cannot use it for watching a lot of sensor data, IoT) as we are doing.
this.delete = function (layerName, feature) {
if (!(layerName in self.myLayers)) {
alert("live.display: Missing Layer: " + layerName);
return;
}
var layerInfo = Live.myLayers[layerName];
var base = layerInfo.layer;
var id = feature.properties.objectID;
this.find(layerName, id, function (layer) {
this.removeLayer(layer);
Tools.debug(16, "live.delete:", "killed object: " + id);
});
}
this.find = function (layerName, id, action) {
var base = Live.myLayers[layerName].layer;
base.eachLayer(function (feature) {
if (!("objectID" in feature.feature.properties)) { return; }
var objectID = feature.feature.properties.objectID;
if (objectID == id) {
action.call(base, feature);
}
});
}
Instead (or in parallel) of "merging" all created GeoJSON features into a single Leaflet GeoJSON layer group (which you do with addData), why not creating first each feature in its own Leaflet GeoJSON layer, so that it gives you the handle you are looking for (then you could simply record this handle in an object / mapping with the key being your objectID for example)?
If desired, you could even still merge the individual layers into your single GeoJSON layer group after that.
var myGeoJsonLayerGroup = L.geoJson().addTo(map);
var myFeaturesMap = {};
function addNewFeatureToGeoJsonLayerGroup(newGeoJsonData) {
var newGeoJSONfeature = L.geoJson(newGeoJsonData);
myFeaturesMap[newGeoJsonData.properties.objectID] = newGeoJSONfeature;
myGeoJsonLayerGroup.addLayer(newGeoJSONfeature);
}
function updateFeature(updatedGeoJsonData) {
var updatedFeature = myFeaturesMap[updatedGeoJsonData.properties.objectID];
updatedFeature.clearLayers(); // Remove the previously created layer.
updatedFeature.addData(updatedGeoJsonData); // Replace it by the new data.
}
function deleteFeature(deletedGeoJsonData) {
var deletedFeature = myFeaturesMap[deletedGeoJsonData.properties.objectID];
myGeoJsonLayerGroup.removeLayer(deletedFeature);
}
Demo (not using GeoJSON): http://jsfiddle.net/ve2huzxw/94/
EDIT:
A slightly more simple solution would be to store the reference to each individual layer through the onEachFeature function of the GeoJSON layer group:
var myFeaturesMap = {};
var myGeoJsonLayerGroup = L.geoJson({
onEachFeature: function (feature, layer) {
myFeaturesMap[feature.properties.objectID] = layer;
}
}).addTo(map);
function addNewFeatureToGeoJsonLayerGroup(newGeoJsonData) {
myGeoJsonLayerGroup.addData(newGeoJsonData);
}
function updateFeature(updatedGeoJsonData) {
deleteFeature(updatedGeoJsonData); // Remove the previously created layer.
addNewFeatureToGeoJsonLayerGroup(updatedGeoJsonData); // Replace it by the new data.
}
function deleteFeature(deletedGeoJsonData) {
var deletedFeature = myFeaturesMap[deletedGeoJsonData.properties.objectID];
myGeoJsonLayerGroup.removeLayer(deletedFeature);
}
If you want to store references to the actual layers being created, you can access them from the layeradd event on your L.GeoJSON instance:
var geojson = new L.GeoJSON().on(
'layeradd', function (e) {
console.log(e.layer);
}
).addTo(map);
// This addData call will fire the handler above twice
// because it adds two features.
geojson.addData({
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [90, 0]
}
}, {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-90, 0]
}
}]
});
If someone still looking for another short method to update GeoJSON, you can try something like this,
//function to clear the previous geojson feature
function clearMap() {
map.eachLayer(function(layer){
if(layer.myTag && layer.myTag === 'previousLayer'){
lmap.removeLayer(layer);
}
});
}
function geojsonUpdate(geojsonData){
var geojsonLayer = L.geoJson(geojsonData, {
onEachFeature: function (feature, layer) {
layer.myTag = 'previousLayer'
);
},
});
geojsonLayer.addTo(lmap);
lmap.fitBounds(geojsonLayer.getBounds());
}
//function call
clearMap();
geojsonUpdate(data);

Leaflet: How to toggle GeoJSON feature properties from a single collection?

I have a single GeoJSON object that contains over 2000+ features and each feature is part of one category (i.e. "Electrical", "Military", etc). There are a total of about 38 categories.
Here's the schema example of my collection:
{"type":"Feature","properties":{"category":"Electrical","Name":"Plant No 1"},"geometry":{"type":"Point","coordinates":[81.73828125,62.59334083012024]}},{"type":"Feature","properties":{"category":"Electrical","Name":"Plane No 2"},"geometry":{"type":"Point","coordinates":[94.5703125,58.722598828043374]}},{"type":"Feature","properties":{"category":"Military","Name":"Base 1"},"geometry":{"type":"Point","coordinates":[104.4140625,62.91523303947614]}}
Here's my L.geoJson function that iterates through the
collection:
var allPoints = L.geoJson(myCollection, {
onEachFeature: function(feature, layer){
layer.bindPopup(L.Util.template(popTemplate, feature.properties));
},
"stylel": function(feature){
return { "color": legend[feature.properties.category]
}
}}).addTo(map);
How can I assign each category property to my L.control function so the user can toggle on/off the various categories from the collection? I could do this if I made each category a dataset and an individual geoJSOn layer, but that's too much work to do all 38 categories.
My attempt:
L.control.layers({
'Street Map': L.mapbox.tileLayer('mapbox.streets').addTo(map)
},{
'Electrical': myCollection[feature.properties.category["Electrical"]],
'Military': myCollection[feature.properties.category["Military"]]
});
Is there a better way to do this? Thanks!
You can simply assign your layers within the onEachFeature function. You could even automate the creation of Layer Groups for each category.
Result:
var categories = {},
category;
function onEachFeature(feature, layer) {
layer.bindPopup(L.Util.template(popTemplate, feature.properties));
category = feature.properties.category;
// Initialize the category array if not already set.
if (typeof categories[category] === "undefined") {
categories[category] = [];
}
categories[category].push(layer);
}
// Use function onEachFeature in your L.geoJson initialization.
var overlays = {},
categoryName,
categoryArray;
for (categoryName in categories) {
categoryArray = categories[categoryName];
overlays[categoryName] = L.layerGroup(categoryArray);
}
L.control.layers(basemaps, overlays).addTo(map);
EDIT: replaced overlays to be a mapping instead of an array.
Iterate your GeoJSON collection and create multiple L.GeoJSON layers, one per category and add them as overlays to your L.Control.Layers instance.
var controlLayers = L.control.layers({
'Street Map': L.mapbox.tileLayer('mapbox.streets').addTo(map)
}).addTo(map);
// Object to store category layers
var overlays = {};
// Iterate the collection
collection.features.forEach(function (feature) {
var category = feature.properties.category;
// Check if there's already an overlay for this category
if (!overlays[category]) {
// Create and store new layer in overlays object
overlays[category] = new L.GeoJSON(null, {
'onEachFeature': function () {},
'style': function () {}
});
// Add layer/title to control
controlLayers.addOverlay(overlays[category], category);
}
// Add feature to corresponding layer
overlays[category].addData(feature);
});
Here's an example on Plunker: http://plnkr.co/edit/Zv2xwv?p=preview

Adding properties to a leaflet layer that will become geojson options

Lets say I draw a shape on a mapbox map, and do this on the draw:crated event:
e.layer.properties = {};
e.layer.properties.myId = 'This is myId';
If I do a featureGroup.toGeoJSON() the geojson features has an empty properties object. Is there any way I configure a leaflet layer so that when it is transformed to geoJson, it will have certain properties set?
Actually, the trick is just to define the layer feature with its type (must be a "Feature") and properties (use the latter to record whatever information you need).
map.on('draw:created', function (event) {
var layer = event.layer,
feature = layer.feature = layer.feature || {}; // Initialize feature
feature.type = feature.type || "Feature"; // Initialize feature.type
var props = feature.properties = feature.properties || {}; // Initialize feature.properties
props.myId = 'This is myId';
drawnItems.addLayer(layer); // whatever you want to do with the created layer
});
See also Leaflet Draw not taking properties when converting FeatureGroup to GeoJson and update properties of geojson to use it with leaflet
You can either modify the leaflet source or write your own function to process the layers and set the properties that you are looking for.

Cannot set style on leaflet L.geoJSON layer

I am having a bit of trouble setting the style on a L.geoJSON based layer,my code looks like this:
var addProperties = function addProperties(prop,map)
{
//the API does not seem to support adding properties to an existing feature,
//the idea here is simple:
//(1) currentFeature.toGeoJson() needs to be called to obtain a json representation
//(2) set the properties on the geojson
//(3) create a new feature based on the geojson
//(4) remove res and add the new feature as res
var style = function style(feature){
var markerStyle = {
draggable: 'true',
icon: L.AwesomeMarkers.icon({
icon: 'link',
prefix: 'glyphicon',
markerColor: 'red',
spin: true
})
};
if(feature.geometry.type==='Point')
return markerStyle;
};
var onEachFeature = function onEachFeature(feature,layer){
console.log("Inside on each feature,checking to see if feature was passed to it ",feature);
layer.on('click',function(e){
//open display sidebar
console.log("Checking to see if setupTabs exists ",setupTabs);
setupTabs('#display-feature-tabs');
console.log("Checking to see if featureInfo exists ",featureInfo);
var featureInfoAPI =featureInfo('feature-properties');
featureInfoAPI.swap(feature.properties);
setTimeout(function() {
sidebar.show();
}, 100);
});
};
console.log("Inside add properties");
var geoJSON,feature;
if(res != null)
{
geoJSON = res.toGeoJSON();
geoJSON.properties = prop;
console.log(geoJSON);
feature = L.geoJson(geoJSON,{style:style,onEachFeature:onEachFeature});
console.log("The new feature that has been created ",feature);
removeFeature(map);
addFeature(feature);
feature.addTo(map);
}
};
I have also tried the style method as well,I am looking to add styles to the active layer for points(styles will also be added for lines and polylines by type).
To style point features, you should use pointToLayer option instead.