Extension of Polygon in Google Maps V3
/*********************************************************************\ * * * epolys.js by Mike Williams * * updated to API v3 by Larry Ross * * * * A Google Maps API Extension * * * * Adds various Methods to google.maps.Polygon and google.maps.Polyline * * * * .Contains(latlng) returns true is the poly contains the specified * * GLatLng * * * * .Area() returns the approximate area of a poly that is * * not self-intersecting * * * * .Distance() returns the length of the poly path * * * * .Bounds() returns a GLatLngBounds that bounds the poly * * * * .GetPointAtDistance() returns a GLatLng at the specified distance * * along the path. * * The distance is specified in metres * * Reurns null if the path is shorter than that * * * * .GetPointsAtDistance() returns an array of GLatLngs at the * * specified interval along the path. * * The distance is specified in metres * * * * .GetIndexAtDistance() returns the vertex number at the specified * * distance along the path. * * The distance is specified in metres * * Reurns null if the path is shorter than that * * * * .Bearing(v1?,v2?) returns the bearing between two vertices * * if v1 is null, returns bearing from first to last * * if v2 is null, returns bearing from v1 to next * * * * * *********************************************************************** * * * This Javascript is provided by Mike Williams * * Blackpool Community Church Javascript Team * * http://www.blackpoolchurch.org/ * * http://econym.org.uk/gmap/ * * * * This work is licenced under a Creative Commons Licence * * http://creativecommons.org/licenses/by/2.0/uk/ * * * *********************************************************************** * * * Version 1.1 6-Jun-2007 * * Version 1.2 1-Jul-2007 - fix: Bounds was omitting vertex zero * * add: Bearing * * Version 1.3 28-Nov-2008 add: GetPointsAtDistance() * * Version 1.4 12-Jan-2009 fix: GetPointsAtDistance() * * Version 3.0 11-Aug-2010 update to v3 * * * \*********************************************************************/ // === first support methods that don't (yet) exist in v3 google.maps.LatLng.prototype.distanceFrom = function(newLatLng) { //var R = 6371; // km (change this constant to get miles) var R = 6378100; // meters var lat1 = this.lat(); var lon1 = this.lng(); var lat2 = newLatLng.lat(); var lon2 = newLatLng.lng(); var dLat = (lat2-lat1) * Math.PI / 180; var dLon = (lon2-lon1) * Math.PI / 180; var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) * Math.sin(dLon/2) * Math.sin(dLon/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; return d; } google.maps.LatLng.prototype.latRadians = function() { return this.lat() * Math.PI/180; } google.maps.LatLng.prototype.lngRadians = function() { return this.lng() * Math.PI/180; } google.maps.LatLng.prototype.equals = function(otherLatLng) { var EQUALS_THRESHOLD = 0.001; return (this.distanceFrom(otherLatLng) < EQUALS_THRESHOLD); } // === A method for testing if a point is inside a polygon // === Returns true if poly contains point // === Algorithm shamelessly stolen from http://alienryderflex.com/polygon/ google.maps.Polygon.prototype.Contains = function(point) { var j=0; var oddNodes = false; var x = point.lng(); var y = point.lat(); for (var i=0; i < this.getPath().getLength(); i++) { j++; if (j == this.getPath().getLength()) {j = 0;} if (((this.getPath().getAt(i).lat() < y) && (this.getPath().getAt(j).lat() >= y)) || ((this.getPath().getAt(j).lat() < y) && (this.getPath().getAt(i).lat() >= y))) { if ( this.getPath().getAt(i).lng() + (y - this.getPath().getAt(i).lat()) / (this.getPath().getAt(j).lat()-this.getPath().getAt(i).lat()) * (this.getPath().getAt(j).lng() - this.getPath().getAt(i).lng())<x ) { oddNodes = !oddNodes } } } return oddNodes; } // === A method which returns the approximate area of a non-intersecting polygon in square metres === // === It doesn't fully account for spherical geometry, so will be inaccurate for large polygons === // === The polygon must not intersect itself === // // only calculates the area of the first path // google.maps.Polygon.prototype.Area = function() { var a = 0; var j = 0; var b = this.Bounds(); var x0 = b.getSouthWest().lng(); var y0 = b.getSouthWest().lat(); for (var i=0; i < this.getPath().getLength(); i++) { j++; if (j == this.getPath().getLength()) {j = 0;} var x1 = this.getPath().getAt(i).distanceFrom(new google.maps.LatLng(this.getPath().getAt(i).lat(),x0)); var x2 = this.getPath().getAt(j).distanceFrom(new google.maps.LatLng(this.getPath().getAt(j).lat(),x0)); var y1 = this.getPath().getAt(i).distanceFrom(new google.maps.LatLng(y0,this.getPath().getAt(i).lng())); var y2 = this.getPath().getAt(j).distanceFrom(new google.maps.LatLng(y0,this.getPath().getAt(j).lng())); a += x1*y2 - x2*y1; } return Math.abs(a * 0.5); } // == spherical area from // http://forum.worldwindcentral.com/showthread.php?t=20724 /// <summary> /// Haversine function : hav(x) = (1-cos(x))/2 /// </summary> /// <param name="x"></param> /// <returns>Returns the value of Haversine function</returns> function Haversine( x ) { return ( 1.0 - Math.cos( x ) ) / 2.0; } /// <summary> /// Compute the Area of a Spherical Polygon /// </summary> /// <param name="paths">latlng array of all vertices</param> /// <param name="r">spherical radius</param> /// <returns>Returns the area of a spherical polygon</returns> // // only calculates the area of the first path // google.maps.Polygon.prototype.SphericalPolygonArea = function ( ) { var r= 6378100; // meters var lam1 = 0, lam2 = 0, beta1 =0, beta2 = 0, cosB1 = 0, cosB2 = 0; var hav = 0; var sum = 0; var paths = this.getPath(); if (paths.getLength() < 2) return 0; for( var j = 0 ; j < paths.getLength() ; j++ ) { var k = j + 1; if( j == 0 ) { lam1 = paths.getAt(j).lngRadians(); beta1 = paths.getAt(j).latRadians(); lam2 = paths.getAt(j + 1).lngRadians(); beta2 = paths.getAt(j + 1).latRadians(); cosB1 = Math.cos( beta1 ); cosB2 = Math.cos( beta2 ); } else { k = ( j + 1 ) % paths.getLength(); lam1 = lam2; beta1 = beta2; lam2 = paths.getAt(k).lngRadians(); beta2 = paths.getAt(k).latRadians(); cosB1 = cosB2; cosB2 = Math.cos( beta2 ); } if( lam1 != lam2 ) { var hav = Haversine( beta2 - beta1 ) + cosB1 * cosB2 * Haversine( lam2 - lam1 ); var a = 2 * Math.asin( Math.sqrt( hav ) ); var b = Math.PI / 2 - beta2; var c = Math.PI / 2 - beta1; var s = 0.5 * ( a + b + c ); var t = Math.tan( s / 2 ) * Math.tan( ( s - a ) / 2 ) * Math.tan( ( s - b ) / 2 ) * Math.tan( ( s - c ) / 2 ); var excess = Math.abs( 4 * Math.atan( Math.sqrt( Math.abs( t ) ) ) ); if( lam2 < lam1 ) { excess = -excess; } sum += excess; } } return Math.abs( sum ) * r * r; } // === A method which returns the length of a path in metres === google.maps.Polygon.prototype.Distance = function() { var dist = 0; for (var i=1; i < this.getPath().getLength(); i++) { dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1)); } return dist; } // === A method which returns the bounds as a GLatLngBounds === google.maps.Polygon.prototype.Bounds = function() { var bounds = new google.maps.LatLngBounds(); for (var i=0; i < this.getPath().getLength(); i++) { bounds.extend(this.getPath().getAt(i)); } return bounds; } // === A method which returns a GLatLng of a point a given distance along the path === // === Returns null if the path is shorter than the specified distance === google.maps.Polygon.prototype.GetPointAtDistance = function(metres) { // some awkward special cases if (metres == 0) return this.getPath().getAt(0); if (metres < 0) return null; var dist=0; var olddist=0; for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) { olddist = dist; dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1)); } if (dist < metres) {return null;} var p1= this.getPath().getAt(i-2); var p2= this.getPath().getAt(i-1); var m = (metres-olddist)/(dist-olddist); return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m); } // === A method which returns an array of GLatLngs of points a given interval along the path === google.maps.Polygon.prototype.GetPointsAtDistance = function(metres) { var next = metres; var points = []; // some awkward special cases if (metres <= 0) return points; var dist=0; var olddist=0; for (var i=1; (i < this.getPath().getLength()); i++) { olddist = dist; dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1)); while (dist > next) { var p1= this.getPath().getAt(i-1); var p2= this.getPath().getAt(i); var m = (next-olddist)/(dist-olddist); points.push(new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m)); next += metres; } } return points; } // === A method which returns the Vertex number at a given distance along the path === // === Returns null if the path is shorter than the specified distance === google.maps.Polygon.prototype.GetIndexAtDistance = function(metres) { // some awkward special cases if (metres == 0) return this.getPath().getAt(0); if (metres < 0) return null; var dist=0; var olddist=0; for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) { olddist = dist; dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1)); } if (dist < metres) {return null;} return i; } // === A function which returns the bearing between two vertices in decgrees from 0 to 360=== // === If v1 is null, it returns the bearing between the first and last vertex === // === If v1 is present but v2 is null, returns the bearing from v1 to the next vertex === // === If either vertex is out of range, returns void === google.maps.Polygon.prototype.Bearing = function(v1,v2) { if (v1 == null) { v1 = 0; v2 = this.getPath().getLength()-1; } else if (v2 == null) { v2 = v1+1; } if ((v1 < 0) || (v1 >= this.getPath().getLength()) || (v2 < 0) || (v2 >= this.getPath().getLength())) { return; } var from = this.getPath().getAt(v1); var to = this.getPath().getAt(v2); if (from.equals(to)) { return 0; } var lat1 = from.latRadians(); var lon1 = from.lngRadians(); var lat2 = to.latRadians(); var lon2 = to.lngRadians(); var angle = - Math.atan2( Math.sin( lon1 - lon2 ) * Math.cos( lat2 ), Math.cos( lat1 ) * Math.sin( lat2 ) - Math.sin( lat1 ) * Math.cos( lat2 ) * Math.cos( lon1 - lon2 ) ); if ( angle < 0.0 ) angle += Math.PI * 2.0; angle = angle * 180.0 / Math.PI; return parseFloat(angle.toFixed(1)); } // === Copy all the above functions to GPolyline === google.maps.Polyline.prototype.Contains = google.maps.Polygon.prototype.Contains; google.maps.Polyline.prototype.Area = google.maps.Polygon.prototype.Area; google.maps.Polyline.prototype.Distance = google.maps.Polygon.prototype.Distance; google.maps.Polyline.prototype.Bounds = google.maps.Polygon.prototype.Bounds; google.maps.Polyline.prototype.GetPointAtDistance = google.maps.Polygon.prototype.GetPointAtDistance; google.maps.Polyline.prototype.GetPointsAtDistance = google.maps.Polygon.prototype.GetPointsAtDistance; google.maps.Polyline.prototype.GetIndexAtDistance = google.maps.Polygon.prototype.GetIndexAtDistance; google.maps.Polyline.prototype.Bearing = google.maps.Polygon.prototype.Bearing; google.maps.Polyline.prototype.SphericalPolygonArea = google.maps.Polygon.prototype.SphericalPolygonArea