How to check Mapbox GL JS Draw state - mapbox-gl-js

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

Related

checking duplicate markers leaflet geoman

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/

Leaflet: Layer check box state is reset every time the map moves or zooms

I have the following code which fetches some remote GeoJSON from an API and displays the results on a Leaflet map:
<script>
// Center the map
var map = L.map('map').setView([54.233669, -4.406027], 6);
// Attribution
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=REMOVED', {
attribution: 'Map © OpenStreetMap',
id: 'mapbox.streets'
}).addTo(map);
// Create an empty layergroup for the data
var LayerUTMGroundHazards = L.layerGroup();
var LayerUTMAirspace = L.layerGroup();
// Style the features
function setStyle(feature) {
return {
fillColor: feature.properties.fillColor,
color: feature.properties.strokeColor,
fillOpacity: feature.properties.fillOpacity,
opacity: feature.properties.strokeOpacity
};
}
// Build Guardian UTM
function getGuardianUTMdata() {
// Clear the current Layer content
LayerUTMGroundHazards.clearLayers();
LayerUTMAirspace.clearLayers();
// Define what we want to include
function FuncGroundHazards(feature) {
if (feature.properties.category === "groundHazard") return true
}
function FuncAirspace(feature) {
if (
(feature.properties.category === "airspace" || feature.properties.category === "airport")
&& feature.properties.detailedCategory !== "uk:frz"
) return true
}
// Build the layers
fetch("https://example.com/json?n=" + map.getBounds().getNorth() + "&e=" + map.getBounds().getEast() + "&s=" + map.getBounds().getSouth() + "&w=" + map.getBounds().getWest(), { headers: { 'Authorization': 'REMOVED', 'X-AA-DeviceId': 'mySite' } })
.then(function (responseGuardianUTM) { return responseGuardianUTM.json() })
.then(function (dataGuardianUTM) {
// Create Layer: Ground Hazards
var featuresUTMGroundHazards = L.geoJson(dataGuardianUTM, {
filter: FuncGroundHazards,
style: setStyle,
pointToLayer: function (feature, latlng) { return L.marker(latlng, { icon: L.icon({ iconUrl: feature.properties.iconUrl, iconSize: [25, 25], }), }) },
onEachFeature: function (feature, layer) { layer.bindPopup(feature.properties.name); },
});
// Add the L.GeoJSON instance to the empty layergroup
LayerUTMGroundHazards.addLayer(featuresUTMGroundHazards).addTo(map);
});
// other layers are here (removed from this example)
}
// Update the Guardian UTM layer if the map moves
map.on('dragend', function () { getGuardianUTMdata(); });
map.on('zoomend', function () { getGuardianUTMdata(); });
// Layer controls
var layerControl = new L.Control.Layers(null, {
'Airspace Restrictions': LayerUTMAirspace,
'Ground Hazards': LayerUTMGroundHazards
// other layers are here (removed from this example)
}).addTo(map);
</script>
The problem is that every time the map is moved or zoomed, all of the Layer checkboxes are reset to Checked again, regardless of how many were checked before the map moved. They do not honour / remember their state when the map moves.
Given my code above, how can I store or preserve the checkbox state for each of the multiple Layers that I have so they are not reset every time the map is moved?
EDIT:
Here is a working fiddle. Remove the checkbox from the 'Ground Hazards', then move or zoom the map, you will see how it puts a tick back in the box again
https://jsfiddle.net/hdwz1b6t/1/
You're (re-)adding LayerUTMGroundHazards every time. This line here...
// Add the L.GeoJSON instance to the empty layergroup
LayerUTMGroundHazards.addLayer(featuresUTMGroundHazards).addTo(map);
...is not only adding featureUTMGroundHazards to layerUTMGroundHazards, it's also (re-)adding layerUTMGroundHazards to the map.
And quoting from https://leafletjs.com/examples/layers-control/ :
The layers control is smart enough to detect what layers we’ve already added and have corresponding checkboxes and radioboxes set.
So when you do LayerUTMGroundHazards.addTo(map);, the checkboxes reset.

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

Editing/Removing GeoJSON layers from a featureGroup in Mapbox using Leaflet

