/* GMapToGPX 6.4k Originally based in part on the "Improved MSN and Google GPS GPX Waypoint Extraction" bookmarklet described at http://badsegue.org/archives/2005/04/21/ Josh Larios August 3, 2005 - February 12, 2014 WARNING: Highly dependent on internal formats that aren't part of the API, so subject to complete breakdown at any time, outside my control. Gmap-pedometer elevation code courtesy of Mathew O'Brien. 3/05/2007 - HeyWhatsThat.com code by mk -at- heywhatsthat.com 10/09/2007 - Allpoints speed improvement by Kyle Yost TO DO: Separate out gpx writing and point/track extraction, so I can load some array up front regardless of whether it's a google map, a pedometer, heywhatsthat, or whatever, and then just print the damn gpx once. */ var error = 0; var version = '6.4k'; var googledoc = ""; // will hold retrieved google info var googleurl = ""; var gpxvar = ""; // will hold gHomeVPage structure, even for IE var routes = new Array(); var polylines = new Array(); var milestones = new Array(); var yelp = new Array(); var googlepage; // Will hold the page element that gets toggled. May change. var charset; function fixup (foo) { foo = foo.replace(/\\x3e/g, '>'); foo = foo.replace(/\\x3c/g, '<'); foo = foo.replace(/\\x26/g, '&'); foo = foo.replace(/\\x42/g, '"'); foo = foo.replace(/\\x3d/g, '='); foo = foo.replace(/\\u003e/g, '>'); foo = foo.replace(/\\u003c/g, '<'); foo = foo.replace(/\\u0026/g, '&'); foo = foo.replace(/\\042/g, '"'); foo = foo.replace(/"*polylines"*:\s*/g, 'polylines:'); foo = foo.replace(/"*markers"*:\s*/g, 'markers:'); foo = foo.replace(/"*id"*:\s*/g, 'id:'); foo = foo.replace(/"*lat"*:\s*/g, 'lat:'); foo = foo.replace(/"*lng"*:\s*/g, 'lng:'); foo = foo.replace(/"*laddr"*:\s*/g, 'laddr:'); foo = foo.replace(/"*points"*:\s*/g, 'points:'); foo = foo.replace(/\\"/g, '"'); foo = foo.replace(/\"/g, '\''); return foo; } function callInProgress (xmlhttp) { switch (xmlhttp.readyState) { case 1: case 2: case 3: return true; break; // Case 4 and 0 default: return false; break; } } // Synchronous, with an alarm to catch timeouts (30 seconds) // No idea if this is the best way to do this, but for sure the best way I // came up with at 3 in the morning. function loadXMLDoc(url) { var req; var timeoutid; if (window.XMLHttpRequest) { req = new XMLHttpRequest(); showstatusdiv('Loading...'); timeoutid = window.setTimeout( function(){if(callInProgress(req)){req.abort();}}, 30000); req.open("GET", url, false); req.send(null); window.clearTimeout(timeoutid); hidestatusdiv(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); if (req) { showstatusdiv('Loading...'); timeoutid = window.setTimeout( function(){if(callInProgress(req)){req.abort();}}, 30000); req.open("GET", url, false); req.send(); window.clearTimeout(timeoutid); hidestatusdiv(); } } if (req.readyState == 4) { // only if "OK" if (req.status == 200) { return(req.responseText); } else { showstatusdiv('Error ' + req.status + ' getting google data: ' + req.statusText); return(''); } } else { showstatusdiv('Error: loadXMLDoc continued with readystate: ' + req.readyState); return(''); } } function hidestatusdiv() { var statusbox; if (statusbox = document.getElementById("statusbox")) { document.body.removeChild(statusbox); } } function showstatusdiv(boxcontents) { hidestatusdiv(); z=document.body.appendChild(document.createElement("div")); z.id = "statusbox"; z.style.position = "absolute"; if (self.pageYOffset != null) { z.style.top = self.pageYOffset + "px"; } else if (document.documentElement.scrollTop != null) { z.style.top = document.documentElement.scrollTop + "px"; } z.style.width = "50%"; z.style.left = "0px"; z.style.background = "#ffffff"; z.style.border = ".3em solid #ff0000"; z.style.padding = ".3em 1.3em .3em .3em"; z.style.zIndex = "1000"; z.innerHTML = '
X
'; z.innerHTML += boxcontents; } // This function is from Google's polyline utility. function decodeLine (encoded) { var len = encoded.length; var index = 0; var array = []; var lat = 0; var lng = 0; while (index < len) { var b; var shift = 0; var result = 0; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1)); lat += dlat; shift = 0; result = 0; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1)); lng += dlng; array.push({"lat": round(lat * 1e-5), "lon": round(lng * 1e-5)}); } return array; } function StringBuffer() { this.buffer = []; } StringBuffer.prototype.append = function append(string) { this.buffer.push(string); return this; }; StringBuffer.prototype.toString = function toString() { return this.buffer.join(""); }; function gmaptogpxdiv(dtype) { var mypoints = null; var qtype = 0; var subtype = 0; /* Determine which type of data we're extracting -- a route, or points of interest. (Or gmap-pedometer/heywhatsthat.) */ if (gpxvar && gpxvar.overlays && gpxvar.overlays.polylines) { qtype = 2; /* FIXME errorbox('

2008.05.07 - GMapToGPX is currently unable to process driving directions, due to a change in the underlying Google Maps code. I\'m working on it.

'); closebox(); return(0); END FIXME */ // Load "polylines" up with the decoded polyline segments for (i = 0; i < gpxvar.overlays.polylines.length; i++) { polylines[i] = decodeLine(gpxvar.overlays.polylines[i].points); } // Stuff the descriptions into the "polylines" array if (segmatch = googledoc.match(/]*class=.?dirsegtext.?.*?>.*?<\/span>/g)) { for (var s = 0; s < segmatch.length; s++) { var route = segmatch[s].replace(/.*dirsegtext_([0-9]+)_([0-9]+).*/, "$1"); var step = segmatch[s].replace(/.*dirsegtext_([0-9]+)_([0-9]+).*/, "$2"); var desc = deamp(segmatch[s].replace(/.*?>(.*?)<\/span>.*/, "$1")); var polyline = gpxvar.drive.trips[0].routes[route].steps[step].polyline; var ppt = gpxvar.drive.trips[0].routes[route].steps[step].ppt; polylines[polyline][ppt].desc = deamp(desc); } } // Figure out which polylines go into which routes for (i = 0; i < gpxvar.drive.trips[0].routes.length; i++) { var start = gpxvar.drive.trips[0].routes[i].steps[0].polyline; var end = gpxvar.drive.trips[0].routes[i].steps[gpxvar.drive.trips[0].routes[i].steps.length - 1].polyline; var route = "route" + i; routes[route] = new Array(); for (n = start; n <= end; n++) { routes[route] = routes[route].concat(polylines[n]); } } // Get the milestone descriptions var msaddrmatch; if (msaddrmatch = gpxvar.panel.match(/]*id=.?sxaddr.*?>.*?<\/div>/g)) { for (var i = 0; i < msaddrmatch.length; i++) { milestones[parseInt(i)] = deamp(msaddrmatch[i].replace(/]*id=.?sxaddr.?.*?>]+>(.*?)<\/div>/, "$1")); } } } else if (googledoc.match(/id:'(A|addr)'/)) { qtype = 1; routes['poi'] = new Array(); for (var i = 0; i < gpxvar.overlays.markers.length; i++) { var desc = gpxvar.overlays.markers[i].laddr; desc = desc.replace(/(.*) \((.*)\)/, "$2 ($1)"); routes['poi'].push({"lat": round(gpxvar.overlays.markers[i].latlng.lat), "lon": round(gpxvar.overlays.markers[i].latlng.lng), "desc": deamp(desc)}); } } /* gmap-pedometer.com */ if ((document.location.hostname.indexOf('gmap-pedometer') >= 0) && (qtype==0) && (self.o) && (self.o[0])) { qtype = 3; } /* Things which work like gmap-pedometer used to. */ if ( (qtype==0) && (self.gLatLngArray) && (self.gLatLngArray[0]) ) { qtype = 3; subtype = 1; } /* HeyWhatsThat.com list of peaks visible from given location */ if (qtype == 0 && location.href.match(/heywhatsthat.com/i) && peaks && peaks.length) { qtype = 4; subtype = 1; } /* Yelp.com search */ if (qtype == 0 && location.href.match(/yelp.com/i) && document.body.innerHTML.match('result_plot_obj.map.addOverlay')) { qtype = 4; subtype = 2; var yelpmatch = document.body.innerHTML.match(/result_plot_obj.map.addOverlay.*?\)\)/g); for (var i = 0; i < yelpmatch.length; i++) { yelp[i] = new Array(); yelp[i].name = deamp(yelpmatch[i].replace(/.*

(.*?)<\/h3>.*/, "$1")); yelp[i].addr = deamp(yelpmatch[i].replace(/.*]*>(.*?)<\/address>.*/, "$1")); yelp[i].lon = yelpmatch[i].replace(/.*Yelp.TSRUrl.*?,.*?,.*?, (.*?),.*/, "$1"); yelp[i].lat = yelpmatch[i].replace(/.*Yelp.TSRUrl.*?,.*?,.*?,.*?, (.*?),.*/, "$1"); } } /* Yelp.com single location */ // var json_biz = {"city":"Seattle","zip":"98102","review_count":6,"name":"Tacos Guaymas - CLOSED","neighborhoods":["Capitol Hill"],"photos":[],"address1":"213 Broadway East","avg_rating":4.000000,"longitude":-122.320999,"address2":null,"phone":"(206) 860-7345","state":"WA","latitude":47.620201,"id":"PidHplYWockrwJpijqUwsg","categories":{}}; if (qtype == 0 && location.href.match(/yelp.com/i) && json_biz) { qtype = 4; subtype = 2; yelp[0] = new Array(); yelp[0].name = json_biz.name; yelp[0].lat = json_biz.latitude; yelp[0].lon = json_biz.longitude; yelp[0].addr = json_biz.address1 + ", "; if (json_biz.address2 != null) { yelp[0].addr += json_biz.address2 + ", "; } yelp[0].addr += json_biz.city + ", " + json_biz.state + " " + json_biz.zip; } /* findmespot.com */ if (qtype == 0 && location.href.match(/findmespot.com/i) && document.getElementsByTagName('iframe')[1] && document.getElementsByTagName('iframe')[1].contentDocument.getElementById('mapForm:inputHidden1').value) { qtype = 4; subtype = 3; } /* logyourrun.com */ if (qtype == 0 && location.href.match(/logyourrun.com/i) && route_polyline) { qtype = 4; subtype = 4; } if (qtype==0) { errorbox('

