Skip to content

Instantly share code, notes, and snippets.

@kareemgrant
Created May 18, 2013 17:34
Show Gist options
  • Save kareemgrant/5605219 to your computer and use it in GitHub Desktop.
Save kareemgrant/5605219 to your computer and use it in GitHub Desktop.
Preliminary route_builder code (still needs enhancement)
/*********************************************************************\
* *
* epolys.js by Mike Williams *
* *
* A Google Maps API Extension *
* *
* Adds various Methods to GPolygon and GPolyline *
* *
* .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 *
* Community Church Javascript Team *
* http://www.bisphamchurch.org.uk/ *
* 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() *
* *
\*********************************************************************/
// === 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/
GPolygon.prototype.Contains = function(point) {
var j=0;
var oddNodes = false;
var x = point.lng();
var y = point.lat();
for (var i=0; i < this.getVertexCount(); i++) {
j++;
if (j == this.getVertexCount()) {j = 0;}
if (((this.getVertex(i).lat() < y) && (this.getVertex(j).lat() >= y))
|| ((this.getVertex(j).lat() < y) && (this.getVertex(i).lat() >= y))) {
if ( this.getVertex(i).lng() + (y - this.getVertex(i).lat())
/ (this.getVertex(j).lat()-this.getVertex(i).lat())
* (this.getVertex(j).lng() - this.getVertex(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 spechical geometry, so will be inaccurate for large polygons ===
// === The polygon must not intersect itself ===
GPolygon.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.getVertexCount(); i++) {
j++;
if (j == this.getVertexCount()) {j = 0;}
var x1 = this.getVertex(i).distanceFrom(new GLatLng(this.getVertex(i).lat(),x0));
var x2 = this.getVertex(j).distanceFrom(new GLatLng(this.getVertex(j).lat(),x0));
var y1 = this.getVertex(i).distanceFrom(new GLatLng(y0,this.getVertex(i).lng()));
var y2 = this.getVertex(j).distanceFrom(new GLatLng(y0,this.getVertex(j).lng()));
a += x1*y2 - x2*y1;
}
return Math.abs(a * 0.5);
}
// === A method which returns the length of a path in metres ===
GPolygon.prototype.Distance = function() {
var dist = 0;
for (var i=1; i < this.getVertexCount(); i++) {
dist += this.getVertex(i).distanceFrom(this.getVertex(i-1));
}
return dist;
}
// === A method which returns the bounds as a GLatLngBounds ===
GPolygon.prototype.Bounds = function() {
var bounds = new GLatLngBounds();
for (var i=0; i < this.getVertexCount(); i++) {
bounds.extend(this.getVertex(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 ===
GPolygon.prototype.GetPointAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getVertex(0);
if (metres < 0) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getVertexCount() && dist < metres); i++) {
olddist = dist;
dist += this.getVertex(i).distanceFrom(this.getVertex(i-1));
}
if (dist < metres) {return null;}
var p1= this.getVertex(i-2);
var p2= this.getVertex(i-1);
var m = (metres-olddist)/(dist-olddist);
return new GLatLng( 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 ===
GPolygon.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.getVertexCount()); i++) {
olddist = dist;
dist += this.getVertex(i).distanceFrom(this.getVertex(i-1));
while (dist > next) {
var p1= this.getVertex(i-1);
var p2= this.getVertex(i);
var m = (next-olddist)/(dist-olddist);
points.push(new GLatLng( 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 ===
GPolygon.prototype.GetIndexAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getVertex(0);
if (metres < 0) return null;
var dist=0;
var olddist=0;
for (var i=1; (i < this.getVertexCount() && dist < metres); i++) {
olddist = dist;
dist += this.getVertex(i).distanceFrom(this.getVertex(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 ===
GPolygon.prototype.Bearing = function(v1,v2) {
if (v1 == null) {
v1 = 0;
v2 = this.getVertexCount()-1;
} else if (v2 == null) {
v2 = v1+1;
}
if ((v1 < 0) || (v1 >= this.getVertexCount()) || (v2 < 0) || (v2 >= this.getVertexCount())) {
return;
}
var from = this.getVertex(v1);
var to = this.getVertex(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 ===
GPolyline.prototype.Contains = GPolygon.prototype.Contains;
GPolyline.prototype.Area = GPolygon.prototype.Area;
GPolyline.prototype.Distance = GPolygon.prototype.Distance;
GPolyline.prototype.Bounds = GPolygon.prototype.Bounds;
GPolyline.prototype.GetPointAtDistance = GPolygon.prototype.GetPointAtDistance;
GPolyline.prototype.GetPointsAtDistance = GPolygon.prototype.GetPointsAtDistance;
GPolyline.prototype.GetIndexAtDistance = GPolygon.prototype.GetIndexAtDistance;
GPolyline.prototype.Bearing = GPolygon.prototype.Bearing;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps</title>
<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=AIzaSyBsmTCDQYlmID4H8rqVe7Gb9Nm_SMpHahA" type="text/javascript"></script>
<script src="epoly.js" type="text/javascript"></script>
</head>
<body onunload="GUnload()">
Click on the map and a path will be drawn that follows the streets.<br />
Drag the markers and the path will be redrawn.
<div id="map" style="width: 800px; height: 600px"></div>
<div id="link" style="display:none">
<div id="distance"></div>
<input type="button" value="Show on maps.google.com" onclick="linkToGoogle()" />
</div>
<a href="snap.htm">Back to the tutorial page</a>
<noscript><b>JavaScript must be enabled in order for you to use Google Maps.</b>
However, it seems JavaScript is either disabled or not supported by your browser.
To view Google Maps, enable JavaScript by changing your browser options, and then
try again.
</noscript>
<script type="text/javascript">
//<![CDATA[
if (GBrowserIsCompatible()) {
var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(53.7877, -2.9832),13)
map.addControl(new GLargeMapControl());
map.addControl(new GMapTypeControl());
// == use different GDirections for adding and dragging, it is just simpler that way ==
var dirn1 = new GDirections();
var dirn2 = new GDirections();
var dirn3 = new GDirections();
var firstpoint = true;
var gmarkers = [];
var gpolys = [];
var lastindex = 0;
GEvent.addListener(map, "click", function(overlay,point) {
// == When the user clicks on a the map, get directiobns from that point to itself ==
if (!overlay) {
if (firstpoint) {
dirn1.loadFromWaypoints([point.toUrlValue(6),point.toUrlValue(6)],{getPolyline:true});
} else {
dirn1.loadFromWaypoints([gmarkers[gmarkers.length-1].getPoint(),point.toUrlValue(6)],{getPolyline:true});
}
}
});
function calculateDistance() {
var dist = 0;
for (var i=0; i<gpolys.length; i++) {
dist+=gpolys[i].Distance();
}
document.getElementById("distance").innerHTML="Path length: "+(dist/1000).toFixed(2)+" km. "+(dist/1609.344).toFixed(2)+" miles.";
}
// == when the load event completes, plot the point on the street ==
GEvent.addListener(dirn1,"load", function() {
// snap to last vertex in the polyline
var n = dirn1.getPolyline().getVertexCount();
var p=dirn1.getPolyline().getVertex(n-1);
var marker=new GMarker(p,{draggable:true});
GEvent.addListener(marker, "dragend", function() {
lastIndex = marker.MyIndex;
var point = marker.getPoint();
if (lastIndex>0) {
// recalculate the polyline preceding this point
dirn2.loadFromWaypoints([gmarkers[lastIndex-1].getPoint(),point.toUrlValue(6)],{getPolyline:true});
}
if (lastIndex<gmarkers.length-1) {
// recalculate the polyline following this point
dirn3.loadFromWaypoints([point.toUrlValue(6),gmarkers[lastIndex+1].getPoint()],{getPolyline:true});
}
});
map.addOverlay(marker);
// store the details
marker.MyIndex=gmarkers.length;
gmarkers.push(marker);
if (!firstpoint) {
map.addOverlay(dirn1.getPolyline());
gpolys.push(dirn1.getPolyline());
calculateDistance();
}
firstpoint = false;
if (gmarkers.length>1 && gmarkers.length<26) {
document.getElementById("link").style.display="";
} else {
document.getElementById("link").style.display="none";
}
});
// == move the polyline preceding this point ==
GEvent.addListener(dirn2,"load", function() {
// snap to last vertex in the polyline
var n = dirn2.getPolyline().getVertexCount();
var p=dirn2.getPolyline().getVertex(n-1);
gmarkers[lastIndex].setPoint(p);
// remove the old polyline
map.removeOverlay(gpolys[lastIndex-1]);
// add the new polyline
map.addOverlay(dirn2.getPolyline());
gpolys[lastIndex-1] = (dirn2.getPolyline());
calculateDistance();
});
// == move the polyline following this point ==
GEvent.addListener(dirn3,"load", function() {
// snap to first vertex in the polyline
var p=dirn3.getPolyline().getVertex(0);
gmarkers[lastIndex].setPoint(p);
// remove the old polyline
map.removeOverlay(gpolys[lastIndex]);
// add the new polyline
map.addOverlay(dirn3.getPolyline());
gpolys[lastIndex] = (dirn3.getPolyline());
calculateDistance();
});
GEvent.addListener(dirn1,"error", function() {
GLog.write("Failed: "+dirn1.getStatus().code);
});
GEvent.addListener(dirn2,"error", function() {
GLog.write("Failed: "+dirn2.getStatus().code);
});
GEvent.addListener(dirn3,"error", function() {
GLog.write("Failed: "+dirn3.getStatus().code);
});
function linkToGoogle() {
var url="http://maps.google.com?q=from:+Start@" + gmarkers[0].getPoint().toUrlValue(5);
for (var i=1; i<gmarkers.length-1; i++) {
url+="+to:+"+gmarkers[i].getPoint().toUrlValue(5)
}
url+="+to:+End@"+gmarkers[gmarkers.length-1].getPoint().toUrlValue(5);
window.location = url;
}
}
else {
alert("Sorry, the Google Maps API is not compatible with this browser");
}
// This Javascript is based on code provided by the
// Community Church Javascript Team
// http://www.bisphamchurch.org.uk/
// http://econym.org.uk/gmap/
//]]>
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment