polygon

Google Maps Circle Overlay

This was another feature that should have been simple, but just wasn't. This Google Maps extension allows you to use circle overlays. It is really a GPolygon with 45 points, but it looks like a circle. Here is a sample. You can download the source here.

js/gmaps.CircleOverlay.js:

// This file adds a new circle overlay to GMaps2
// it is really a many-pointed polygon, but look smooth enough to be a circle.
var CircleOverlay = function(latLng, radius, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity) {
    this.latLng = latLng;
    this.radius = radius;
    this.strokeColor = strokeColor;
    this.strokeWidth = strokeWidth;
    this.strokeOpacity = strokeOpacity;
    this.fillColor = fillColor;
    this.fillOpacity = fillOpacity;
}

// Implements GOverlay interface
CircleOverlay.prototype = GOverlay;

CircleOverlay.prototype.initialize = function(map) {
    this.map = map;
}

CircleOverlay.prototype.clear = function() {
    if(this.polygon != null && this.map != null) {
        this.map.removeOverlay(this.polygon);
    }
}

// Calculate all the points and draw them
CircleOverlay.prototype.redraw = function(force) {
    var d2r = Math.PI / 180;
    circleLatLngs = new Array();
    var circleLat = this.radius * 0.014483;  // Convert statute miles into degrees latitude
    var circleLng = circleLat / Math.cos(this.latLng.lat() * d2r);
    var numPoints = 40;
   
    // 2PI = 360 degrees, +1 so that the end points meet
    for (var i = 0; i < numPoints + 1; i++) {
        var theta = Math.PI * (i / (numPoints / 2));
        var vertexLat = this.latLng.lat() + (circleLat * Math.sin(theta));
        var vertexLng = this.latLng.lng() + (circleLng * Math.cos(theta));
        var vertextLatLng = new GLatLng(vertexLat, vertexLng);
        circleLatLngs.push(vertextLatLng);
    }
   
    this.clear();
    this.polygon = new GPolygon(circleLatLngs, this.strokeColor, this.strokeWidth, this.strokeOpacity, this.fillColor, this.fillOpacity);
    this.map.addOverlay(this.polygon);
}

CircleOverlay.prototype.remove = function() {
    this.clear();
}

CircleOverlay.prototype.containsLatLng = function(latLng) {
    // Polygon Point in poly
    if(this.polygon.containsLatLng) {
        return this.polygon.containsLatLng(latLng);
    }
}

CircleOverlay.prototype.setRadius = function(radius) {
    this.radius = radius;
}

CircleOverlay.prototype.setLatLng = function(latLng) {
    this.latLng = latLng;
}

circle.html:

<html>
<head>
    <title>GMaps Circle Test</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAOzwQ7Lh4CqiMto5Mx5BruhS-xyZmcPPoBCehH0LrfEv4pktlHxSKTILYLRlghp_oPDcwTo3STVXADg" type="text/javascript"></script>
    <script src="js/gmaps.CircleOverlay.js" type="text/javascript"></script>
    <script type="text/javascript">
    var circle = null;
    var circleRadius = 50; // Miles
    var map = null;
    var isCompatible = GBrowserIsCompatible();
   
    //<![CDATA[
    function load() {
        if (isCompatible) {
            // Create Map
            map = new GMap2(document.getElementById("map"));
            map.setCenter(new GLatLng(42, -74), 6);

            // Add controls
            map.addControl(new GLargeMapControl());
            map.addControl(new GMapTypeControl());
           
            // Create and add the circle
            circle = new CircleOverlay(map.getCenter(), circleRadius, "#336699", 1, 1, '#336699', 0.25);
            map.addOverlay(circle);
        }
    }
    //]]>
    </script>
</head>
<body onload="load()" onunload="GUnload()">
    <div id="map" style="width: 400px; height: 300px; border: 1px solid #666666;"></div>
</body>
</html>

Check if a polygon contains a coordinate in Google Maps

At work, I've been tasked with a neat little project where a user can select a list of users represented by markers on a Google map by editing a polygon to surround the markers with. However, to my dismay, I could not find any methods in the API to see if a polygon contains a coordinate. I also had a hard time finding an algorithm so that I could implement it.

