Context:
I’ve made a map, and populated it with around 300 random markers. I can ‘select’ the markers by clicking on a link in the popup and activate a selection to display data from. I also have the Leaflet.draw plugin to draw shapes like circles, rectangles and custom shapes, and I would like to use it to ‘select’ a couple of markers.
The issue
How can I grab the leaflet marker object of the markers that fall inside a drawn leaflet.draw shape so I can edit them? I cannot seem to make a selection, It either selects none of the markers, or all of them.
Code snippet, stripped from unnecessary code:
const drawControl = new L.Control.Draw({ draw: { marker : false, polygon : true, polyline : false, rectangle: true, circle : { metric: 'metric' } }, edit: false }); const map = L.map('map', { layers: [streets, light] }).setView([CONFIG.MAP.LATITUDE, CONFIG.MAP.LONGITUDE], CONFIG.MAP.ZOOMLEVEL) map.addControl(drawControl); map.on(L.Draw.Event.DRAWSTOP, e => { const hello = e.target; console.log(hello); e.target.eachLayer(layer => { if (layer.options.icon) { console.log(layer); } }); });
Advertisement
Answer
Most of what you want can quite easily be done using Leaflet’s utility methods. If you want to do this with a complex shape like L.Polygon
you’re going to need something like TurfJS
For L.Circle
you need to calculate the distance between the circle’s center and compare it to the radius:
var marker = new L.Marker(...), circle = new L.Circle(...); var contains = circle.getLatLng().distanceTo(marker.getLatLng()) < circle.getRadius();
For L.Rectangle
you need to fetch it’s bounds object and use the contains method:
var marker = new L.Marker(...), rectangle = new L.Rectangle(...); var contains = rectangle.getBounds().contains(marker.getLatLng());
As said for complex polygons i’de use Turf but there are more libraries and plugins out there. Here’s an example using Turf’s inside
method. It take a GeoJSON point and polygon feature as parameters so mind the conversion:
var marker = new L.Marker(...), polygon = new L.Polygon(...); var contains = turf.inside(marker.toGeoJSON(), polygon.toGeoJSON());
You could wrap those into convenience methods for each respective class:
L.Polygon.include({ contains: function (latLng) { return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON()); } }); L.Rectangle.include({ contains: function (latLng) { return this.getBounds().contains(latLng); } }); L.Circle.include({ contains: function (latLng) { return this.getLatLng().distanceTo(latLng) < this.getRadius(); } }); var marker = new L.Marker(...), polygon = new L.Polygon(...), rectangle = new L.Rectangle(...), circle = new L.Circle(...); polygon.contains(marker.getLatLng()); rectangle.contains(marker.getLatLng()); circle.contains(marker.getLatLng());
Note that if you implement the polygon method that there is no need for the rectangle method. Since rectangle is extended from polygon it will inherit the method. I left it in there to be complete.
Now iterating your markers and comparing them is easy:
map.on(L.Draw.Event.CREATED, function (e) { markers.eachLayer(function (marker) { if (!e.layer.contains(marker.getLatLng())) { marker.remove(); } }); });
Hope that helps, here’s a working snippet:
var map = new L.Map('leaflet', { 'center': [0, 0], 'zoom': 0 }); var markers = new L.LayerGroup().addTo(map); for (var i = 0; i < 300; i++) { var marker = new L.Marker([ (Math.random() * (90 - -90) + -90).toFixed(5) * 1, (Math.random() * (180 - -180) + -180).toFixed(5) * 1 ]).addTo(markers); } new L.Control.Draw({ draw: { marker : false, polygon : true, polyline : false, rectangle: true, circle : { metric: 'metric' } }, edit: false }).addTo(map); L.Polygon.include({ contains: function (latLng) { return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON()); } }); L.Rectangle.include({ contains: function (latLng) { return this.getBounds().contains(latLng); } }); L.Circle.include({ contains: function (latLng) { return this.getLatLng().distanceTo(latLng) < this.getRadius(); } }); map.on(L.Draw.Event.CREATED, function (e) { markers.eachLayer(function (marker) { if (!e.layer.contains(marker.getLatLng())) { marker.remove(); } }); });
body { margin: 0; } html, body, #leaflet { height: 100%; }
<!DOCTYPE html> <html> <head> <title>Leaflet 1.0.3</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link type="text/css" rel="stylesheet" href="//unpkg.com/leaflet@1.0.3/dist/leaflet.css" /> <link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.css" /> </head> <body> <div id="leaflet"></div> <script type="application/javascript" src="//unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script> <script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script> <script type="application/javascript" src="//unpkg.com/@turf/turf@latest/turf.min.js"></script> </body> </html>