Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I grab a selection of markers with Leaflet.draw?

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

});
like image 279
roberrrt-s Avatar asked May 30 '17 11:05

roberrrt-s


People also ask

How do you show markers in leaflet?

Adding a Simple Marker Step 1 − Create a Map object by passing a <div> element (String or object) and map options (optional). Step 2 − Create a Layer object by passing the URL of the desired tile. Step 3 − Add the layer object to the map using the addLayer() method of the Map class.

How many markers can leaflet handle?

The Clusterer can handle 10,000 or even 50,000 markers (in chrome).

How do you remove a marker in leaflet?

You just need to filter the clicked marker' coords not to be included anymore to your state variable and that's it!

How do I make a Poplet in leaflet?

Use the addPopups() function to add standalone popup to the map. A common use for popups is to have them appear when markers or shapes are clicked. Marker and shape functions in the Leaflet package take a popup argument, where you can pass in HTML to easily attach a simple popup.


2 Answers

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/[email protected]/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/[email protected]/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>
like image 163
iH8 Avatar answered Oct 06 '22 00:10

iH8


Thanks @iH8 for the cool example. I went further to avoid some repetition with

markers.eachLayer(function (marker) {
    ...
}

and extended the wrappers with additionnal methods using arrays of markers instead:


First I noticed that a LayerGroup has an object with key-values containing all the markers. I simply use that object to create an array of markers :

// In the on draw event
...
// Set an array containing all the markers
var markers = jsonToArray(layerGroup._layers); 
...

function jsonToArray(jsonObject) {
  var result = [];
  var keys = Object.keys(jsonObject);
  keys.forEach(function (key) {
    result.push(jsonObject[key]);
  });
  return result;
}

I then re-use the wrappers with modified contains() methods :

  L.Rectangle.include({
    // Single marker case
    contains: function (marker) {
      return this.getBounds().contains(marker.getLatLng());
    },
    // Array of markers
    contains: function (markers) {
      var markersContained = [];
      markers.forEach(marker => {
        markersContained.push(this.getBounds().contains(marker.getLatLng()));
      })
      return markersContained;
    }
  });

  L.Circle.include({
    contains: function (marker) {
      return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
    },
    contains: function (markers) {
      var markersContained = [];
      markers.forEach(marker => {
        markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
      })
      return markersContained;
    }
  });

and finally on the draw event, I check whether my markers are contained within or not :

  map.on(L.Draw.Event.CREATED, function (geometry) {
    // Set an array containing all the markers
    var markers = jsonToArray(layerGroup._layers);

    var result = geometry.layer.contains(markers);
    console.log('result => ', result);
  });

function jsonToArray(jsonObject) {
  var result = [];
  var keys = Object.keys(jsonObject);
  keys.forEach(function (key) {
    result.push(jsonObject[key]);
  });
  return result;
}

var map = new L.Map('leaflet', {
    'center': [0, 0],
    'zoom': 0
});

var layerGroup = new L.LayerGroup().addTo(map);

for (var i = 0; i < 10; i++) {
    var marker = new L.Marker([
        (Math.random() * (90 - -90) + -90).toFixed(5) * 1,
        (Math.random() * (180 - -180) + -180).toFixed(5) * 1
    ]).addTo(layerGroup);
}

new L.Control.Draw({
    draw: {
        marker   : false,
        polygon  : false,
        polyline : false,
        rectangle: true,
        circle   : {
            metric: 'metric'
        }
    },
    edit: false
}).addTo(map);

// Define contains() method for each geometry
L.Rectangle.include({
  contains: function (marker) {
    return this.getBounds().contains(marker.getLatLng());
  },
  contains: function (markers) {
    var markersContained = [];
    markers.forEach(marker => {
      markersContained.push(this.getBounds().contains(marker.getLatLng()));
    })
    return markersContained;
  }
});

L.Circle.include({
  contains: function (marker) {
    return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
  },
  contains: function (markers) {
    var markersContained = [];
    markers.forEach(marker => {
      markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
    })
    return markersContained;
  }
});

map.on(L.Draw.Event.CREATED, function (geometry) {
  // Set an array containing all the markers
  var markers = jsonToArray(layerGroup._layers);

  var result = geometry.layer.contains(markers);
  console.log('result => ', result);
});
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/[email protected]/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/[email protected]/dist/leaflet.js"></script>
    <script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script>
  </body>
</html>
like image 44
Alex Beugnet Avatar answered Oct 06 '22 00:10

Alex Beugnet