Leaflet draw loses shapes after drawing - leaflet

I'm using leaflet to show a map and the leaflet-draw plugin to draw shapes on this map.
I have the code below (see plunker) which allows to draw shapes. But as soon the shape is finished, it disapears.
What is missing to make the shapes visible on the map after drawing?
var mymap = L.map('mapid').setView([47.2090048, 7.7746663], 15);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZXJuc3RwIiwiYSI6ImNpcGdtMTUzOTAwMGV2Ymt3Z2JlYnMyejgifQ.gHxSIfBUM0-UiuWurWoEvQ', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(mymap);
var marker = L.marker([47.2090048, 7.7747663]).addTo(mymap);
// Initialise the FeatureGroup to store editable layers
var drawnItems = new L.FeatureGroup();
mymap.addLayer(drawnItems);
// Initialise the draw control and pass it the FeatureGroup of editable layers
var drawControl = new L.Control.Draw({
edit: {
featureGroup: drawnItems
}
});
mymap.addControl(drawControl);

You've successfully drawn the shapes, but what's missing here is you haven't shown the drawn shape on top of your map layer, despite of what you're trying to achieve is to display it.
What you've got to do is just to add the drawn layer on top of your map.
E.g.
mymap.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'marker') {
// Do marker specific actions
}
// Do whatever else you need to. (save to db, add to map etc)
mymap.addLayer(layer);
});
You can add this code to the end of your js. This is taken from the docs

You need to listen for the draw:created event and add the layer to the L.FeatureGroup in the event listener:
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'marker') {
// Do marker specific actions
}
// Do whatever else you need to. (save to db, add to map etc)
drawnItems.addLayer(layer);
});

Related

Leaflet current position multiple markers

Hello everyone I have some problems its about the current positionmarker in my leaflet its supposed to update every 3 second and it does but it everytime it puts a new "position" marker on the map and the old one stays how can i fix this?
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: '© Leaflet 2021',
tileSize: 512,
zoomOffset: -1,
id: 'mapbox/streets-v11',
accessToken: '######'
}).addTo(map);
var greenIcon = L.icon({
iconUrl: 'person.png',
iconSize: [35, 35], // size of the icon // the same for the shadow
popupAnchor: [0, -20] // point from which the popup should open relative to the iconAnchor
});
// placeholders for the L.marker and L.circle representing user's current position and accuracy
var current_position, current_accuracy;
function onLocationFound(e) {
var radius = e.accuracy / 2;
var marker;
L.marker(e.latlng, {icon: greenIcon}).addTo(map)
}
// wrap map.locate in a function
function locate() {
map.locate({setView: true, maxZoom: 15});
}
map.on('locationfound', onLocationFound);
// call locate every 3 seconds... forever
setInterval(locate, 3000);
An efficient way to fix this is to keep a reference to the marker you create, so that you can update its position rather than creating a new marker each time you get a location update. The reference needs to be held in a variable that is outside your callback function, but in scope when the callback is created.
For instance, your callback can check whether the marker already exists, and either create it and attach it to the map object for easy re-use, or just update its coordinates if it is already there:
function onLocationFound(e) {
var radius = e.accuracy / 2;
if (map._here_marker) {
// Update the marker if it already exists.
map._here_marker.setLatLng(e.latlng);
} else {
// Create a new marker and add it to the map
map._here_marker = L.marker(e.latlng, {icon: greenIcon}).addTo(map);
}
}
Having this reference will also let you edit the marker from other functions, e.g. to change the icon or popup, hide it from view, etc.
You can do it, for example, in the following way.
Add an ID (customId) to the marker:
const marker = L.marker([lng, lat], {
id: customId
});
And when you add a new marker remove the existing one with the code below:
map.eachLayer(function(layer) {
if (layer.options && layer.options.pane === "markerPane") {
if (layer.options.id !== customId) {
map.removeLayer(layer);
}
}
});

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.

How to get all markers on Leaflet

I have a listener that will detect changes of the location of objects on databases. It will pass all the information of the object that is being changed.
I want to get all markers from the current map and find the marker that is affected. Once found, update the location.
But, I am still looking for the best ways to get all markers from the map and then I can update the location.
var map = L.map('map').setView([37.78541,-122.40787], 13);
var markers = new L.FeatureGroup();
var mapLink =
'OpenStreetMap';
L.tileLayer(
'https://{s}.tiles.mapbox.com/v4/examples.map-i87786ca/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ2Vja29iIiwiYSI6IndzVjRGN0kifQ.lToORsjE0lkt-VQ2SXOb-Q', {
attribution: '© ' + mapLink + ' Contributors',
maxZoom: 18,
}).addTo(map);
var marker = createCircleMarker([44.977368, -93.232659]);
marker._id = "69"; // Id of the marker
map.addLayer(marker);
var socket = io();
socket.on('update location', function(obj) {
// Get all markers and find markers with attribute obj.name to
// update the location to [obj.lat,obj.lon]
});
Use eachLayer method on L.map. Like
map.eachLayer(function (layer) {
if (layer.options.name === 'XXXXX') {
layer.setLatLng([newLat,newLon])
}
});
Documentation at http://leafletjs.com/reference-1.2.0.html#map-eachlayer
To add an option without using map.eachLayer; all layers within the map are internally stored in map._layers.
Use
map._layers.forEach(function (layer) {
...
});
to iterate over ALL Layer elements. Not just the ones currently visible.

