Enable editing geojson data - javascript

Enable editing to layers drown using geojson
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
Json data
$.getJSON("js/draw/neighborhoods.json",function(hoodData){
alert("this is editableLayers");
var i = 0;
var geojsonlayer = L.geoJson(hoodData,
{
onEachFeature: function (feature, layer) {
alert(feature.properties.prop0);
var myLayer = layer;
drawnItems.addLayer(myLayer);
}
});
map.addLayer(drawnItems);
});
Adding control
//draw control
var drawControl = new L.Control.Draw({
draw: false,
edit: {
featureGroup: drawnItems,
remove: false,
edit: true
}
});
map.addControl(drawControl);
map.on('draw:edited', function (e) {
var layers = e.layers;
layers.eachLayer(function (layer) {
console.log(layer)
});
});
Using this code i am able to draw the layers but unable to edit it.
I am using leaflet.draw lib.

If you could set up a JSFiddle with some sample data, I can take a closer look at the process you have set up.
Ideally, the geojson data would be available prior to adding to the control and adding to the map. Since this is asynchronous, you should have the control and layer added to the map after each update from your ajax/getJSON closure. Sort of like cleaning the workspace each time you have new data to edit.

Related

Markkers are displayed twice with Leaflet search

I've made a map, based on a geojson file and with clustered markers.
Then I tried to add the leaflet-search plugin.
The search feature works : when I search somtehing, it opens the good popup (informations are generated by "complex" routines).
But now I have my markers displayed twice : the ones I previously created, then those displayed by the search plugin.
How to make leaflet-search to not display its own markers ?
I hope I was clear enough. Bellow here is a sample of my code (I tried to made it readable) :
var geojsonFeature = { mygeojsondata };
// Runs several function to generate an "information page" for each feature
function createPopupInfo(feature, layer) {
var pop = render_name(feature);
//
// ...
}
var nbfeatures = 0;
var layer1 = new L.geoJSON(geojsonFeature, {
onEachFeature: createPopupInfo,
pointToLayer: function (feature, latlng) {
nbfeatures++;
var marker = L.marker(latlng)
arrayOfLatLngs.push(latlng);
marker.on("add", function (event) {
// Retrieve the layer through event.target http://leafletjs.com/reference-1.0.0.html#event-target
event.target.openPopup();
var latLngs = [marker.getLatLng()];
var markerBounds = L.latLngBounds(latLngs);
map.fitBounds(markerBounds);
});
map.maxBoundsViscosity = 0;
return marker;
}
});
var searchControl = new L.Control.Search({
layer: layer1,
propertyName: 'search_index',
marker: false,
moveToLocation: function (latlng, title, map) {
map.setView(latlng, 17);
}
});
searchControl.on('search:locationfound', function (e) {
if (e.layer._popup)
e.layer.openPopup();
}).on('search:collapsed', function (e) {
layer1.eachLayer(function (layer) { //restore feature color
layer1.resetStyle(layer);
});
});
// Clustering
var markers = L.markerClusterGroup();
markers.addLayer(layer1);
map.addLayer(markers);
When the search finds something, harness that event to remove the layer with all the markers:
searchControl.on('search:locationfound', function (e) {
if (e.layer._popup) e.layer.openPopup();
markers.removeLayer(layer1)
})
Of course you'll also want to add these markers back in when you close the search:
searchControlon('search:collapsed', function (e) {
markers.addLayer(layer1);
layer1.eachLayer(function (layer) { //restore feature color
layer1.resetStyle(layer);
});
});
I would say its good UX to also add them all back in when the search comes up empty, but theres' no obvious event for that with leaflet-search.
I found what didn't work, I must pass the "clustered layer" :
var searchControl = new L.Control.Search({
layer: markers,
propertyName: 'search_index',
...
Sources :
https://gis.stackexchange.com/questions/310797/using-l-control-search-and-l-markerclustergroup
https://github.com/stefanocudini/leaflet-search/issues/166
And another example :
http://embed.plnkr.co/46VJcp/

Leaflet move single point of polygon in GeoJSON layer

I have leaflet with a geoJSON layer group and load several geoJSON features, each as a separate layer added to the geoJSON layer group. For a given selected layer, I need to move a point of the polygon on that layer using javascript. So, for example, I may need to move the 3rd vertex to 30.123, -80.123. I cannot figure out how to do this. I can move a marker easily with the setLatLng() method but I can't find anything to change a polygon point.
Here is an example of how I am creating the map and adding my geoJSON features:
function createMap(){
myMap = L.map('locationMap', {
editable: true,
attributionControl: false,
fullscreenControl: true,
fullscreenControlOptions: {
position: 'topleft'
}
}).setView([#Model.MapCenterLat, #Model.MapCenterLong], #Model.MapInitialZoom);
L.tileLayer('#Model.MapUrl2', {
drawControl: true,
maxZoom: 20,
id: 'mapbox.streets'
}).addTo(myMap);
geoJsonLayer = L.geoJson().addTo(myMap);
loadGeoFences('');
}
function loadGeoFences(parentId) {
var url = '#Url.Action("GetGeoFences")';
$.get(url, { parentId: parentId },
function (data) {
if (data.length > 0) {
$.each(data, function (index, value) {
var newLayer = L.geoJson(value,
{
onEachFeature: applyLayerStyle
});
newLayer.addTo(geoJsonLayer);
});
}
});
}
I was able to do this using the leaflet.editing plugin. Once you have the correct layer, the layer.editing.latlngs array can be modified with the desired coordinates. Then call layer.redraw() to update the polygon.
You can change the latlngs while geoJson loading with following:
function onEachFeature(feature, layer) {
if(layer instanceof L.Polyline){
var latlngs = layer.getLatLngs()
var ll = latlngs[0][2];
ll.lat = 51.490056
latlngs[0][2] = ll;
layer.setLatLngs(latlngs);
}
}
L.geoJSON(json,{onEachFeature: onEachFeature}).addTo(map);
https://jsfiddle.net/falkedesign/hvdxo3z7/

Best way to convert Leaflet Geojson layers to Leaflet rectangle vector [duplicate]

I am trying to use leaflet's edit function on polygons that I loaded from my database. When I click on leaflet's edit button I get the error
Cannot read property 'enable' of undefined
This thread describes a similar problem, and user ddproxy said
"Since FeatureGroup extends LayerGroup You can walk through the layers
presented and add them individually to the FeatureGroup used for
Leaflet.draw"
I am confused what he means by "walk through", I thought I was adding a layer group, so i'm not sure what I would be walking through. Does this have to do with the fact that i'm adding the polygons as a geoJSON object? Adding the polygons to the map, binding their popups, and assigning them custom colors works perfectly FYI.
The following is the relevant code:
<script>
window.addEventListener("load", function(event){
//other stuff
loadHazards();
});
//next 6 lines siply add map to page
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
var osmAttrib = '© OpenStreetMap contributors'
var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib})
var map = new L.Map('map', { center: new L.LatLng(39.255467, -76.711964), zoom: 16 })
osm.addTo(map);
var drawnItems = L.featureGroup().addTo(map);
var Hazards = L.featureGroup().addTo(map);
L.control.layers({
'osm': osm.addTo(map)
},
{
'drawlayer': drawnItems,
"Hazards" : Hazards,
"Tickets": Tickets
},
{
position: 'topleft', collapsed: false
}
).addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: Hazards,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
},
rectangle:false,
circle:false,
circlemarker:false
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
});
</script>
And the loadHazards() function:
function loadHazards(){
$.ajax({
type: 'GET',
url:'/loadPolygonFromDatabase',
success : function(polygons){
polygons = JSON.parse(polygons);
var toAdd = [];
for (i in polygons){
var item = {
"type" : "Feature",
"properties":{
"category":"",
"description":"",
"ID":""
},
"geometry" : {
"type":"Polygon",
"coordinates":[],
}
};
item["geometry"]["coordinates"][0] = polygons[i]["coordinates"];
item["properties"]["category"] = polygons[i]["category"];
item["properties"]["description"] = polygons[i]["description"];
item["properties"]["ID"] = polygons[i]["ID"];
toAdd.push(item);
}
//Add information to popup
var layerGroup = L.geoJSON(toAdd, {
onEachFeature: function (feature, layer) {
layer.bindPopup( '<h1>' + feature.properties.category + '</h1>'
+ '<p>' + feature.properties.description + '</p>');
layer.id = feature.properties.ID;
},
style: function(feature){
switch (feature.properties.category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}
}).addTo(Hazards);
}
});
}
Thanks in advance!
As mentioned by #ghybs Leaflet.Draw doesn't support Groups or MultiPolygons. I needed the same functionality so a few years ago I created Leaflet-Geoman (previously named leaflet.pm) which supports holes, MultiPolygons, GeoJSON and LayerGroups:
https://github.com/geoman-io/leaflet-geoman
Hope it helps.
Unfortunately Leaflet.draw plugin does not handle nested Layer Groups (same for Feature Groups / GeoJSON Layer Groups).
That is the meaning of the Leaflet.draw #398 issue you reference: they advise looping through the child layers of your Layer/Feature/GeoJSON Layer Group (e.g. with their eachLayer method). If the child layer is a non-group layer, then add it to your editable Feature Group. If it is another nested group, then loop through its own child layers again.
See the code proposed in that post:
https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
var geoJsonGroup = L.geoJson(myGeoJSON);
addNonGroupLayers(geoJsonGroup, drawnItems);
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461
function addNonGroupLayers(sourceLayer, targetGroup) {
if (sourceLayer instanceof L.LayerGroup) {
sourceLayer.eachLayer(function(layer) {
addNonGroupLayers(layer, targetGroup);
});
} else {
targetGroup.addLayer(sourceLayer);
}
}
In your very case, you can also refactor your code with 2 other solutions:
Instead of building your layerGroup (which is actually a Leaflet GeoJSON Layer Group) first and then add it into your Hazards Feature Group, make the latter a GeoJSON Layer Group from the beginning, and addData for each of your single Features (item):
var Hazards = L.geoJSON(null, yourOptions).addTo(map);
for (i in polygons) {
var item = {
"type" : "Feature",
// etc.
};
// toAdd.push(item);
Hazards.addData(item); // Directly add the GeoJSON Feature object
}
Instead of building a GeoJSON Feature Object (item) and parse it into a Leaflet GeoJSON Layer, you can directly build a Leaflet Polygon and add it into your Hazards Layer/Feature Group:
for (i in polygons) {
var coords = polygons[i]["coordinates"];
var style = getStyle(polygons[i]["category"]);
var popup = ""; // fill it as you wish
// Directly build a Leaflet layer instead of an intermediary GeoJSON Feature
var itemLayer = L.polygon(coords, style).bindPopup(popup);
itemLayer.id = polygons[i]["ID"];
itemLayer.addTo(Hazards);
}
function getStyle(category) {
switch (category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}

How to edit loaded geoJson with Leaflet

I want to retrieve polygon data from a database, then edit it. I can retrieve the polygons (stored as geojson), but cannot make them editable. How can I do this?
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: drawnItems
}
});
map.addControl(drawControl);
map.on('draw:created',function(e) {
e.layer.addTo(drawnItems);
});
L.control.layers(baseLayers).addTo(map);
var oldPolygon = null;
function showOnMap(rowid){
if(oldPolygon != null){
map.removeLayer(oldPolygon);
}
$.get("testdbextract.php?show="+rowid,function(data){
var newPolygon = L.geoJson(data);
newPolygon.addTo(drawnItems); // or drawnItems.addLayer(newPolygon);
oldPolygon = newPolygon;
});
}
In your example, you need to parse the geojson data you receive, create layers and initialize drawnItems
To make it easier you can create a GeoJson layer like this:
// Create a GeoJson layer without adding it to the map
L.geoJson(geojsonFeatures, {
onEachFeature: onEachFeature
});
// Take advantage of the onEachFeature callback to initialize drawnItems
function onEachFeature(feature, layer) {
drawnItems.addLayer(layer);
}
Here is an example
In your code, it could be used like that
$.get("testdbextract.php?show="+rowid,function(data){
L.geoJson(data, {
onEachFeature: onEachFeature
});
});