I'm using Mapbox with Leaflet for drawing, editing and removing polygons etc. Every time I create a new polygon, I convert them to a GeoJSON layer and then add it to the featureGroup that I created, because I want to associate each layer with an ID property that I can use later. This is what I have:
var featureGroup = L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup
},
draw: {
polygon: {
allowIntersection: false
},
polyline: false,
rectangle: false,
circle: false,
marker: false
}
}).addTo(map);
map.on('draw:created', addPolygon);
map.on('draw:edited', editPolygon);
map.on('draw:deleted', deletePolygon);
function deletePolygon(e) {
featureGroup.removeLayer(e.layer);
}
function editPolygon(e) {
featureGroup.eachLayer(function (layer) {
layer.eachLayer(function (layer) {
addPolygon({ layer: layer });
});
});
}
function addPolygon(e) {
var geojsonstring = e.layer.toGeoJSON();
var geojson = L.geoJson(geojsonstring);
featureGroup.addLayer(geojson);
}
When I do this, creating polygons is not a problem. But when I try to edit or delete polygons, it doesn't work properly.
When I try to edit a polygon, it tells me "TypeError: i.Editing is undefined". It doesn't allow me to cancel editing as well.
When I try to delete a polygon, it is not displayed in the map anymore, but it is still not removed from the featureGroup.
What am I doing wrong here?
Edit: The way I'm currently doing this is the same way that ghybs has suggested. But the problem is, once all the edits are done, the polygons are saved to a database (I convert it to a WKT string to save in SQLServer). And when the page is loaded the next time, the polygons are loaded back from the database, and the user can edit or delete them and save it back to the database.
As it is right now, when the user makes the edit and saves them again, it only creates duplicate polygons. and I don't know of any way to connect the edited polygons to the ones from the database.
So I thought if I could convert them to GeoJSON and assign an ID property to each layer (something like ID=0 if it is a new layer, and the corresponding polygonID from the database if it is loaded from the database). So that when they are saved again, I can check this ID and based on that, I can either update the available polygon, or create a new polygon in the database.
Is there a better way of doing this?
Not sure exactly why in addPolygon you go through a GeoJSON object that you convert back into a Leaflet layer group through L.geoJson?
You could have directly added the created layer, as in Leaflet.draw "draw:created" example:
function addPolygon(e) {
var layer = e.layer;
var feature = layer.feature = layer.feature || {}; // Initialize layer.feature
// use the feature.id: http://geojson.org/geojson-spec.html#feature-objects
feature.id = 0; // you can change it with your DB id once created.
featureGroup.addLayer(layer);
// record into DB, assuming it returns a Promise / Deferred.
recordToDb(layer.toGeoJSON()).done(function (result) {
feature.id = result.id; // Update the layer id.
});
}
As for the reason for you error, it is due to the fact that you add a (GeoJSON) Layer Group to your featureGroup, which Leaflet.draw plugin does not know how to handle. You must add only "non group" layers.
See also: https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet

Leaflet: Removing markers from map

I load some lat / lon info, then use it to build a polyline.
I then want to add a marker at each of the polyline vertices that will show when the polyline is clicked.
The vertices should disappear if a different (or the same) polyline is clicked.
The code below creates the polyline and the vertex markers.
But the vertex markers do not ever disappear.
I've tried to do this several ways with the same result. I've tried storing the vertex markers in an array and adding them directly to the map, then map.removeLayer'ing them. That doesn't work either. Nor does it work if I use an L.featureGroup instead of a layerGroup.
Clearly I've missed the point somewhere as to how markers can be removed. Could someone point me at the error in my methodology?
// trackMarkersVisible is a global L.layerGroup
// success is a callback from an ajax that fetches trackData, an array f lat/lon pairs
success: function (trackData) {
// build an array of latLng's
points = buildTrackPointSet(trackData, marker.deviceID);
var newTrack = L.polyline(
points, {
color: colors[colorIndex],
weight: 6,
clickable: true
}
);
$(newTrack).on("click", function () {
trackMarkersVisible.clearLayers();
$.each(points, function(idx, val) {
var tm = new L.Marker(val);
trackMarkersVisible.addLayer(tm);
});
map.addLayer(trackMarkersVisible);
});
}
Without a JSFiddle or Plunker it's hard to say because i'm not sure what behaviour your getting but using the clearLayers() method of L.LayerGroup should remove all layers from that group. I would check in the onclick handler if the group already has layers: group.getLayers().length === 0 If the group is empty, add the markers, if the group has layers use clearLayers. Example in code:
polyline.on('click', function (e) {
if (group.getLayers().length === 0) {
e.target._latlngs.forEach(function (latlng) {
group.addLayer(L.marker(latlng));
});
} else {
group.clearLayers();
}
});
This works for me, see the example on Plunker: http://plnkr.co/edit/7IPHrO?p=preview
FYI: an instance of L.Polyline is always clickable by default so you can leave out the clickable: true