User talk:Sunyin

test

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">   v\:* { behavior:url(#default#VML); }	 function checkUrl { //some bad URL's (without the www) have found their way onto the web. Handle this. var urlUsed = location.href; var urlParams = ''; if (urlUsed.substr(7,3) != 'www') { //if they sent querystring parameters, make sure to preserve them if (urlUsed.indexOf('?') > -1) { var qStringArray = urlUsed.split('?'); if (qStringArray[1].length > 0) urlParams = '?' + qStringArray[1]; }				location.href='http://www.sueandpaul.com/gmapPedometer/' + urlParams; }		}		checkUrl;   #panel table { border:solid 1px grey; width:50%; margin-bottom: 5px; }	#panel table td:first-child { width: 24px; }	html, body { width:100%; height:100% }	.label { font-size:smaller; font-family:sans-serif; }	.plain { font-family:arial; font-size:10pt; }	.hdng { font-weight:bold; margin-top:10pt; }	.new { font-weight:bold; margin-top:10pt; color:#ff0000 }	.para { margin-top:10pt; } 

Gmaps Pedometer 

 

Permalink (to email or bookmark your route)

Clear points and start over

Click here if you don't live in Hoboken

NEW!

7/5/05: We're official! The tool now uses the Google Maps API. There are a number of bugs and incompatabilities that will hopefully be cleared up by using the offical API. 7/5/05: And because it's always a drag to have a new software release with no new features, you can now choose your units. See the radio button under the distance buttons.

What is this?

This is a little hack that uses Google's superb mapping application to help record distances traveled during a running or walking workout. Usage:

Just as you do at Google Maps</a>, drag or double-click to move the map, use the slider to increase or decrease the zoom level, and click the "Satellite" button (top right) to switch to satellite imagery.

When you're ready to start recording the distance you traveled, click the Start recording button.

Thereafter, every double-click will draw a point on the map, and the distance from your last point and your cumulative distance will both appear in the boxes above. Note that only a double-click will create a new point.

If you make a mistake or change your mind, the Undo last point button will remove the last point drawn and revert your distances to what they were before you drew it.

If you want to start over, click the Clear points and start over link.

If you want to save your map, click the Permalink link and the URL in your browser's location bar will be updated with information about your current map. If you follow that link later from a bookmark or by plugging it directly into the browser, you will get back exactly the map you were viewing when you created the permalink, location and route, and distances.

Use the miles/kilometers radio button beneath the distance boxes to choose your units.

Why?

As a runner training for a marathon for the first time, I found myself wishing I had an easy way to know the exact distance a certain course is, without having to drag a GPS or pedometer around on my runs. Looking at Google Maps, and knowing there was a vibrant community of geeks hacking it, I knew there had to be a way. So here it is.

Note that there's a reason this is called a pedometer. Considering that you have to choose all the points in your route individually yourself, it's probably not suitable for driving trips and the like. But I've found it suits my purposes pretty well for getting running distances.

Acknowledgements and references <ul><li>First and foremost, Google</a>, who made a great map site, and had the chutzpah to make it driven by client-site code and hackable. This is the sort of thing that make the Internet fun.</li>

<li>Follower at myrealbox.com for Gmap Standalone</a>, the embedding how-to</a>, and other good tricks.</li>

<li>How to calculate distance in miles from latitude and longitude</a></li> <li>Matt at gnik.com for Gmaps Mapper</a> which helped with learning the XML format. There is also lots of other good general info at his Gmaps page</a>. Including excellent documentation of the route encoding algorithm, which god help me I'll get off my lazy butt and use to add polylines to this thing eventually.</li>

<li>Jesse Ruderman et al for the painfully cool Javascript shell and bookmarklet</a>. He has a whole bunch of other bookmarklets</a> available too.</li>

<li>All those nutty kids at the Google Maps Google Group</a>.</li></ul>

Change History