style a geojson point like a POI with leaflet/mapbox

I am using mapbox.js to render a mapbox map. I am trying to load geojson from my server that contain either a country polygon or a city coordinates (lon,lat).
I have been able to style the country polygons but not the city points/markers.
I am not able to modify the geojson to use mapbox simplestyle
Here is the code executed when the page loads (I changed the mapbox map ID):
var southWest = L.latLng(-90, -360), northEast = L.latLng(90, 360);
var bounds = L.latLngBounds(southWest, northEast);
var map = L.mapbox.map('map', 'MapboxMapID', { zoomControl: false, infoControl: true, detectRetina: true, maxBounds: bounds, minZoom: 2, legendControl: {position: 'topright'}});
new L.Control.Zoom({ position: 'bottomright' }).addTo(map);
map.fitBounds(bounds);
var locationsGroup = L.featureGroup().addTo(map);
and then when the user selects a country or city with a selectbox:
$("#select2-search-up").on("change", function (e) {
if (e.added) {
var location = L.mapbox.featureLayer().loadURL('/citiesCountriesID/' + e.added.id).on('ready', function(featLayer) {
this.eachLayer(function(polygon) {
polygon.setStyle({
stroke:false, fillColor:'red', fillOpacity:0.2
});
});
});
locationsGroup.addLayer(location);
} else {
locationsGroup.eachLayer(function (layer) {
if (layer._geojson[0]._id == e.removed.id) {
locationsGroup.removeLayer(layer);
}
});
}
});
Ideally I would like to display a different icon that the standard marker, but I could do with a small red square
Thank you for your inputs
In this example I did some circle markers but I'm pretty sure you can do other basic svg shps or your own .png pretty easy. http://bl.ocks.org/mpmckenna8/db2eef40314fe24e9177
This example from Mapbox also shows how to use a icon from their icon Library which has a lot of choices also. https://www.mapbox.com/mapbox.js/example/v1.0.0/l-mapbox-marker/
It might also help to see some of your geojson structure to see why it can't use simplestyle
In my bl.ocks example I loaded each of the geojson datasets separately
var begin = L.geoJson(start,{
pointToLayer:function(feature, latlng){
return L.circleMarker(latlng,{
radius:9,
fillColor: "orange",
fillOpacity:.7
})
}
})
Is how I set up my circles and I set a different L.geoJson to the other data which I left as the default markers.

How do I get the latlng after the dragend event in leaflet?

I'm trying to update the lat/lng value of a marker after it is moved. The example provided uses a popup window to display the lat/lng.
I have a "dragend" event listener for the marker, but when I alert the value of e.latlng it returns undefined.
javascript:
function markerDrag(e){
alert("You dragged to: " + e.latlng);
}
function initialize() {
// Initialize the map
var map = L.map('map').setView([38.487, -75.641], 8);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA',
maxZoom: 18
}).addTo(map);
// Event Handlers
map.on('click', function(e){
var marker = new L.Marker(e.latlng, {draggable:true});
marker.bindPopup("<strong>"+e.latlng+"</strong>").addTo(map);
marker.on('dragend', markerDrag);
});
}
$(document).ready(initialize());
http://jsfiddle.net/rhewitt/Msrpq/4/
Use e.target.getLatLng() to get the latlng of the updated position.
// Script for adding marker on map click
function onMapClick(e) {
var marker = L.marker(e.latlng, {
draggable:true,
title:"Resource location",
alt:"Resource Location",
riseOnHover:true
}).addTo(map)
.bindPopup(e.latlng.toString()).openPopup();
// #12 : Update marker popup content on changing it's position
marker.on("dragend",function(e){
var chagedPos = e.target.getLatLng();
this.bindPopup(chagedPos.toString()).openPopup();
});
}
JSFiddle demo
latlng value is not in e.latlng but in e.target._latlng .
Use console.
While using e.target._latlng works (as proposed by this other answer), it's better practice to use
e.target.getLatLng();
That way we're not exposing any private variables, as is recommended by Leaflet:
Private properties and methods start with an underscore (_). This doesn’t make them private, just recommends developers not to use them directly.
I think the API changed.
Nowadays is: const { lat, lng } = e.target.getCenter();
if anyone is using react than you should use :
const map = useMap()
map.addEventListener("dragend" , ()=> {
const {lat , lng} = map.getCenter()
})