checking duplicate markers leaflet geoman - leaflet

Im trying to check each geojson feature if it is a Marker. If it is I want to remove the placed layer then init drawing marker again.
If it is not the same position, I will just add it to the feature layer.
The problem is with eachLayer it always returns true because it loops trough all layers and it always return true because marker is added to feature. So it always repeats.
features.eachLayer(layer => {
if(layer.pm._shape === 'Marker') {
if(e.layer._latlng !== layer._latlng) { //This is never true, should be true if the placed marker is not placed over an existing features marker
features.addLayer(e.layer);
} else if(e.layer._latlng === layer._latlng) { //this elseif is always true for some reason and will loop
map.removeLayer(e.layer)
DrawUtil.addMarker(map, isSnapping); //Alias for pm.enableDraw.marker
features.addLayer(e.layer);
}
}
})
Here is fiddle, my bad forgot to add vital code.
https://jsfiddle.net/2ftmy0bu/2/

Change your code to:
// listen to when a new layer is created
map.on('pm:create', function(e) {
//should only place one marker each time
// check if the layer with the same latlng exists
var eq = features.getLayers().find(function(layer){
if(layer instanceof L.Marker) {
return layer.getLatLng().equals(e.layer.getLatLng())
}else{
return false;
}
}) !== undefined;
if(!eq) {
console.log('not equal')
features.addLayer(e.layer);
map.pm.disableDraw('Marker')
//if marker is placed on the map and it is not placed on same place as another marker
} else if(eq) {
console.log('equal')
//if marker is placed on the map over another marker, remove marker, then init draw marker again.
map.removeLayer(e.layer);
map.pm.enableDraw('Marker', {
snappable: true,
snapDistance: 20,
finishOn: 'click' || 'mousedown',
});
// TODO: I think you don't want this
// features.addLayer(e.layer);
}
});
https://jsfiddle.net/falkedesign/c6Lf758j/

Related

How to check Mapbox GL JS Draw state

How can you check the state of a new MapboxDraw object before sending it to the backend? For example, to show the user some warnings when he tries to submit some actions without creating an object (in my case a polygon) on the map.
this.draw = new MapboxDraw({
controls: {
trash: true,
polygon: true
},
defaultMode: 'draw_polygon',
displayControlsDefault: false,
})
# sudocode
if (user has not created a polygon on the map) {
alert('You must create a polygon before submitting the form!')
}
I actually tried to solve this with the following code, because the length value of the correct polygon must be more than 3 points.
if (draw.getAll().features[0].geometry.coordinates[0].length <= 3) {
alert('You must create a polygon before submitting the form!')
}
The above code only works in the first execution, but in the second execution it causes an error e.g if user tries to create a Polygon of two points or if user creates one polygon and then removes it
Uncaught TypeError: Cannot read property 'geometry' of undefined
You can attach many events from Mapbox Draw to your current map.
For example, map.on('draw.crete', function() {}) This will execute once 1 polygon was already created.
You can also use draw.getMode() for catching any type of polygons you draw.
See below example, Hope it helps :)
var draw = new mapboxDraw({
displayControlsDefault: false,
controls: {
point: true,
polygon: true,
line_string: true,
trash: true
}
});
map.on('draw.create', function(e) {
var drawMode = draw.getMode();
var drawnFeature = e.features[0];
switch (drawMode) {
case 'draw_point':
// Draw point here
break;
case 'draw_polygon':
// Draw polygon here
break;
case 'draw_line_string':
// Draw linestring here
break;
default: alert('no draw options'); break;
}
});
map.on('draw.update', function(e) {
// This will call once you edit drawn polygon
});
map.on('draw.delete', function(e) {
// This will call once you delete any polygon
});

Toggle geojson layer interactivity

First of all, since it's my first question, I'm sorry if I make any mistakes.
I'd like to "deactivate" a Geojson Layer, composed by a feature collection with many polygons (or lines). I have seen how to create it with the option interactive = yes or interactive = no, but I can't find out how to toggle this dynamically.
Thanks in advance.
EDIT: last thing I tried was this:
var onEachFeature = function(feature, layer) {
function onmouseover(e) {
var layer = e.target;
var activeLayer = ___get_active_layergroup();
if (activeLayer === null){
layer.setStyle({
cursor: 'move'
});
} else if (activeLayer.hasLayer(layer)){
layer.setStyle({
cursor: 'pointer'
});
} else {
layer.setStyle({
cursor: 'move'
});
}
}
function onmouseout(e) {
}
return {
mouseover: onmouseover,
mouseout: onmouseout
}
}
And I put that function in the layer initialization.
___get_active_layergroup is accesible in the context.
EDIT2:
It works if instead of setStyle I use this:
var element = layer.getElement();
if (element.classList.contains("leaflet-interactive")) {
element.classList.remove("leaflet-interactive")
}
But I don't really like how it works. I can see the pointer while I'm moving the mouse blinking (because it takes a few milliseconds process the mouseover I think).

