Let's say I have some overlapping layers and each layer has a click event. When I click on the map, I'd like to know which layers are clicked on, though the click event stops after the first layer and does not propagate to its underlying layers. How can I achieve this?
Here's a sample fiddle and its code: /
<div id="mapid" style="width: 600px; height: 400px;"></div>
<script>
var mymap = L.map('mapid').setView([51.505, -0.09], 13);
L.tileLayer('/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: 'Map data © <a href="">OpenStreetMap</a> contributors, ' +
'<a href=".0/">CC-BY-SA</a>, ' +
'Imagery © <a href="">Mapbox</a>',
id: 'mapbox.streets'
}).addTo(mymap);
L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 1st polygon')
});
L.polygon([
[51.609, -0.1],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 2nd polygon')
});
</script>
If you click on each polygon, you see its related message. If you click on the overlapping part, you only see the message for the second polygon.
Let's say I have some overlapping layers and each layer has a click event. When I click on the map, I'd like to know which layers are clicked on, though the click event stops after the first layer and does not propagate to its underlying layers. How can I achieve this?
Here's a sample fiddle and its code: https://jsfiddle.net/r0r0xLoc/
<div id="mapid" style="width: 600px; height: 400px;"></div>
<script>
var mymap = L.map('mapid').setView([51.505, -0.09], 13);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
'Imagery © <a href="http://mapbox.com">Mapbox</a>',
id: 'mapbox.streets'
}).addTo(mymap);
L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 1st polygon')
});
L.polygon([
[51.609, -0.1],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(mymap).on('click', function() {
console.log('clicked on 2nd polygon')
});
</script>
If you click on each polygon, you see its related message. If you click on the overlapping part, you only see the message for the second polygon.
Share Improve this question edited May 27, 2019 at 1:25 thor 22.5k31 gold badges102 silver badges190 bronze badges asked Mar 24, 2017 at 18:46 kavehkaveh 2,1461 gold badge22 silver badges37 bronze badges5 Answers
Reset to default 11You have to listen directly to the map "click"
event and to "manually" determine which layers contain the clicked position.
You can use leaflet-pip plugin (point in polygon) for example for this determination:
map.on("click", function (event) {
var clickedLayers = leafletPip.pointInLayer(event.latlng, geoJSONlayerGroup);
// Do something with clickedLayers
});
Demo: https://jsfiddle.net/ve2huzxw/526/ (listening to "mousemove"
instead of "click"
)
There is a leaflet plugin for propagating events to the underlying layers: https://github.com/danwild/leaflet-event-forwarder
You can use it in your javascript to enable event-forwarding, e.g.:
const myEventForwarder = new L.eventForwarder({
map: map,
events: {click: true, mousemove: false}
});
For me the solution was to use the option interactive: false
when creating the upper layer:
var options = {
style: feature => this.__determineStyle(feature),
interactive: false,
};
var overlay = L.geoJson(geoJsonData, options);
overlay.addTo(this.map);
https://leafletjs.com/reference-1.0.3.html#interactive-layer
The problems is the order in which the layers (geometries) are added. In my case I had an array of geometries and what I did was just sort the geometries array using its bounds L.LatLngBounds#contains
, so if a geometry contains another it should be added later.
var geometries = [layerOne, layerTwo, ...];
geometries
.sort((a, b) => {
// in my case a separate function is required because the geometry could be a Circle, Rectangle, Polygon or Marker
// and the methods to get the corresponding bounds are different.
var boundsA = this.getBoundsFromGeometry(a);
var boundsB = this.getBoundsFromGeometry(b);
// if the second geometry contains the first, the order must be change so the layers don't overlap
return boundsB.contains(boundsA) ? 1 : -1;
})
.forEach(l => this.map.addLayer(l));
You can listen on the click event on the map. Then, getting the coordinates (lat, lng
) of the clicked point, you can filter your features by checking which of them contain it.
For this, you can use booleanPointInPolygon
function of turf.js
The following (sample code and fiddle) use polygons as an example.
map.on('click', e => {
const { lat, lng } = e.latlng;
const point = turf.point([lng, lat]);
/* polygons is an array where all your polygon layers are stored */
polygons.forEach((p, i) => {
const polygon= p.toGeoJSON();
if (turf.booleanPointInPolygon(point, polygon)) {
/* do whatever you want with your clicked polygon */
}
});
});
Check fiddle: https://jsfiddle.net/yfg7mdkx/