How to get all markers on Leaflet - 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.

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: how many tiles are loaded when map is loading for the first time?

I integrated Leaflet OSM (Open Street Maps) to my application and the map view is loading.
this.map = leaflet.map(this.mapContainer.nativeElement, {
preferCanvas: true,
zoom: 14,
zoomAnimationThreshold: 0,
renderer: leaflet.canvas()
});
I want the count of tiles loaded when map is loaded.
I checked official docs of leaflet but didn't find regarding the count.
do I need to calculate the count of tiles loaded according to screen? if yes then how?
You can try this:
function getTilesCount(){
var tileLayers = map.getPane('tilePane').children[0].children;
var idx = -1;
var tileCount = 0;
for(var i = 0; i < tileLayers.length; i++){
var tile = tileLayers[i];
if(tile.style.zIndex >= idx){
idx = tile.style.zIndex;
tileCount = tile.children.length;
}
}
return tileCount;
}
It counts the DOM tiles in tile container with the highest z-Index
Leverage the tileload event of L.TileLayers, e.g.
var tileCount = 0;
myTileLayer.on('tileload', function() { tileCount++ });
If you want to count tiles on initial map load, you should make sure to hook an event handler for the corresponding tileload event before the tilelayer starts loading, and stop counting when the tilelayer reports all tiles loaded (via its load event), e.g.:
var map = L.map('leaflet', {
center: [0, 0],
zoom: 3
});
// Create a tilelayer but *do not* add it to the map *yet*
var myTileLayer =
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © OpenStreetMap contributors'
});
var tileCount = 0;
// Hook up event handlers...
myTileLayer.on('tileload', function(){ tileCount++; });
myTileLayer.once('load', function(){
// Do something with the number of loaded tiles
window.alert(tileCount);
// Detach all event handlers for the 'tileload' event since they're not needed anymore
myTileLayer.off('tileload');
});
// Now that the event handlers are ready, add the tilelayer to the map
myTileLayer.addTo(map);

Leaflet draw loses shapes after drawing

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

Leaflet Clustering with multiple layers (use MarkerCluster.LayerSupport?)