Leaflet Trigger Event on Clustered Marker by external element

I just starting to learn about Leaflet.js for my upcoming project.
What i am trying to accomplish:
I need to make a list of marker which displayed on the map, and when the list item is being hovered (or mouseover) it will show where the position on the map (for single marker, it should change its color. For Clustered marker, it should display Coverage Line like how it behave when we hover it.. and perhaps change its color too if possible).
The map should not be changed as well as the zoom level, to put it simply, i need to highlight the marker/ Cluster on the map.
What i have accomplished now : I am able to do it on Single Marker.
what i super frustrated about : I failed to find a way to make it happen on Clustered Marker.
I use global var object to store any created marker.
function updateMapMarkerResult(data) {
markers.clearLayers();
for (var i = 0; i < data.length; i++) {
var a = data[i];
var myIcon = L.divIcon({
className: 'prop-div-icon',
html: a.Description
});
var marker = L.marker(new L.LatLng(a.Latitude, a.Longitude), {
icon: myIcon
}, {
title: a.Name
});
marker.bindPopup('<div><div class="row"><h5>Name : ' + a.Name + '</h5></div><div class="row">Lat : ' + a.Latitude + '</div><div class="row">Lng : ' + a.Longitude + '</div>' + '</div>');
marker.on('mouseover', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon");
this._icon.classList.add("prop-div-icon-shadow");
}
});
marker.on('mouseout', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon-shadow");
this._icon.classList.add("prop-div-icon");
}
});
markersRef[a.LocId] = marker; // <-- Store Reference
markers.addLayer(marker);
updateMapListResult(a, i + 1);
}
map.addLayer(markers);
}
But i don't know which object or property to get the Clustered Marker reference.
And i trigger the marker event by my global variable (which only works on single marker).
...
li.addEventListener("mouseover", function(e) {
jQuery(this).addClass("btn-info");
markersRef[this.getAttribute('marker')].fire('mouseover'); // --> Trigger Marker Event "mouseover"
// TODO : Trigger ClusteredMarker Event "mouseover"
});
...
This is my current https://jsfiddle.net/oryza_anggara/2gze75L6/, any lead could be a very big help. Thank you.
Note: the only js lib i'm familiar is JQuery, i have no knowledge for others such as Angular.js
You are probably looking for markers.getVisibleParent(marker) method, to retrieve the containing cluster in case your marker is clustered.
Unfortunately, it is then not enough to fire your event on that cluster. The coverage display functionality is set on the Cluster Group, not on its individual clusters. Therefore you need to fire your event on that group:
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// In case the marker is hidden in a cluster, have the clusterGroup
// show the regular coverage polygon.
markers.fire(eventName, {
layer: visibleLayer
});
} else {
marker.fire(eventName);
}
}
var marker = markersRef[this.getAttribute('marker')];
_fireEventOnMarkerOrVisibleParentCluster(marker, 'mouseover');
Updated JSFiddle: https://jsfiddle.net/2gze75L6/5/
That being said, I think another interesting UI, instead of showing the regular coverage polygon that you get when "manually" hovering a cluster, would be to spiderfy the cluster and highlight your marker. Not very easy to implement, but the result seems nice to me. Here is a quick try, it would probably need more work to make it bullet proof:
Demo: https://jsfiddle.net/2gze75L6/6/
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
if (eventName === 'mouseover') {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// We want to show a marker that is currently hidden in a cluster.
// Make sure it will get highlighted once revealed.
markers.once('spiderfied', function() {
marker.fire(eventName);
});
// Now spiderfy its containing cluster to reveal it.
// This will automatically unspiderfy other clusters.
visibleLayer.spiderfy();
} else {
// The marker is already visible, unspiderfy other clusters if
// they do not contain the marker.
_unspiderfyPreviousClusterIfNotParentOf(marker);
marker.fire(eventName);
}
} else {
// For mouseout, marker should be unclustered already, unless
// the next mouseover happened before?
marker.fire(eventName);
}
}
function _unspiderfyPreviousClusterIfNotParentOf(marker) {
// Check if there is a currently spiderfied cluster.
// If so and it does not contain the marker, unspiderfy it.
var spiderfiedCluster = markers._spiderfied;
if (
spiderfiedCluster
&& !_clusterContainsMarker(spiderfiedCluster, marker)
) {
spiderfiedCluster.unspiderfy();
}
}
function _clusterContainsMarker(cluster, marker) {
var currentLayer = marker;
while (currentLayer && currentLayer !== cluster) {
currentLayer = currentLayer.__parent;
}
// Say if we found a cluster or nothing.
return !!currentLayer;
}

