text: /*********************************************************************\
  *                                                                     *
  * 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;
    if (otherLatLng == null)
      return false;
    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
  