Alright, so I've tried and failed a number of times and dug through SE hoping to figure out my problems.
I've been basing a lot of my work off this SE post: link.
I have been unable to make this work, mainly because of two errors, but the first one being an obvious hurdle to overcome first:
Error 1:
Uncaught SyntaxError: Unexpected token <
Error 2:
Uncaught TypeError: L.markerClusterGroup.layerSupport is not a function
So, I would like clustering to work with any layer that is turned on via the L.control.layers() function.
Here is my page, as it sits now: TN Alcohol Map
And the code, sans headers/misc:
// initialize the map
var map = L.map('map').setView([36.17, -86.78], 7);
// load a tile layer/base map
L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png',
{
attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
maxZoom: 18,
minZoom: 7
}).addTo(map);
//attributes for basemap credit (lower right hand corner annotation)
var streetsAttr = 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap';
var aerialAttr = 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
var artsyfartsyAttr = 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap';
//crete variables for the base map layer switcher
var streets = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', {id: 'MapID', attribution: streetsAttr}),
aerial = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {id: 'MapID', attribution: aerialAttr}),
artsyfartsy = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png', {id: 'MapID', attribution: artsyfartsyAttr});
//create baseMaps variable to store basemap layer switcher
var baseMaps = {
"Streets": streets,
"Aerial": aerial,
"ArtsyFartsy": artsyfartsy
};
// BEER icon & load beer geojson
var beerIcon = L.icon({
iconUrl: 'glass.png',
iconSize: [24, 48]
});
var beerMarker = L.geoJson(false, {
pointToLayer: function(feature, latlng) {
var marker = L.marker(latlng, {
icon: beerIcon
});
//popup shows NAME, ADDRESS, URL and opens the URL in a new window/tab
marker.bindPopup("<strong>" + feature.properties.NAME + "</strong><br/>" + feature.properties.STREETNUM + " " + feature.properties.STREET + ", " + feature.properties.CITY + "<br/>" + "<a target = _blank href=" +
feature.properties.URL + ">" + feature.properties.URLDISPLAY + "</a>");
return marker;
}
}).addTo(map);
$.getJSON("breweries.geojson", function(data) {
beerMarker.addData(data);
});
// WINE icon & load wine geojson
var wineIcon = L.icon({
iconUrl: 'wine.png',
iconSize: [48, 48]
});
var wineMarker = L.geoJson(false, {
pointToLayer: function(feature, latlng) {
var marker = L.marker(latlng, {
icon: wineIcon
});
//popup shows NAME, ADDRESS, URL and opens the URL in a new window/tab
marker.bindPopup("<strong>" + feature.properties.NAME + "</strong><br/>" + feature.properties.STREETNUM + " " + feature.properties.STREET + ", " + feature.properties.CITY + "<br/>" + "<a target = _blank href=" +
feature.properties.URL + ">" + feature.properties.URLDISPLAY + "</a>");
return marker;
}
}).addTo(map);
$.getJSON("wine.geojson", function(data) {
wineMarker.addData(data);
});
//Define overlay maps (non-base layer maps)
var overlayMaps = {
"Breweries": beerMarker,
"Wineries": wineMarker
};
//Creates a Marker Cluster Group
var mcg = L.markerClusterGroup.layerSupport().addTo(map);
//Checking in the 'sub groups'
mcg.checkIn([
beerMarker,
wineMarker
]);
//baseMaps layer switcher
L.control.layers(baseMaps, overlayMaps).addTo(map);
As said by nathansnider in the question comment, the content of your leaflet.markercluster.layersupport-src file is not the JavaScript code of the markerCluster.LayerSupport plugin, but the GitHub HTML page that displays the code of that file, i.e. surrounded by plenty HTML code.
You should simply replace the content of your file by the content of the raw file here: https://raw.githubusercontent.com/ghybs/Leaflet.MarkerCluster.LayerSupport/master/leaflet.markercluster.layersupport-src.js
Demo: http://plnkr.co/edit/Jd8skZ1U0bWxgl2orJV6?p=preview
Side note:
If you just need the Layers Control to work with Leaflet.markercluster, there is another plugin that does just that and which is much simpler: Leaflet.FeatureGroup.SubGroup (230 lines of code v.s. 600 lines for Leaflet.MarkerCluster.LayerSupport as of today).
In your case you would use it this way:
// Create a normal Marker Cluster Group.
var mcg = L.markerClusterGroup().addTo(map);
// Create SubGroups.
var beerMarkerSub = L.featureGroup.subGroup(mcg).addTo(map);
var wineMarkerSub = L.featureGroup.subGroup(mcg).addTo(map);
// For Layers Control.
var overlayMaps = {
"Breweries": beerMarkerSub,
"Wineries": wineMarkerSub
};
// That is it! No need to check-in.
Code specific for your application, since you load GeoJSON data by AJAX:
var beerMarker = L.geoJson(null, beerOptions); // DO NOT add to map.
var wineMarker = L.geoJson(null, wineOptions); // Same story.
$.getJSON("breweries.geojson", function(data) {
beerMarker.addData(data); // GeoJSON conversion.
// Then transfer all features into the corresponding sub-group.
beerMarker.eachLayer(function (layer) {
layer.addTo(beerMarkerSub);
});
});
$.getJSON("wine.geojson", function(data) {
wineMarker.addData(data); // GeoJSON conversion.
// Then transfer all features into the corresponding sub-group.
wineMarker.eachLayer(function (layer) {
layer.addTo(wineMarkerSub);
});
});
Demo: http://plnkr.co/edit/x8vTLjE53TPiLd52BQ1d?p=preview
Disclosure: I am the author of these plugins.

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