[GIS] Select one feature of multiple overlapping features (here polygons) on a Leaflet map

javascriptleafletoverlapping-features

I have a map with multiple polygons rendered on it which can overlap eachother. I use leafletPip.pointInLayer(point, layer) from https://github.com/mapbox/leaflet-pip for determining which polygons do overlap. This happens in the processClick function. In the Vue object I create the map and my GeoJSON layer with the polygons. What I now want is following feature: if you click on a point on the map and this point is contained in multiple polygons, you have something like a selection tool, e.g. in a pop-up, to click on one of these polygons and trigger the .on('click') function only for this specific polygon. I searched all the Leaflet features but I could not find anything really useful. Right now, if you click on a point contained in multiple polygons, you only trigger the .on('click') for the polygon which spatially contains the others.

var mapVue = new Vue({
  parent: vue_broadcaster,
  el: '#map',
  data: {
    map: null,
    layer: null
  },
  created: function () {
   // Adding Leaflet map here
    var map = L.map('map').setView([51.959, 7.623], 14);
    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);
    this.$set('map', map);
    this.get_zones();
  },
  methods: {
    switch_zone: function (zoneid) {
      this.$dispatch('zoneSelected', zoneid)
    },
    get_zones: function () {
      this.$http.get('/api/zones/', function (data) {
      // Creation of the GeoJSON layer
        var geoZone = {
          "type": "FeatureCollection",
          "features": []
        };
        for (var i = 0; i < data['Zones'].length; i++) {
          geoZone.features.push({
            "type": "Feature",
            "geometry": {
              "type": "Polygon",
              "coordinates": [[]]
            },
            "properties": {
              "name": ""
            }
          })
          geoZone.features[i].properties.name = data['Zones'][i]['Name'];
          for (var j = 0; j < data['Zones'][i]['Geometry']['Coordinates'].length; j++) {
            geoZone.features[i].geometry.coordinates[0].push([data['Zones'][i]['Geometry']['Coordinates'][j][1], data['Zones'][i]['Geometry']['Coordinates'][j][0]])
          }
          this.layer = new L.geoJson(geoZone)
            .bindPopup(data['Zones'][i]['Name'])
            .on('click', dispatchZoneID(data['Zones'][i]['Zone-id'], this))
            .on('click', function (e) {
              processClick(e.latlng.lat, e.latlng.lng)
            })
            .addTo(this.map)
        };
      })
    }
  }
});
// function for processing the clicks on the map
function processClick(lat, lng) {
  var info = '';
  var point = [lng, lat];
  var match = leafletPip.pointInLayer(point, mapVue.layer, false);
  if (match.length) {
    for (var i = 0; i < match.length; i++) {
      info +=
      "<b>" + match[i].feature.properties.name + "<br>"
    }
  }
  if (info) {
    mapVue.map.openPopup(info, [lat, lng]);
  }
};
// not important for this one
function dispatchZoneID(id, vue) {
  return function () {
    vue.$dispatch('zoneSelected', id)
  }
};

Best Answer

I found a solution working for me, but it's maybe not the most elegant one. If the point clicked is contained in multiple polygons (match.length > 1), I generate this info string. In each iteration the for-loop creates a clickable link which then calls a function on click depending on the id. So I basically have to generate a lot of HTML in one single string and work with literals and string concatenation. Then I call the openPopup function with info as my html param.

function processClick(lat, lng) {
  var info = '';
  var point = [lng, lat];
  var match = leafletPip.pointInLayer(point, mapVue.layer, false);
  if (match.length > 1) {
    for (var i = 0; i < match.length; i++) {
      id = match[i].feature.properties.zoneid;
      name = match[i].feature.properties.name;
      info +=
      "<b><a onclick='dispatchZoneID(\"" + id + "\")();'>"+ name + "</a><br>"
    }
  }
  else dispatchZoneID(mapVue.zoneid)();

  if (info) {
    mapVue.map.openPopup(info, [lat, lng]);
  }
};