6/24/05: Now your course is drawn using lines instead of points! I think you'll find this makes the tool easier (and more fun) to use. 6/27/05:OK, I think I've cleared out the bug in the polyline drawing functionality. Thanks for the feedback, please keep it coming</a> 6/29/05: Looks like Google released an upgrade that broke the app. I've uploaded a version that directly references the older version of their .js so we should be OK. Let me know</a> if you still have problems. 6/29/05: Permalinks! This will allow you to save the your location, course, and distances as a URL and come back to them later. When you click the "Permalink" link, the URL in the location bar of your browser will be updated with parameters describing your position and current course. Then just bookmark the page! You can use this function to create a boomark to a map pre-zoomed to your town, save all your favorite runs as bookmarks, email a URL for a course to someone, or post a link to a course in a message board. 6/29/05: Clear Link. By popular demand, there is now a link that lets you clear whatever points you've recorded so far, and start over. As always, let me know</a> what you think and if you have problems with it. 6/30/05: Bugfix: You should now be able to zoom out much further than before without your route degrading into a line between the start and end./div>

<script type="text/javascript"> //<![CDATA[ var map; var bDoubleClickHappened = false; function initializeMap{

//intialize the map map = new GMap(document.getElementById("map")); map.centerAndZoom(new GPoint(-74.03119972477536, 40.74566650390625), 2); map.addControl(new GLargeMapControl); map.addControl(new GMapTypeControl); //if parameters have been passed in to prepop location and route, parse them and display correct values rehydrateMapFromUrl; GEvent.addListener(map, "moveend", function {			if (bDoubleClickHappened){				addLeg(map.getCenterLatLng.x, map.getCenterLatLng.y);				drawPolyLine(gPointArray);			} 			bDoubleClickHappened = false;		});

}