First I tried the winding number algorithm (http://en.wikipedia.org/wiki/Point_in_polygon) but failed. I didn't put too much time into it since it is so CPU-intesive anyway. Then I came across an article on MSDN (http://msdn.microsoft.com/en-us/library/cc451895.aspx) which I adopted into Javascript. I extended the GPolygon class with a new method called containsLatLng, just like the GLatLngBounds class. The difference is that the method in the GLatLngBounds class will only get the 4 coordinates of the bounding box, which are the farthest points in the polygon, and see if the coordinate lies within that box. My method, however, does true collision detection to see if the point lies within any shape polygon with as many sides as you want. It worked out perfectly.

Without any further ado, here is the code [js/gmaps.polygon.containsLatLng.js]:

// Create polygon method for collision detection
GPolygon.prototype.containsLatLng = function(latLng) {
    // Do simple calculation so we don't do more CPU-intensive calcs for obvious misses
    var bounds = this.getBounds();
   
    if(!bounds.containsLatLng(latLng)) {
        return false;
    }
   
    var numPoints = this.getVertexCount();
    var inPoly = false;
    var i;
    var j = numPoints-1;
   
    for(var i=0; i < numPoints; i++) {
        var vertex1 = this.getVertex(i);
        var vertex2 = this.getVertex(j);
       
        if (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng() || vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng())  {
            if (vertex1.lat() + (latLng.lng() - vertex1.lng()) / (vertex2.lng() - vertex1.lng()) * (vertex2.lat() - vertex1.lat()) < latLng.lat()) {
                inPoly = !inPoly;
            }
        }
       
        j = i;
    }
   
    return inPoly;
};

And here is the sample usage [index.htm]:

<html>
<head>
    <title>GIS Test</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAOzwQ7Lh4CqiMto5Mx5BruhRP7uQ16ovEZegyAfeetOdXUUHv1xQhaNqllcioqRUJo6b7JhhTgJZNyw" type="text/javascript"></script>
    <script src="js/gmaps.polygon.containsLatLng.js" type="text/javascript"></script>
    <script type="text/javascript">
    var isEditing = false;
    var polygon = null;
    var map = null;
    var isCompatible = GBrowserIsCompatible();
    var markers = [];
    var iconIncluded = 'http://maps.google.com/mapfiles/dd-start.png';
    var iconExcluded = 'http://maps.google.com/mapfiles/dd-end.png';
       
        function editClick() {
                isEditing = !isEditing;
                       
                if(isEditing && polygon != null) {
                        document.getElementById('edit_button').innerHTML = 'Stop Editing';
                        document.getElementById('help').innerHTML = 'Click to add points to your selection.';
                        document.getElementById('reset_button').disabled = 'disabled';
                        polygon.enableDrawing();
                } else {
                        document.getElementById('edit_button').innerHTML = 'Edit Selection';
                        document.getElementById('reset_button').disabled = '';
                        document.getElementById('help').innerHTML = 'Click "Edit Selection" to select users.';
                        polygon.disableEditing();
                }
        }
       
        // Update all markers with in poly status
        function updatePoints() {
                for(var i in markers) {
                        var marker = markers[i];
                        var point = marker.getLatLng();
                        var inPoly = polygon.containsLatLng(point);
                       
                        if(inPoly) {
                                marker.setImage(iconIncluded);
                        } else {
                                marker.setImage(iconExcluded);
                        }
                }
        }
       
        function resetPolygon() {
                if(polygon != null) {
                        map.removeOverlay(polygon);
                }
               
                polygon = new GPolygon([], "#000000", 1, 1, "#336699", 0.3);
                map.addOverlay(polygon);
               
                GEvent.addListener(polygon, "lineupdated", function() {
                        setTimeout(updatePoints, 50);
                });
               
                GEvent.addListener(polygon, "endline", function() {
                        setTimeout(editClick, 50);
                });
               
                updatePoints();
        }

    //<![CDATA[
    function load() {
        if (isCompatible) {
            // Create Map
            map = new GMap2(document.getElementById("map"));
            map.setCenter(new GLatLng(40, -90), 3);

            // Add controls
            map.addControl(new GLargeMapControl());
            map.addControl(new GMapTypeControl());

            // Create Selection Polygon
            var latlng = map.getCenter();
            var width = 20;
            var height = 12;
            var lat = latlng.lat();
            var lng = latlng.lng();
            var x1 = lng - width / 2;
            var y1 = lat - height / 2;
            var x2 = lng + width / 2;
            var y2 = lat + height / 2;
                       
                        resetPolygon();

            // Add 10 markers to the map at random locations
            var bounds = map.getBounds();
            var southWest = bounds.getSouthWest();
            var northEast = bounds.getNorthEast();
            var lngSpan = northEast.lng() - southWest.lng();
            var latSpan = northEast.lat() - southWest.lat();

            for (var i = 0; i < 100; i++) {
                var point = new GLatLng(southWest.lat() + latSpan * Math.random(), southWest.lng() + lngSpan * Math.random());
                var marker = new GMarker(point, {bouncy: true, title: "Customer " + i, autoPan: false});
                map.addOverlay(marker);
                marker.bindInfoWindowHtml("This is customer info " + i);
                markers.push(marker);
            }
           
            updatePoints();
        }
    }
    //]]>
    </script>
</head>
<body onload="load()" onunload="GUnload()">
    <div>
        <span><button id="edit_button" onclick="editClick()" type="button">Edit Selection</button></span>
        <span><button id="reset_button" onclick="resetPolygon()" type="button">Reset Selection</button></span>
        <span id="help" style="color: #666;">Click "Begin Editing" to select users.</span>
    </div>
    <hr />
    <div id="map" style="width: 640; height: 480; border:1px solid #333;"></div>
</body>
</html>

Sample: http://dawsdesign.com/Samples/GIS/
Download: http://dawsdesign.com/Samples/GIS/GIS.zip

Syndicate content