google-mapsgoogle-maps-api-3

How to access overlapping geojson/polygons to display in infobox


I'm using Google Maps API to load multiple polygons into the map using the geoJSON data layer. Some of these polygons overlap in certain regions. When a user clicks on a point that is inside of multiple polygons, I want to display the properties (name, tags, etc) in an InfoBox with the click event.

I'm wanting to display the properties of all the polygons for a given point. Currently when I click on a point I can only see one polygon, despite the point being inside of multiple polygons.

How can I access all the properties of all the polygons with Google Maps API v3?

const map = useGoogleMap(); // google map instance
const polygons; // an array of polygons, example snippet below. 

map.data.addGeoJson(polygons);

map.data.addListener('click', function(event) {
   // how can i access other features underneath this clicked point
   console.log(event.feature); // only returns "Geofence 1"
})

example GeoJson:

polygons = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
      "name": "Geofence 1"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -8.96484375,
              -9.96885060854611
            ],
            [
              3.955078125,
              -9.96885060854611
            ],
            [
              3.955078125,
              -0.17578097424708533
            ],
            [
              -8.96484375,
              -0.17578097424708533
            ],
            [
              -8.96484375,
              -9.96885060854611
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
      "name": "Geofence 2"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -6.591796875,
              -8.320212289522944
            ],
            [
              2.197265625,
              -8.320212289522944
            ],
            [
              2.197265625,
              -1.9332268264771106
            ],
            [
              -6.591796875,
              -1.9332268264771106
            ],
            [
              -6.591796875,
              -8.320212289522944
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
      "name": "Geofence 3"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -4.39453125,
              -6.926426847059551
            ],
            [
              0.263671875,
              -6.926426847059551
            ],
            [
              0.263671875,
              -3.337953961416472
            ],
            [
              -4.39453125,
              -3.337953961416472
            ],
            [
              -4.39453125,
              -6.926426847059551
            ]
          ]
        ]
      }
    }
  ]
}

Solution

  • One option would be to use the containsLocation method in the geometry library.

    containsLocation(point, polygon) Parameters:
    point: LatLng
    polygon: Polygon
    Return Value: boolean
    Computes whether the given point lies inside the specified polygon.

    Unfortunately that only works with native google.maps.Polygon objects not Data.Polygon objects. Translate the data in the feature into native google.maps.Polygon objects, push them on an array, then process through the array to see which polygon(s) the click is in.

    1. create google.maps.Polygon for each polygon in the input (assumes only polygons)
      var polygonArray = [];
      map.data.addListener('addfeature', function(e) {
        e.feature.getGeometry().getArray().forEach(function(latLngArry){
          const polygon = new google.maps.Polygon({
            map: map,
            paths: latLngArry.getArray(),
            clickable: false,
            name: e.feature.getProperty("name") // save the data we want to output as an attribute
          })
          polygonArray.push(polygon);
        })
    
    1. on click check for which polygon(s) the click was in:
      map.addListener('click', function(event) {
        var content = "";
        for (var i=0;i<polygonArray.length;i++) {
           if (google.maps.geometry.poly.containsLocation(event.latLng, polygonArray[i])) {
              if (content.length!=0) 
                content+=" : "
              content += polygonArray[i].name;
           }
        }
        console.log(content);
      })
    

    proof of concept fiddle

    screenshot of resulting map

    // This example uses the Google Maps JavaScript API's Data layer
    // to create a rectangular polygon with 2 holes in it.
    function initMap() {
      const map = new google.maps.Map(document.getElementById("map"));
      const infowindow = new google.maps.InfoWindow();
      var bounds = new google.maps.LatLngBounds();
      var polygonArray = [];
      map.data.addListener('addfeature', function(e) {
        console.log(e.feature.getGeometry().getArray().length);
        e.feature.getGeometry().getArray().forEach(function(latLngArry) {
          console.log(latLngArry.getArray())
          const polygon = new google.maps.Polygon({
            map: map,
            paths: latLngArry.getArray(),
            clickable: false,
            name: e.feature.getProperty("name")
          })
          polygonArray.push(polygon);
        })
        processPoints(e.feature.getGeometry(), bounds.extend, bounds);
        map.fitBounds(bounds);
      });
      const features = map.data.addGeoJson(polygons);
      map.data.setMap(null);
      map.addListener('click', function(event) {
        var content = "";
        for (var i = 0; i < polygonArray.length; i++) {
          if (google.maps.geometry.poly.containsLocation(event.latLng, polygonArray[i])) {
            if (content.length != 0)
              content += " : "
            content += polygonArray[i].name;
          }
        }
        console.log(content);
        document.getElementById('info').innerHTML = content;
        infowindow.setPosition(event.latLng);
        if (content.length == 0) content = "no GeoFence";
        infowindow.setContent(content);
        infowindow.open(map);
      })
    
      function processPoints(geometry, callback, thisArg) {
        if (geometry instanceof google.maps.LatLng) {
          callback.call(thisArg, geometry);
        } else if (geometry instanceof google.maps.Data.Point) {
          callback.call(thisArg, geometry.get());
        } else {
          geometry.getArray().forEach(function(g) {
            processPoints(g, callback, thisArg);
          });
        }
      }
    }
    const polygons = {
      "type": "FeatureCollection",
      "features": [{
          "type": "Feature",
          "properties": {
            "name": "Geofence 1"
          },
          "geometry": {
            "type": "Polygon",
            "coordinates": [
              [
                [-8.96484375, -9.96885060854611],
                [
                  3.955078125, -9.96885060854611
                ],
                [
                  3.955078125, -0.17578097424708533
                ],
                [-8.96484375, -0.17578097424708533],
                [-8.96484375, -9.96885060854611]
              ]
            ]
          }
        },
        {
          "type": "Feature",
          "properties": {
            "name": "Geofence 2"
          },
          "geometry": {
            "type": "Polygon",
            "coordinates": [
              [
                [-6.591796875, -8.320212289522944],
                [
                  2.197265625, -8.320212289522944
                ],
                [
                  2.197265625, -1.9332268264771106
                ],
                [-6.591796875, -1.9332268264771106],
                [-6.591796875, -8.320212289522944]
              ]
            ]
          }
        },
        {
          "type": "Feature",
          "properties": {
            "name": "Geofence 3"
          },
          "geometry": {
            "type": "Polygon",
            "coordinates": [
              [
                [-4.39453125, -6.926426847059551],
                [
                  0.263671875, -6.926426847059551
                ],
                [
                  0.263671875, -3.337953961416472
                ],
                [-4.39453125, -3.337953961416472],
                [-4.39453125, -6.926426847059551]
              ]
            ]
          }
        }
      ]
    }
    /* Always set the map height explicitly to define the size of the div
           * element that contains the map. */
    
    #map {
      height: 90%;
    }
    
    
    /* Optional: Makes the sample page fill the window. */
    
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <title>Data Layer: Polygon</title>
      <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
      <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly" defer></script>
      <!-- jsFiddle will insert css and js -->
    </head>
    
    <body>
      <div id="info"></div>
      <div id="map"></div>
    </body>
    
    </html>