in-place Update Leaflet GeoJSON feature - leaflet

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);

Related

Dynamically add data, remove markers outside of bounds, add markers inside of bounds

I've been struggling with this feature for weeks, but am starting to think it's not possible. Hopefully someone here can prove me wrong! :)
Using Mapbox GL. On page load, map renders with markers within the given bounds. I'm trying to mimic functionality where the user drags the map, and based on the new bounds, new markers are drawn and old ones are removed. Data for the new markers are dynamic based on an API request. I managed to find a function after much Googling to test if a point is in bounds of a map and that works, but given how the function works to add/remove the marker, dynamic data doesn't seem to fit in.
I've created a fiddle here and hard-coded a new "feature" but it's not getting drawn. There is most likely a second part of this issue, but maybe i can figure it out on my own once this is deemed feasible
Any advice would be greatly appreciated. Thanks in advance!
geojson.features.forEach(function (marker) {
// create a DOM element for the marker
var el = document.createElement('div');
el.className = 'marker';
el.style.backgroundImage = 'url(https://placekitten.com/g/' + marker.properties.iconSize.join('/') + '/)';
el.style.width = marker.properties.iconSize[0] + 'px';
el.style.height = marker.properties.iconSize[1] + 'px';
el.addEventListener('click', function () {
window.alert(marker.properties.message);
});
// add marker to map
var point = new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates);
map.on("dragend", function() {
if ( inBounds(marker.geometry.coordinates, map.getBounds()) == false ) {
point.remove();
} else {
geojson.features.push({
"type": "Feature",
"properties": {
"message": "Lurman",
"iconSize": [20, 20]
},
"geometry": {
"type": "Point",
"coordinates": [
-59.29223632812499,
-17.28151823530889
]
}
})
point.addTo(map);
}
})
});
function inBounds(point, bounds) {
var lng = (point[0] - bounds._ne.lng) * (point[0] - bounds._sw.lng) < 0;
var lat = (point[1] - bounds._ne.lat) * (point[1] - bounds._sw.lat) < 0;
return lng && lat;
}
I was able to resolve this by using layers and updating it on dragend.

Mapbox No Source With This ID

I want to remove sources and layers on Mapbox map. I managed to remove every sources and layers except for the first source and layer that I have added to Mapbox map.
Note that I am not good in using jQuery $.post.
Here is how I add all the sources and layers.
$.post('ajax/marker.php', function(data)
{
var firstSplit = data.split(",");
for(i=0;i<firstSplit.length-1;i++)
{
var secondSplit = firstSplit[i].split("|");
var id = secondSplit[0];
var lat = secondSplit[1];
var lng = secondSplit[2];
var point = {
"type": "Point",
"coordinates": [lat, lng]
};
map.addSource(id, { type: 'geojson', data: point });
map.addLayer({
"id": id,
"type": "symbol",
"source": id,
"layout": {
"icon-image": "airport-15"
}
});
}
});
Remember that, I managed to view all the sources and layers on Mapbox map.
Its just that I am not able to remove only the first source and layers that I have added to the map. I hope someone out there has some ideas regarding this problem. Thanks.
I used the two statements below in a loop to remove sources and layers.
map.removeSource(id);
map.removeLayer(id);
I did a test to remove the first source and layers manually as below but it did not work.
map.removeSource('1612280004A');
map.removeLayer('1612280004A');
However, it works on the next sources and layers.
My best guess from what you've posted is that you can't remove the layer while there are still sources attached to it. Try reversing the order of your two statements:
map.removeLayer('1612280004A');
map.removeSource('1612280004A');
My GeoJson shared the same ID. Removing both layer and source fixed this issue
function RemoveMapLayer() {
var mpLayer = map.getLayer("points");
if (typeof mpLayer === 'undefined') {
// No Layer
} else {
map.removeLayer("points");
}
var mpSource = map.getSource("points");
if (typeof mpSource === 'undefined') {
alert("no source");
} else {
map.removeSource("points");
}
}
Before deleting, check if the source and layer are present, for example:
if (map.getLayer('points')){
map.removeLayer('points');
}
if (map.getSource('point')){
map.removeSource('point');
}

Leaflet Draw not taking properties when converting FeatureGroup to GeoJson

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 ) );
}} )

geojson ignored when using mapbox

why does mapbox ignore my geoJson marker-symbol, marker-color, and marker-size? if for whatever reason it ignores, how do you set either?
sample geoJson:
"properties": {
"id": 578202,
"name": "University of North Carolina at Charlotte",
"marker-symbol": "marker",
"marker-color": "#ff8888",
"marker-size": "small",
"description": 1
}
script:
$.getJSON(url, function(data) {
var geojson = L.geoJson(data, {
onEachFeature: function(feature, layer) {
var popupContent = feature.properties.name +'project(s)';
layer.bindPopup(popupContent, {
closeButton: true,
minWidth: 225
});
}
});
var map = L.mapbox.map('map', '', {
attributionControl: false
});
geojson.addTo(map);
});
That's happening because L.GeoJSON doesn't automaticly know that you want to set the marker options, so if it encounters a Point feature, it simply adds a default marker. If you want to do something special with point features, you can use the pointToLayer function of L.GeoJSON, check the following example:
var geoJsonLayer = L.geoJson(geoJson, {
pointToLayer: function (feature, latLng) {
return L.marker(latLng, {
icon: L.mapbox.marker.icon(feature.properties)
});
}
}).addTo(map);
The only problem with this is that it also adds all the other properties as options of the markericon. Personally i would write some logic so that only the relevant properties get added to the icon options.
Here's a working example on Plunker: http://plnkr.co/edit/3OJPXxOYdzX8mSnjEb90?p=preview
As tmcw pointed out in the comments (see below): you could use L.mapbox.featureLayer, it does exactly what you're trying to accomplish with L.GeoJSON without having to resort to the pointToLayer method i described above and it only uses the appropriate properties. It can even load your data for you so you can do away with jQuery's $.getJSON. Win/win situation if you ask me. You can simply do the following and you're set:
L.mapbox.featureLayer(url).addTo(map);
Here's the working example of this on Plunker: http://plnkr.co/edit/Og6tuYDIkTX7ftedoR3C?p=preview

Load geoJson in MapBox for editing with Leaflet.Draw

I try to load geoJson data in Mapbox and edit it with the plugin Leaflet.Draw
Here is an example : fiddle
var featureGroup = L.featureGroup().addTo(map);
var geojson = {
"type": "FeatureCollection",
"features": [ ........... ]
}
L.geoJson(geojson).addTo(featureGroup);
When i click to the edit button, i have an error :
Uncaught TypeError: Cannot read property 'enable' of undefined
Object seems to be editable but i can't modify it.
What is the correct way to add geojson object in mapbox draw layer ?
I have found the solution :
L.geoJson(geojson, {
onEachFeature: function (feature, layer) {
featureGroup.addLayer(layer);
}
});
Here is working example using CoffeeScript:
drawnItems = new L.FeatureGroup()
map.addLayer drawnItems
layers = L.geoJson geojson
layers.eachLayer (layer) => drawnItems.addLayer layer
I had to do the following to get mine to work (in addition to the above answers):
L.geoJson(geojson, {
onEachFeature: function (feature, layer) {
if (layer.getLayers) {
layer.getLayers().forEach(function (l) {
featureGroup.addLayer(l);
})
} else {
featureGroup.addLayer(layer);
}
}
});
This was for a geojson that was a "Feature" type.