There doesn\'t seem to be any extractable data on this page.

If there is, but it\'s not detected, please visit the project homepage and leave a bug report, including a link to the page you\'re on right now.

Note: Google Maps mashups (that is, a page with a Google Map on it, but not at google.com) do not automatically work with this utility. If you would like to see GMapToGPX work with a Google Maps mashup site you maintain, please leave a comment on the project page.

'); closebox(); return(0); } /* t contains the text that will be injected into a
overlay */ var t="
"; if (navigator.userAgent.match(/Safari/)) { t+='\ \ '; } t+="
"; t+='
'; t+=''; t+="
"; t+='
'; displaybox(t); } function displaybox(boxcontents) { closebox(); if (googlepage=document.getElementById("page")) { googlepage.style.display='none'; } var z=document.body.appendChild(document.createElement("div")); z.id = "gpxbox"; /* I don't know about this stuff; it came from badsegue. */ z.style.position = "absolute"; if (self.pageYOffset != null) { z.style.top = self.pageYOffset + "px"; } else if (document.documentElement.scrollTop != null) { z.style.top = document.documentElement.scrollTop + "px"; } z.style.width = "99%"; z.style.zIndex = "1000"; z.innerHTML = boxcontents; } function closebox() { var gpxbox; if (gpxbox = document.getElementById("gpxbox")) { document.body.removeChild(gpxbox); } if (googlepage != undefined) { googlepage.style.display='block'; } } function loadabout() { var about = '

GMapToGPX Extractor ' + version + '
'; about += 'A project of Communications From Elsewhere

'; about += '

Usage:

    '; about += '
  • "Track" displays driving directions as a GPX track with one or more track segments, depending on the number of milestones in the directions.
  • '; about += '
  • "Route" displays driving directions as one or more GPX routes.
  • '; about += '
  • "Full" displays driving directions as a GPX track containing one or more track segments, each of which contains every single point on the line Google Maps draws to represent the segment. Use with caution, as long routes may produce huge results.
  • '; about += '
  • "Points" displays driving directions as a list of waypoints for each turn in the route. The waypoints will be in order, but this option is mainly intended for devices which can only handle waypoints, not tracks or routes. In most cases, you should use another option.
  • '; about += '
  • For single or multiple address searches, there are no display options. You get a list of individual waypoints.
  • '; about += '
If you have questions or comments, please visit the project homepage.

'; showstatusdiv(about); } function errorbox(a) { var err = 'GMapToGPX v' + version + " (ERROR)
" + a; showstatusdiv(err); } /* Clean up floating point math errors */ function round(a) { return parseInt(a*1E+5)/1E+5; } function reload(t) { closebox(); if (t==0) { gmaptogpxdiv("route"); } else if (t=="1") { gmaptogpxdiv("track"); } else if (t=="2") { gmaptogpxdiv("points"); } else if (t=="3") { gmaptogpxdiv("allpoints"); } } function deamp(a) { a = a.replace(/
(.+)/g, ", $1"); a = a.replace(/
/g, ''); a = a.replace(/'/g, '\''); a = a.replace(/\\047/g, '\''); a = a.replace(/\\042/g, '\"'); a = a.replace(/ /g, ' '); a = a.replace(/<\/*b>/g, ''); a = a.replace(//g, ''); a = a.replace(/]*?>.*?<\/div>/g, ' '); a = a.replace(/\\\'/g, '\''); a = a.replace(/\\\"/g, '\"'); a = a.replace(/\\x26/g, '&'); a = a.replace(/&/g, '&'); a = a.replace(/&amp;amp;/g, '&amp;'); a = a.replace(/\\n/g, ''); a = a.replace(/\\t/g, ''); a = a.replace(/\s+/g, ' '); a = a.replace(/^\s+/, ''); a = a.replace(/\s+$/, ''); a = a.replace(/<[^>]+>/, ''); // This may be overkill. return a; } /* main() */ if (document.location.hostname.indexOf('google') >= 0) { var kmlurl; if ( (kmlurl = document.getElementById('link').href) && (kmlurl.indexOf('msid=') > 0) ) { kmlurl = kmlurl + '&output=kml'; errorbox('This is a "My Maps" page, which means that the original KML used to create it is available. Please download the KML file (using this link, not the one provided by Google) and convert it using GPSVisualizer.'); error = 1; } if (!error) { // bar_icon_link is the "link to this page" icon. If they change // its name, I need to fix that here. if (googleurl=document.getElementById('link').href) { googleurl = googleurl.replace(/&view=text/, ''); googledoc = loadXMLDoc(googleurl); charset=googledoc.slice(googledoc.indexOf('charset=')); charset=charset.slice(8, charset.indexOf('"')); // Doing this as a regexp was causing firefox to stall out. bah. var encpointblob=googledoc.slice(googledoc.indexOf('gHomeVPage=')); encpointblob=encpointblob.slice(0, encpointblob.indexOf('};') + 2 ); encpointblob=encpointblob.replace(/gHomeVPage/, "gpxvar"); eval(encpointblob); var panel=googledoc.slice(googledoc.indexOf('id="panel_dir"')); panel=panel.slice(0,panel.indexOf('Map data')); gpxvar.panel = panel; googledoc=fixup(googledoc); } } } charset = charset ? charset : "UTF-8"; /* This bit of code was causing Safari to seriously freak out, hence the stylesheet being included above in t, but only for Safari. */ if (! navigator.userAgent.match(/Safari/)) { var styleObject = document.getElementsByTagName("HEAD")[0].appendChild(document.createElement("link")); styleObject.rel="Stylesheet"; styleObject.type="text/css"; styleObject.href="http://www.elsewhere.org/GMapToGPX/menubar.css"; styleObject.id="sst_css"; } if (error != 1) { /* Default action. If it's not a route, the argument doesn't matter. */ gmaptogpxdiv("route"); } else { closebox(); }