Capturing Google Maps markers inside a polygon

1 years ago

Capturing Google Maps markers inside a polygon

Do you want to capture Google Maps markers within a drawn polygon? For example, enable brush selection below and draw a circle around 2-3 markers and watch them start bouncing!

Enable brush selection and draw a circle around some markers. Source code

If we want to capture markers inside of a polygon, we first must implement our drawing mechanism. If you already have this set up, you can skip ahead to the next code block.

There's a drawing library by Google Maps which works great, however, unfortunately you can't brush a polygon by just dragging your mouse around.

To combat this, we can create our own drawing implementation that captures each mouse movement!

To start off, we'll just throw together a custom control that toggles the brush on each click:

JavaScript CSS
createCustomControl() { this.brushControl = document.createElement("button"); this.brushControl.className = "custom-control"; this.brushControl.type = "button"; this.setDrawingEnabled(false); this.brushControl.addEventListener("click", () => { this.setDrawingEnabled(!this.drawingEnabled); }); const centerControl = document.createElement("div"); centerControl.appendChild(this.brushControl); this.map.controls[google.maps.ControlPosition.TOP_CENTER].push(centerControl); };
.custom-control { cursor: pointer; color: rgb(25,25,25); background: #FFF; box-shadow: 0 2px 6px rgba(0, 0, 0, .3); border: 2px solid #FFF; border-radius: 3px; font-family: "Roboto", Arial, sans-serif; font-size: 16px; line-height: 38px; text-align: center; margin: 8px 0 22px; padding: 0 5px; }

When we enable our brush control, we want to seize any movements done to the map while interacting with it.

Therefore, we're disabling gesture handling which partially handles mouse inputs and keyboard shortcuts which partially handles arrow movements.

We also want to register our event listeners, but also remove them when disabling the brush, so that we don't keep our mouse events while not drawing.

setDrawingEnabled(enabled) { this.drawingEnabled = enabled; if(!this.drawingEnabled) { this.map.setOptions({ gestureHandling: "auto", keyboardShortcuts: true }); this.brushControl.textContent = "Enable brush selection"; this.brushControl.title = "Click to enable selecting markers by drawing"; this.eventListeners.forEach((listener) => { google.maps.event.removeListener(listener); }); } else { this.map.setOptions({ gestureHandling: "none", keyboardShortcuts: false }); this.brushControl.textContent = "Disable brush selection"; this.brushControl.title = "Click to disable selecting markers by drawing"; this.createDrawingEvents(); } };

With the brush control added, we can initialize our polygon which will start with no vertices. It's important that we set clickable to be false, this is to later prevent the mouse events from sticking to the polygon instead of the map.

createPolygon() { return new google.maps.Polygon({ map: this.map, clickable: false, strokeColor: "#4B91C3", strokeOpacity: 0.8, strokeWeight: 2, fillColor: "#4B91C3", fillOpacity: 0.5 }); };

To register our brushes, we simply want to listen to 3 mouse events: mousedown, mousemove, and mouseup; and during these 3 events, we want to append the latitude and longitude from the mouse event to an array, as well as updating our polygon to reflect the changes!

createDrawingEvents() { let mousedown = false; this.eventListeners.push(this.map.addListener("mousedown", (event) => { mousedown = true; this.coordinates = [ event.latLng ]; this.polygon.setPaths(this.coordinates); this.selectedMarkers.forEach((marker) => { marker.setAnimation(null); }); this.selectedMarkers = []; })); this.eventListeners.push(this.map.addListener("mousemove", (event) => { if(!mousedown) return; this.coordinates.push(event.latLng); this.polygon.setPaths(this.coordinates); })); this.eventListeners.push(this.map.addListener("mouseup", (event) => { mousedown = false; this.coordinates.push(event.latLng); this.polygon.setPaths(this.coordinates); this.setDrawingEnabled(false); })); };

Mow we have a functioniong drawing mechanism and a brush control. So, once we have a polygon at mouseup, we first want to for performance reasons create boundaries of our drawn polygon.

This is because it may be more performance heavy to instantly check if a marker is within a polygon if the marker isn't even within the rectangle boundary.

This is not for certain, the Google Maps JS API might have covered this in their geometry library already, but this feels safer without explicitly knowing about it!

createBoundsFromPath(path) { const bounds = new google.maps.LatLngBounds(); path.forEach((coordinate) => bounds.extend(coordinate)); return bounds; };

To determine if a marker then is inside of the polygon, after we've ensured it's within the rectangle boundary, we can use the geometry library's poly#containsLocation utility function as such:

getMarkersInPolygon(polygon, markers) { const bounds = this.createBoundsFromPath(polygon.getPath()); return markers.filter((marker) => { const position = marker.getPosition(); if(!bounds.contains(position)) return false; if(!google.maps.geometry.poly.containsLocation(position, polygon)) return false; return true; }); };

Since Google Maps does not currently provide a method to get all marker instances attached to a map, we must unfortunately track them ourselves.

So for an example here, I'll spawn 20 randomly placed markers that I keep track of in an array:

createRandomMarkers() { for(let index = 0; index < 20; index++) { this.markers.push(new google.maps.Marker({ map: this.map, position: { lat: this.center.lat + ((Math.random() - 0.5) / 10), lng: this.center.lng + ((Math.random() - 0.5) / 10) } })); } };

Now, in our mouseup event, we can collect all markers that are within our drawn polygon and then make them bounce!

this.selectedMarkers = this.getMarkersInPolygon(this.polygon, this.markers); this.selectedMarkers.forEach((marker) => { marker.setAnimation(google.maps.Animation.BOUNCE); });

I will also track our selected markers, so that we can clear the bounce animation in our mousedown event!

this.selectedMarkers.forEach((marker) => { marker.setAnimation(null); }); this.selectedMarkers = [];

You can find the full source code on Github!