var xArray = new Array(0); var yArray = new Array(0); var locationArray = new Array(0); var legArray = new Array(0); var distancesArray = new Array(0); var pointArray = new Array(0); var gPointArray = new Array(0); var routePolyline var distance = 0; var bRecordPoints = false; var REMOVE = 0; var ADD = 1; var MILES = "0"; var KILOMETERS = "1"; var altXML = "<?xml version='1.0'?> <center lat='37.0625' lng='-95.677068'/>  " distancesArray.push(0); if (navigator.appName == 'Microsoft Internet Explorer'){ document.ondblclick = handleDblClick; } else { window.ondblclick = handleDblClick; }	function handleDblClick(e) { bDoubleClickHappened = true; }	function addLeg(xCoord, yCoord) { if (bRecordPoints) { xArray.push(xCoord); yArray.push(yCoord); pointArray.push(xCoord + ',' + yCoord); gPointArray.push(new GPoint(xCoord, yCoord)); updateDistances(xArray, yArray, ADD); }	}	function updateDistances(xArray, yArray, addOrRemove){ if (addOrRemove == ADD){ //if (distanceToRemove == 0) { fLastLeg = getLastLegDistance(xArray, yArray); distancesArray.push(distancesArray[distancesArray.length-1]+fLastLeg) //distance += fLastLeg; legArray.push(fLastLeg); //} else if (distanceToRemove > 0) { } else if (addOrRemove == REMOVE) { //distance -= removedLeg; distancesArray.pop; legArray.pop; }		updateDistanceBoxes; }	function updateDistanceBoxes { document.controlPanel.mileage.value=returnDistanceInChosenUnits(distancesArray[distancesArray.length-1]); var fLastLeg = 0; if (legArray.length > 0) { fLastLeg = legArray[legArray.length-1]; }		document.controlPanel.lastLeg.value=returnDistanceInChosenUnits(fLastLeg); }	function returnDistanceInMiles(point1y, point1x, point2y, point2x) { return (3963.0 * Math.acos(Math.sin(point1y/57.2958) * Math.sin(point2y/57.2958) + Math.cos(point1y/57.2958) * Math.cos(point2y/57.2958) * Math.cos((point2x/57.2958) - (point1x/57.2958)))); }	function initializeParameter(nameOfParameter, defaultValue){ var returnVal = ''; var qstringVal = getQuerystringParameter(nameOfParameter); if (qstringVal.length > 0) { returnVal = qstringVal; } else { returnVal = defaultValue; }		return returnVal; }	function rehydrateMapFromUrl { var curCenterX = parseFloat(initializeParameter('centerX', -74.03119972477536)); var curCenterY = parseFloat(initializeParameter('centerY', 40.74566650390625)); var zoomLevel = parseInt(initializeParameter('zl',2));

map.centerAndZoom(new GPoint(curCenterX, curCenterY), zoomLevel); var polyline = getQuerystringParameter('polyline'); if (polyline.length > 0) { arrPoints = decodePolyline(polyline); //currently the only way it is possible for the URL to have a polyline is if the user was //recording at the time they permalinked. Thus, we set recording mode back on now. bRecordPoints = true; document.controlPanel.startRecording.value='Recording...'; var j = 0; while(j < arrPoints.length) { var yCoord = contractNumber(arrPoints[j++]); var xCoord = contractNumber(arrPoints[j++]); addLeg(xCoord, yCoord); }			//gPointArray should have been fully repopulated during //recursive calls to addLeg drawPolyLine(gPointArray); }	}	function expandNumber(num) { var noMinusNum = num.replace('-',''); var numDigitsBeforeDot = noMinusNum.indexOf('.'); num = num.replace('\.','') var digitsToGet=numDigitsBeforeDot+5; if (num.charAt(0)=="-") { num = num.substr(0,++digitsToGet); }else { num = num.substr(0,digitsToGet); }		return num; }	function contractNumber(num) { var numObj = new String(num); var lenNum = numObj.length; var decimalLoc = lenNum-5; var afterDecimal = numObj.substr(decimalLoc, 5); var beforeDecimal = numObj.substr(0, decimalLoc); return beforeDecimal + '.' + afterDecimal; }	function prepPointArray(pointArray){ var sReturn = ''; var sIndCoords; for (i=0;i<pointArray.length;i++){ sReturn += (expandNumber(new String(pointArray[i].y)) + ',' + expandNumber(new String(pointArray[i].x))); if (i<pointArray.length-1) sReturn += ','; }		return sReturn; }	function getLastLegDistance(xArray, yArray){ var distanceToReturn = 0; lastPointIdx = xArray.length - 1; secondToLastPointIdx = xArray.length - 2; if (xArray.length > 1) { var fLastLeg; distanceToReturn = returnDistanceInMiles(yArray[lastPointIdx], xArray[lastPointIdx], yArray[secondToLastPointIdx], xArray[secondToLastPointIdx]); }		return distanceToReturn; }	function removeLastLeg { if (xArray.length > 0) { xArray.pop; yArray.pop; pointArray.pop; gPointArray.pop; drawPolyLine(gPointArray); updateDistances(xArray, yArray, REMOVE); } else { alert('No points to remove'); }	}	function encodePolyline(a) { var p = a.split(','); var d = ''; var xo=0; var yo=0; for(c=0;c<p.length;c+=2) { x = p[c]; xd = x - xo; xo = x;			f = (Math.abs(xd) << 1) - (xd<0); do { e = f & 31; f>>=5; if(f){e|=32}; d+=String.fromCharCode(e+63); } while(f!=0); y = p[c+1]; yd = y - yo; yo = y;			f = (Math.abs(yd)<<1)-(yd<0); do { e = f & 31; f>>=5; if(f){e|=32}; d+=String.fromCharCode(e+63); } while (f != 0); }		return d;	} function getQuerystringParameter(paramName){ var sReturnStr = ''; var queryStringObj = new String(location.href) var paramNameObj = new String(paramName+'='); //queryStringObj = queryStringObj.toLowerCase; paramNameObj = paramNameObj.toLowerCase; //parameters were sent if (queryStringObj.indexOf('?') > -1) { var qStringArray = queryStringObj.split('?'); if (qStringArray[1].length > 0) { var allParams = qStringArray[1] var paramArray = allParams.split("&"); for (i=0; i<= paramArray.length-1; i++){ var origCaseFullParam = paramArray[i]; var lcaseFullParam = paramArray[i].toLowerCase; if (lcaseFullParam.indexOf(paramNameObj) > -1) sReturnStr = unescape(origCaseFullParam.substr(paramNameObj.length)); }			}		}		return sReturnStr; }	function createPermalink{ var curCenterX = map.getCenterLatLng.x;		var curCenterY = map.getCenterLatLng.y;		var sPoints = ''; if (gPointArray.length > 0) { sPoints = encodePolyline(prepPointArray(gPointArray)); }		var locationString = new String(location.href); var locationArr = locationString.split('?'); locationString = locationArr[0]; location.href=locationString + '?centerX=' + escape(curCenterX) + '&centerY=' + escape(curCenterY) + '&zl=' + new String(map.getZoomLevel) + '&polyline=' + escape(sPoints); }	function decodePolyline(a) { var b=a.length; var c=0; var d=new Array; var e=0; var f=0; while(c < b){ var g;			var h=0; var i=0; do{ g=a.charCodeAt(c++)-63; i = i | (g&31)<<h; h = h + 5 }while(g>=32); var l;			if (i & 1){ l = ~(i >> 1); } else { l = i >> 1; }			e = e + l;			d.push(e); h=0; i=0; do{ g=a.charCodeAt(c++)-63; i = i | (g&31)<<h; h = h + 5; }while(g>=32); var m			if (i & 1) m = ~(i >> 1); else m = i >> 1 f = f + m;			d.push(f) }		return d;	} function clearLinkHandler{ if (bRecordPoints) { if (confirm("Are you sure you want to clear the route you've created?\nClicking OK to will clear all points and stop recording.\nClicking Cancel will continue recording and leave points as they are.")) { //distances array slightly different from all the other arrays; //it's initialized with a first element of 0. distancesArray.splice(1,distancesArray.length-1); legArray.splice(0,legArray.length); pointArray.splice(0,pointArray.length); gPointArray.splice(0,gPointArray.length); xArray.splice(0,xArray.length); yArray.splice(0,yArray.length); document.controlPanel.mileage.value='0'; document.controlPanel.lastLeg.value='0'; map.clearOverlays; bRecordPoints=false; document.controlPanel.startRecording.value='Start recording'; }		} else { alert('No points to clear'); }	}	function nextLevel{ //thanks to Matt @ google maps group, http://groups-beta.google.com/group/Google-Maps/browse_frm/thread/a6ff645e0cc619c7/f23bd3f1258adcea?q=random+levels&rnum=1#f23bd3f1258adcea var sReturnChar var r = Math.random; //if (r < 0.65) //	sReturnChar = '?'; //		//else if (r < 0.92) //	sReturnChar = '@'; //			//else if (r < 0.97) sReturnChar = 'A'; return sReturnChar }	function drawPolyLine(gPointArray){ map.clearOverlays; //this function may be called from removeLastLeg, in which case //we still want to clear points (above) but don't want to draw a new one. if (gPointArray.length > 0) { //start var startMarker = new GMarker(gPointArray[0]); map.addOverlay(startMarker); //end var EndMarker = new GMarker(gPointArray[gPointArray.length-1]); map.addOverlay(EndMarker); routePolyline = map.addOverlay(new GPolyline(gPointArray)); }	}	function flipMileage{ var multiplier = getCurrentMultiplier; applyUnitsMultiplier(document.controlPanel.mileage); applyUnitsMultiplier(document.controlPanel.lastLeg); }	function getCurrentMultiplier{ var unitInput = document.controlPanel.units; var curValue; if (unitInput[0].checked) { curValue = MILES; } else if (unitInput[1].checked) { curValue = KILOMETERS; }		var multiplier; if (curValue==KILOMETERS) { multiplier = 1.609345; } else { multiplier = 1.0; }		return multiplier; }	function returnDistanceInChosenUnits(valueToApplyTo){ var multiplier = getCurrentMultiplier; return valueToApplyTo * multiplier; }		//]]>