Toggle layers on and off in Leaflet (more complex scenario)

I am using jQuery's getJSON method to load external line data I've created in QGIS.
What I'm trying to do is toggle my layers on and off - simple check boxes, no radio button for the basemap. I'd also like all the layers to be off when the map is initially loaded.
My code
var map=L.map('map').setView([41.9698, -87.6859], 12);
var basemap = L.tileLayer('http://a.tile.stamen.com/toner/{z}/{x}/{y}.png',
{
//attribution: would go here
maxZoom: 17,
minZoom: 9
}).addTo(map);
//display geoJson to the map as a vector
var x = function(source, map)
{
var layers = L.geoJson(source,
{
style: function(feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
},
onEachFeature: function(feature, geojson){
var popupText="<h1 class='makebold'>Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>Which Side?: </h1>"+feature.properties.side;
geojson.bindPopup(popupText);
}
}).addTo(map);
};
$.getJSON("data/Knox.geojson", function(source){ x(source, map); });
$.getJSON("data/abc.geojson", function(source){ x(source, map); });
$.getJSON("data/xyz.geojson", function(source){ x(source, map); });
I tried assigning a variable before the L.geoJson function (var layers), and then L.control.layers(null, layers).addTo(map); That doesn't seem to work.
How does one create a layer control for multiple external geojson's that are already associated with a few callback functions (L.geoJson, style, and onEachFeature)? Thanks in advance.
EDIT:
Since you clarified that you want just the entire collection to be switched on/off, it is even more simple (and almost like what you tried by assigning your L.geoJson to var layers), but you have to take care of asynchronous processes.
To avoid this issue, you could do something like:
var myLayerGroup = L.layerGroup(), // do not add to map initially.
overlays = {
"Merged GeoJSON collections": myLayerGroup
};
L.control.layers(null, overlays).addTo(map);
function x(source, map) {
// Merge the GeoJSON layer into the Layer Group.
myLayerGroup.addLayer(L.geoJson({}, {
style: function (feature) { /* … */ },
onEachFeature: function (feature, layer) { /* … */ }
}));
}
$.getJSON("data/Knox.geojson", function(source){
x(source, map);
});
Then myLayerGroup will be gradually populated with your GeoJSON features, when they are received from the jQuery getJSON requests and they are converted by L.geoJson.
If my understanding is correct, you would like the ability to switch on/off independently each feature from your GeoJSON data?
In that case, you would simply populate your layers object while building the L.geoJson layer group, e.g. inside the onEachFeature function:
var layers = {};
L.geoJson(source, {
style: function (feature) { /* … */ },
onEachFeature: function(feature, layer){
var popupText = "<h1 class='makebold'>Border: </h1>" +
feature.properties.name + "<br/>" +
"<h1 class='makebold'>Which Side?: </h1>" +
feature.properties.side;
layer.bindPopup(popupText);
// Populate `layers` with each layer built from a GeoJSON feature.
layers[feature.properties.name] = layer;
}
});
var myLayersControl = L.control.layers(null, layers).addTo(map);
If you have more GeoJSON data to load and to convert into Leaflet layers, simply do exactly the same (adding built layer into layers in onEachFeature function) and build the Layers Control only once at the end, or use myLayersControl.addOverlay(layer).
Note: make sure to structure your code to take into account your several asynchronous processes, if you load each GeoJSON data in a separate request. Refer to jQuery Deferred object. Or simply create your Layers Control first and use the addOverlay method.
If you want them to be initially hidden from the map, simply do not add the geoJson layer to the map…
I learned a lot more about layer control in Leaflet than I expected, which is great.
#ghybs offered really helpful suggestions.
My issue was about toggling external geoJson files on and off, particularly with the getJSON jQuery method. I was trying to assign a variable within my multiple callbacks, like:
var layers=L.geoJson(source,{
{style: /*....*/},
{onEachFeature: /*....*/}}
and then just going L.control.layers(null, layers).addTo(map);
That doesn't work (why? I still can't explain-I'm quite the beginner-programmer). The way I did get this to work was by creating my style and onEachFeature functions separately, like this:
function borders (feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
};
and
function popUp (feature, geojson){
var popupText="<h1 class='makebold'>
Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>
Which Side</h1>"+feature.properties.side;geojson.bindPopup(popupText);
};
and then assigning these directly as callbacks into the getJSON method. By doing it this way, I could create a variable before "drawing" my geoJson to the map with L.geoJson(). Then I could assign the variable dynamically(?) to the layer control:
$.getJSON("data/xyz.geojson", function(source){
var xyz = L.geoJson(source, {
style: borders,
onEachFeature: popUp});
togglelayer.addOverlay(xyz, 'This name shows up on the control')});
});
I stored the variable togglelayer like this:
var togglelayer = L.control.layers(null, null,{collapsed: false}).addTo(map);
This post was also helpful: How to add two geoJSON feature collections in to two layer groups

Categories

Resources