Markkers are displayed twice with Leaflet search - javascript

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/

Related

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

Leaflet Draw "Cannot read property 'enable' of undefined" adding control to geoJSON layer

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

Enable editing geojson data

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.

LeafletJS popups positions and markers add and remove issue

I am implementing displaying locations on openstreetmaps using leafletjs API. Here is the scenario, When the page loads a JS function is being triggered which will query DB to get current locations of all the devices and show them as markers on map. I am facing two major issues in it:
--> I am trying to bind popups with each marker so that it could be descriptive. In other words user should gain idea that of what device this marker for. The problem is that popups is being showed over the marker making marker non-visible. Here is the screenshot:
Marker is shown below(after closing one popup):
--> On this page, I am displaying devices names in a table. The first column of this table is checkbox. When user check a device its marker appears on the map as expected(working fine). After that if user unchecks that device then its marker disappears. But when user re-selects that device again then its marker doesn't appear.
Here is my code:
function OnPageLoad() {
var IDs = GetAllSelectedDeviceIDs();
LoadOpenStreetMap(IDs);
}
var map;
var markers = new Array();
function LoadOpenStreetMap(deviceIDs) {
for (var i = 0; i < deviceIDs.length; i++) {
$.ajax({
url: "../Database/getcurrentlocation.php",
cache: false,
type: "post",
data: "deviceId=" + deviceIDs[i] + "&r=" + Math.random(),
dataType: "html",
async: false,
success: function (data) {
if (data != null) {
var dataArray = data.split(/~/);
SetOpenStreetMap(dataArray[0], dataArray[1], deviceIDs[i], i, flags[i]);
}
}
});
}
deviceIDs = GetAllSelectedDeviceIDs();
setTimeout(function () { LoadOpenStreetMap(deviceIDs); }, 500);
}
function SetOpenStreetMap(lati, longi, deviceID, markerIndex, markerFlag) {
if (!map) {
map = L.map('map').setView([lati, longi], 12);
L.tileLayer('http://{s}.tile.cloudmade.com/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/997/256/{z}/{x}/{y}.png', {
attribution: '',
maxZoom: 18
}).addTo(map);
}
if (markerFlag == 1) {
//need to add marker
if (!markers[markerIndex]) {
var popupLocation1 = new L.LatLng(lati, longi);
var popupContent1 = 'This is a nice popup.';
popup1 = new L.Popup();
popup1.setLatLng(popupLocation1);
popup1.setContent(popupContent1);
markers[markerIndex] = L.marker([lati, longi]).addTo(map);
markers[markerIndex].bindPopup(popupContent1);
map.addLayer(popup1);
}
else
markers[markerIndex].setLatLng([lati, longi]).update();
}
else {
//need to remove marker
if (markers[markerIndex]) {
map.removeLayer(markers[markerIndex]);
}
}
}
Any help would be appreciated.
Thanks.
To put the popup over the marker with some offset there exists an offset property:
if (markerFlag == 1) { //need to add marker
if (!markers[markerIndex]) {
var popupLocation1 = new L.LatLng(lati, longi);
var popupContent1 = 'This is a nice popup.';
popup1 = new L.Popup();
popup1.setLatLng(popupLocation1);
popup1.setContent(popupContent1);
popup1.offset = new L.Point(0, -20);
markers[markerIndex] = L.marker([lati, longi]).addTo(map);
/* markers[markerIndex].bindPopup(popupContent1); */
map.addLayer(popup1);
} else
markers[markerIndex].setLatLng([lati, longi]).update();
}
Set the offset inside the options object when creating the popup.
var options = {
offset: new L.Point(1, -20)
};
var popup = L.popup(options)
.setLatLng([ lat, lng ])
.setContent('Your popup content goes here!')
.openOn(map)
;
The "options" object is the first object that is passed to the L.popup()
To show a popup over a marker, only call the bindPopup method on the marker.
This is shown in the QuickStart.
You do not need to instantiate a popup, yourself.

Categories

Resources