Leaflet L.Icon marker not removing on Map reload

Map contains two types of markers
Circle icon is added by:
let circle = new customCircleMarker([item.latitude, item.longitude], {
color: '#2196F3',
fillColor: '#2196F3',
fillOpacity: 0.8,
radius: radM,
addressId: item.address_id
}).bindTooltip(`Address: <b>${item.address}</b><br/>
Patients from this address: <b>${item.total_patients}</b><br/>
Production: <b>$${item.total_production}</b><br/>`)
.addTo(this.mapInstance).on('click', this.circleClick);
Icon marker is added in the following method:
// create marker object, pass custom icon as option, pass content and options to popup, add to map
// create marker object, pass custom icon as option, pass content and options to popup, add to map
L.marker([item.latitude, item.longitude], { icon: chartIcon })
.bindTooltip(`Address: <b>${item.address}</b><br/>
Patients from this address: <b>${item.total_patients}</b><br/>
Production: <b>$${item.total_production}</b><br/>`)
.addTo(this.mapInstance).on('click', this.circleClick);
On clearing map Icon marker is not removed
Map clearing function:
if (this.mapInstance) {
for (let i in this.mapInstance._layers) {
if (this.mapInstance._layers[i]._path !== undefined) {
try {
this.mapInstance.removeLayer(this.mapInstance._layers[i]);
} catch (e) {
console.log('problem with ' + e + this.mapInstance._layers[i]);
}
}
}
}
You're checking for a _path property before removing. It will skip the L.Marker layers because they don't have a _path property, only vector layers (extended from L.Path) do.
If you ever need to delete only certain layer types from your map you are best off by grouping them in a grouping layer like L.LayerGroup or L.FeatureGroup and then using their clearLayers method:
var layerGroup = new L.LayerGroup([
new L.Marker(...),
new L.CircleMarker()
]).addTo(map);
layerGroup.clearLayers();
If that is not an option you could iterate the map's layers and then check the instance of the layer:
function customClearLayers () {
map.eachLayer(function (layer) {
if (layer instanceof L.Marker || layer instanceof L.CircleMarker) {
map.removeLayer(layer);
}
});
}

Update Leaflet GeoJson layer and maintain selected feature popup

I have a leaflet map which I am refreshing with new data from the server. You can click on the map feature and a popup will show for the point. Every time the refresh happens, I remove the layer using map.removeLayer, add the new data using L.geoJson, but the popup goes away. I want the popup to stay active with the new data. I know this probably won't work the way I'm doing it by removing the layer. Is there another way to do this that will refresh the layer data and maintain the selected feature popup?
This is the refresh function that I call after I get the new data from the server.
function refreshMapLocations() {
map.removeLayer(locationLayer);
locationLayer = L.geoJson(locations, {
onEachFeature: onEachFeature
}).addTo(map);
}
This creates the popup for each feature
function onEachFeature(feature, layer) {
if (feature.properties && feature.properties.UserName) {
layer.bindPopup(feature.properties.UserName);
}
}
This worked, I keep track of an id that I set in the popup content. After the layer is added I store the Popup that has the clickedId and do popupOpen on that.
var popupToOpen;
var clickedId;
function onEachFeature(feature, layer) {
if (feature.properties && feature.properties.UserName) {
if (feature.properties.MarkerId == clickedId) {
layer.bindPopup("div id='markerid'>"+feature.properties.MarkerId+"</div>feature.properties.UserName);
} else {
layer.bindPopup("div id='markerid'>"+feature.properties.MarkerId+"</div>feature.properties.UserName);
}
}
}
function refreshMapLocations() {
map.removeLayer(locationLayer);
locationLayer = L.geoJson(locations, {
onEachFeature: onEachFeature
}).addTo(map);
if (popupToOpen != null) {
popupToOpen.openPopup();
}
}
function initMap() {
...
map.on('popupopen', function (e) {
...
//clickedId = id from event popup
}
}