Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit

  • Loading branch information...
commit 87d7e1e40f53bff062ad04fef1b05072da95267c 0 parents
Aidan Findlater authored

Showing 4 changed files with 615 additions and 0 deletions. Show diff stats Hide diff stats

  1. +3 0  README
  2. +55 0 index.html
  3. +85 0 js/encoder.js
  4. +472 0 js/polyline.js
3  README
... ... @@ -0,0 +1,3 @@
  1 +See http://www.aidanfindlater.com/2009/01/09/google-maps-api-encoding-a-polyline-from-a-gdirection-object/ or the hosted version at http://projects.aidanfindlater.com/gdirectionsencoder/
  2 +
  3 +Please note that you will need your own Google Maps API key to host this yourself.
55 index.html
... ... @@ -0,0 +1,55 @@
  1 +<html>
  2 +<head>
  3 + <title>Google Maps Directions to Polyline Encoder</title>
  4 + <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAArWABZIDn9rFfEf8juzXn7hT2yXp_ZAY8_ufC3CFXhHIE1NvwkxSwytzHjxZeW43tkdAkFaIp0ZjfXw&sensor=false" type="text/javascript"></script>
  5 + <script type="text/javascript">
  6 + function initialize() {
  7 + if (GBrowserIsCompatible()) {
  8 + document.map = new GMap2(document.getElementById("map_canvas"));
  9 + document.map.setCenter(new GLatLng(55.37911,-97.910156), 3);
  10 + document.map.addControl(new GLargeMapControl());
  11 + document.map.addControl(new GMapTypeControl());
  12 + }
  13 + }
  14 + </script>
  15 + <script type="text/javascript" src="http://code.google.com/apis/maps/documentation/include/polyline.js"></script>
  16 + <script type="text/javascript" src="js/encoder.js"></script>
  17 +</head>
  18 +<body onload="initialize()" onunload="GUnload()">
  19 + <h1>Google Maps Directions to Polyline Encoder</h1>
  20 +
  21 + <div id="map_canvas" style="width: 400px; height: 300px; float: left; margin-right:15px;"></div>
  22 +
  23 + <table>
  24 + <tbody>
  25 + <tr>
  26 + <td colspan="2"><div id="message"></div></td>
  27 + </tr>
  28 +
  29 + <tr>
  30 + <td>List of waypoints:<br />(one per line)</td>
  31 + <td><textarea id="waypointList" cols="50" rows="5"></textarea></td>
  32 + </tr>
  33 + <tr>
  34 + <td colspan="2">
  35 + <input type="button" value="Encode" onclick="encodeDirections(parseWaypoints());"/>
  36 + </td>
  37 + </tr>
  38 +
  39 + <tr>
  40 + <td>Encoded Polyline:</td>
  41 + <td><textarea class="encodeBox" id="encodedPolyline" cols="50" rows="5"></textarea></td>
  42 + </tr>
  43 + <tr>
  44 + <td>Encoded Levels:</td>
  45 + <td><textarea class="encodeBox" id="encodedLevels" cols="50" rows="5"></textarea></td>
  46 + </tr>
  47 + <tr>
  48 + <td colspan="2">
  49 + <input type="button" value="Draw" onclick="drawEncodedPoints();"/>
  50 + </td>
  51 + </tr>
  52 + </tbody>
  53 + </table>
  54 +</body>
  55 +</html>
85 js/encoder.js
... ... @@ -0,0 +1,85 @@
  1 +// Error messages from http://www.geocodezip.com/example_geo2.asp
  2 +var reasons = [];
  3 +reasons[G_GEO_SUCCESS] = "Success";
  4 +reasons[G_GEO_MISSING_ADDRESS] = "Missing Address: The address was either missing or had no value.";
  5 +reasons[G_GEO_UNKNOWN_ADDRESS] = "Unknown Address: No corresponding geographic location could be found for the specified address.";
  6 +reasons[G_GEO_UNAVAILABLE_ADDRESS]= "Unavailable Address: The geocode for the given address cannot be returned due to legal or contractual reasons.";
  7 +reasons[G_GEO_BAD_KEY] = "Bad Key: The API key is either invalid or does not match the domain for which it was given";
  8 +reasons[G_GEO_TOO_MANY_QUERIES] = "Too Many Queries: The daily geocoding quota for this site has been exceeded.";
  9 +reasons[G_GEO_SERVER_ERROR] = "Server error: The geocoding request could not be successfully processed.";
  10 +reasons[G_GEO_BAD_REQUEST] = "A directions request could not be successfully parsed.";
  11 +reasons[G_GEO_MISSING_QUERY] = "No query was specified in the input.";
  12 +reasons[G_GEO_UNKNOWN_DIRECTIONS] = "The GDirections object could not compute directions between the points.";
  13 +
  14 +
  15 +function parseWaypoints() {
  16 + var waypoints = document.getElementById('waypointList').value.split('\n');
  17 + return waypoints;
  18 +}
  19 +
  20 +function encodeDirections(waypoints) {
  21 + points = [];
  22 +
  23 + var directions = new GDirections();
  24 + directions.loadFromWaypoints(waypoints, {'getPolyline':true});
  25 +
  26 + GEvent.addListener(directions, 'load', function() {
  27 + var p = directions.getPolyline();
  28 + console.log(p);
  29 + // Introspects the polyline to get lat/lng and levels data
  30 + var q = r = null;
  31 +
  32 + // Get the points first so that we can use its length to identify the levels array
  33 + // We identify it by the 'x' and 'y' variables in its first element
  34 + for (var it in p) {
  35 + if (typeof p[it] == 'object' && p[it] != null
  36 + && typeof p[it][0] != 'undefined' && p[it][0] != null
  37 + && typeof p[it][0].x != 'undefined' && p[it][0].x != null)
  38 + q = p[it];
  39 + }
  40 +
  41 + // Now find the levels data
  42 + // It should be an array of numbers of the same length as the points array with values between 0 and 3
  43 + for (var it in p) {
  44 + if (typeof p[it] == 'object' && p[it] != null
  45 + && typeof p[it][0] == 'number' && p[it][0] != null
  46 + && p[it][0] >= 0 && p[it][0] <= 3
  47 + && p[it].length == q.length)
  48 + r = p[it];
  49 + }
  50 +
  51 + console.log(q.length);
  52 + console.log(r.length);
  53 +
  54 + for (var i=0; i<q.length; i++) {
  55 + points.push({
  56 + Latitude: q[i].y,
  57 + Longitude: q[i].x,
  58 + Level: r[i]
  59 + });
  60 + }
  61 + createEncodings(true);
  62 + });
  63 +
  64 + GEvent.addListener(directions, 'error', function() {
  65 + var stat = this.getStatus();
  66 + document.getElementById('message').innerHTML = '<p><strong>Error ' + stat.code + ':</strong> ' + reasons[stat.code] + '</p>';
  67 + });
  68 +}
  69 +
  70 +function drawEncodedPoints() {
  71 + if (document.overlay) {
  72 + document.map.removeOverlay(document.overlay);
  73 + }
  74 +
  75 + document.overlay = GPolyline.fromEncoded({
  76 + color: '#0000FF',
  77 + weight: 10,
  78 + opacity: 0.5,
  79 + points: document.getElementById('encodedPolyline').value,
  80 + levels: document.getElementById('encodedLevels').value,
  81 + zoomFactor: 32,
  82 + numLevels: 4
  83 + });
  84 + document.map.addOverlay(document.overlay);
  85 +}
472 js/polyline.js
... ... @@ -0,0 +1,472 @@
  1 +// From http://code.google.com/apis/maps/documentation/include/polyline.js
  2 +
  3 +var points = [];
  4 +var marker = null;
  5 +var highlighted_marker = null;
  6 +var point_markers = [];
  7 +
  8 +// Add a point to the points list.
  9 +function addPoint() {
  10 + var lat = document.getElementById('txtLatitude').value;
  11 + var pLat = parseFloat(lat);
  12 +
  13 + if (pLat.toString() != lat) {
  14 + alert('Invalid latitude entered. Must be in range of -90 to 90');
  15 + return;
  16 + }
  17 +
  18 + if (pLat < -90 || pLat > 90) {
  19 + alert('Invalid latitude entered. Must be in range of -90 to 90');
  20 + return;
  21 + }
  22 +
  23 + var lng = document.getElementById('txtLongitude').value;
  24 + var pLong = parseFloat(lng);
  25 +
  26 + if (pLong.toString() != lng) {
  27 + alert('Invalid longitude entered. Must be in range of -180 to 180');
  28 + return;
  29 + }
  30 +
  31 + if (pLong < -180 || pLong > 180) {
  32 + alert('Invalid longitude entered. Must be in range of -180 to 180');
  33 + return;
  34 + }
  35 +
  36 + var level = document.getElementById('txtLevel').value;
  37 + var pLevel = parseInt(level);
  38 +
  39 + if (pLevel.toString() != level) {
  40 + alert('Invalid minimum level entered. Must be in range of 0 to 3');
  41 + return;
  42 + }
  43 +
  44 + if (pLevel < 0 || pLevel > 3) {
  45 + alert('Invalid minimum level entered. Must be in range of 0 to 3');
  46 + return;
  47 + }
  48 +
  49 + createPoint(lat, lng, pLevel);
  50 + createEncodings(false);
  51 +}
  52 +
  53 +// Returns the index of the marker in the polyline.
  54 +function findMarkerIndex(point_marker) {
  55 + var index = -1;
  56 +
  57 + for (var i = 0; i < point_markers.length; ++i) {
  58 + if (point_markers[i] == point_marker) {
  59 + index = i;
  60 + break;
  61 + }
  62 + }
  63 +
  64 + return index;
  65 +}
  66 +
  67 +// Creates a point and adds it to both the polyline and the list.
  68 +function createPoint(lat, lng, pLevel) {
  69 + addPointItem(lat, lng, pLevel);
  70 +
  71 + var newPoint = {
  72 + Latitude: lat,
  73 + Longitude: lng,
  74 + Level: pLevel
  75 + };
  76 +
  77 + points.push(newPoint);
  78 +
  79 + if (marker) {
  80 + document.map.removeOverlay(marker);
  81 + marker = null;
  82 + }
  83 +
  84 + var point_marker = createPointMarker(new GLatLng(lat, lng), false);
  85 + document.map.addOverlay(point_marker);
  86 + point_markers.push(point_marker);
  87 +}
  88 +
  89 +// Creates a marker representing a point in the polyline.
  90 +function createPointMarker(point, highlighted) {
  91 + var clr = highlighted ? "yellow" : "blue";
  92 +
  93 + var point_marker = createMarker(point, clr);
  94 + point_marker.enableDragging();
  95 +
  96 + GEvent.addListener(point_marker, "drag", function() {
  97 + var index = findMarkerIndex(point_marker);
  98 +
  99 + if (index >= 0) {
  100 + var nLat = point_marker.getPoint().lat();
  101 + var nLng = point_marker.getPoint().lng();
  102 +
  103 + var pLevel = points[index].Level;
  104 +
  105 + var modifiedPoint = {
  106 + Latitude: nLat,
  107 + Longitude: nLng,
  108 + Level: pLevel
  109 + };
  110 +
  111 + points[index] = modifiedPoint;
  112 + createEncodings(false);
  113 + document.getElementById('pointList').options[index]
  114 + = new Option('(' + nLat + ',' + nLng + ') Level: ' + pLevel, index);
  115 + }
  116 + });
  117 +
  118 + GEvent.addListener(point_marker, "click", function() {
  119 + highlight(findMarkerIndex(point_marker));
  120 + });
  121 +
  122 + return point_marker;
  123 +}
  124 +
  125 +// Add an option to the points list with the specified information.
  126 +function addPointItem(lat, lng, pLevel) {
  127 + var displayPoint = new Option('(' + lat + ',' + lng + ') Level: ' + pLevel,
  128 + points.length);
  129 + document.getElementById('pointList').options.add(displayPoint);
  130 +}
  131 +
  132 +// Highlights the point specified by index in both the map and the point list.
  133 +function highlight(index) {
  134 + var pointList = document.getElementById('pointList');
  135 +
  136 + if (index < pointList.length) {
  137 + pointList.selectedIndex = index;
  138 + }
  139 +
  140 + if (point_markers[index] != null
  141 + && point_markers[index] != highlighted_marker) {
  142 + document.map.removeOverlay(point_markers[index]);
  143 + }
  144 +
  145 + if (highlighted_marker != null) {
  146 + var oldIndex = findMarkerIndex(highlighted_marker);
  147 + document.map.removeOverlay(highlighted_marker);
  148 +
  149 + if (oldIndex != index) {
  150 + point_markers[oldIndex]
  151 + = createPointMarker(highlighted_marker.getPoint(), false);
  152 + document.map.addOverlay(point_markers[oldIndex]);
  153 + }
  154 + }
  155 +
  156 + highlighted_marker = createPointMarker(point_markers[index].getPoint(),
  157 + true);
  158 + point_markers[index] = highlighted_marker;
  159 + document.map.addOverlay(highlighted_marker);
  160 +}
  161 +
  162 +// Encode a signed number in the encode format.
  163 +function encodeSignedNumber(num) {
  164 + var sgn_num = num << 1;
  165 +
  166 + if (num < 0) {
  167 + sgn_num = ~(sgn_num);
  168 + }
  169 +
  170 + return(encodeNumber(sgn_num));
  171 +}
  172 +
  173 +// Encode an unsigned number in the encode format.
  174 +function encodeNumber(num) {
  175 + var encodeString = "";
  176 +
  177 + while (num >= 0x20) {
  178 + encodeString += (String.fromCharCode((0x20 | (num & 0x1f)) + 63));
  179 + num >>= 5;
  180 + }
  181 +
  182 + encodeString += (String.fromCharCode(num + 63));
  183 + return encodeString;
  184 +}
  185 +
  186 +// Delete *all* the points from the polyline, with confirmation dialog before
  187 +// deletion.
  188 +function deleteAllPoints() {
  189 + var deleteConfirm = confirm("Are you sure you want to remove all the points"
  190 + + " from this polyline?");
  191 +
  192 + if (deleteConfirm) {
  193 + document.getElementById('pointList').options.length = 0;
  194 + points = [];
  195 + deleteAllMarkers();
  196 + createEncodings();
  197 + }
  198 +}
  199 +
  200 +// Deletes all the markers for the points in the polyline
  201 +function deleteAllMarkers() {
  202 + for(var i = 0; i < point_markers.length; ++i) {
  203 + document.map.removeOverlay(point_markers[i]);
  204 + }
  205 +
  206 + point_markers = [];
  207 + highlighted_marker = null;
  208 +}
  209 +
  210 +// Delete a point from the polyline.
  211 +function deletePoint() {
  212 + if (points.length > 0) {
  213 + var point_index = document.getElementById('pointList').selectedIndex;
  214 +
  215 + if (point_index >= 0 && point_index < points.length) {
  216 + points.splice(point_index, 1);
  217 +
  218 + if (highlighted_marker == point_markers[point_index]) {
  219 + highlighted_marker = null;
  220 + }
  221 +
  222 + document.map.removeOverlay(point_markers[point_index]);
  223 + point_markers.splice(point_index, 1);
  224 + document.getElementById('pointList').options[point_index] = null;
  225 + createEncodings();
  226 + }
  227 +
  228 + if (points.length > 0) {
  229 + if (point_index == 0) {
  230 + point_index++;
  231 + }
  232 +
  233 + highlight(point_index - 1);
  234 + }
  235 + }
  236 +}
  237 +
  238 +// Try to encode an unsigned number. Used by the documentation.
  239 +function tryEncode() {
  240 + var txtValue = document.getElementById('txtNumber').value;
  241 + if (parseInt(txtValue).toString() == txtValue) {
  242 + document.getElementById('cdeValue').innerHTML
  243 + = encodeNumber(parseInt(txtValue));
  244 + }else{
  245 + document.getElementById('cdeValue').innerHTML = '(None)';
  246 + }
  247 +}
  248 +
  249 +// Try to encode a signed number. Used by the documentation.
  250 +function trySignEncode() {
  251 + var txtValue = document.getElementById('txtSignNumber').value;
  252 + if (parseInt(txtValue).toString() == txtValue) {
  253 + document.getElementById('cdeSignValue').innerHTML
  254 + = encodeSignedNumber(parseInt(txtValue));
  255 + }else{
  256 + document.getElementById('cdeSignValue').innerHTML = '(None)';
  257 + }
  258 +}
  259 +
  260 +// Create the encoded polyline and level strings. If moveMap is true
  261 +// move the map to the location of the first point in the polyline.
  262 +function createEncodings(moveMap) {
  263 + var i = 0;
  264 +
  265 + var plat = 0;
  266 + var plng = 0;
  267 +
  268 + var encoded_points = "";
  269 + var encoded_levels = "";
  270 +
  271 + for(i = 0; i < points.length; ++i) {
  272 + var point = points[i];
  273 + var lat = point.Latitude;
  274 + var lng = point.Longitude;
  275 + var level = point.Level;
  276 +
  277 + var late5 = Math.round(lat * 1e5);
  278 + var lnge5 = Math.round(lng * 1e5);
  279 +
  280 + dlat = late5 - plat;
  281 + dlng = lnge5 - plng;
  282 +
  283 + plat = late5;
  284 + plng = lnge5;
  285 +
  286 + encoded_points += encodeSignedNumber(dlat) + encodeSignedNumber(dlng);
  287 + encoded_levels += encodeNumber(level);
  288 + }
  289 +
  290 + // move if moveMap is true.
  291 + if (moveMap) {
  292 + document.map.setCenter(
  293 + new GLatLng(points[0].Latitude, points[0].Longitude),
  294 + document.map.getZoom());
  295 + }
  296 +
  297 + document.getElementById('encodedLevels').value = encoded_levels;
  298 + document.getElementById('encodedPolyline').value = encoded_points;
  299 +
  300 + if (document.overlay) {
  301 + document.map.removeOverlay(document.overlay);
  302 + }
  303 +
  304 + if (points.length > 1) {
  305 + document.overlay = GPolyline.fromEncoded({color: "#0000FF",
  306 + weight: 10,
  307 + points: encoded_points,
  308 + zoomFactor: 32,
  309 + levels: encoded_levels,
  310 + numLevels: 4
  311 + });
  312 +
  313 + document.map.addOverlay(document.overlay);
  314 + }
  315 +}
  316 +
  317 +function centerMap() {
  318 + var address = document.getElementById('txtAddress').value;
  319 +
  320 + if (address.length > 0) {
  321 + var geocoder = new GClientGeocoder();
  322 +
  323 + geocoder.getLatLng(address,
  324 + function(point) {
  325 + if (!point) {
  326 + alert('Address "' + address + '" not found');
  327 + } else {
  328 + document.map.setCenter(point, 13);
  329 + }
  330 + });
  331 + }
  332 +}
  333 +
  334 +// Decode an encoded polyline into a list of lat/lng tuples.
  335 +function decodeLine (encoded) {
  336 + var len = encoded.length;
  337 + var index = 0;
  338 + var array = [];
  339 + var lat = 0;
  340 + var lng = 0;
  341 +
  342 + while (index < len) {
  343 + var b;
  344 + var shift = 0;
  345 + var result = 0;
  346 + do {
  347 + b = encoded.charCodeAt(index++) - 63;
  348 + result |= (b & 0x1f) << shift;
  349 + shift += 5;
  350 + } while (b >= 0x20);
  351 + var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
  352 + lat += dlat;
  353 +
  354 + shift = 0;
  355 + result = 0;
  356 + do {
  357 + b = encoded.charCodeAt(index++) - 63;
  358 + result |= (b & 0x1f) << shift;
  359 + shift += 5;
  360 + } while (b >= 0x20);
  361 + var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
  362 + lng += dlng;
  363 +
  364 + array.push([lat * 1e-5, lng * 1e-5]);
  365 + }
  366 +
  367 + return array;
  368 +}
  369 +
  370 +// Decode an encoded levels string into a list of levels.
  371 +function decodeLevels(encoded) {
  372 + var levels = [];
  373 +
  374 + for (var pointIndex = 0; pointIndex < encoded.length; ++pointIndex) {
  375 + var pointLevel = encoded.charCodeAt(pointIndex) - 63;
  376 + levels.push(pointLevel);
  377 + }
  378 +
  379 + return levels;
  380 +}
  381 +
  382 +// Decode the supplied encoded polyline and levels.
  383 +function decode() {
  384 + var encoded_points = document.getElementById('encodedPolyline').value;
  385 + var encoded_levels = document.getElementById('encodedLevels').value;
  386 +
  387 + if (encoded_points.length==0 || encoded_levels.length==0) {
  388 + return;
  389 + }
  390 +
  391 + var enc_points = decodeLine(encoded_points);
  392 + var enc_levels = decodeLevels(encoded_levels);
  393 +
  394 + if (enc_points.length==0 || enc_levels.length==0) {
  395 + return;
  396 + }
  397 +
  398 + if (enc_points.length != enc_levels.length) {
  399 + alert('Point count and level count do not match');
  400 + return;
  401 + }
  402 +
  403 + deleteAllMarkers();
  404 + document.getElementById('pointList').options.length = 0;
  405 + points = [];
  406 +
  407 + for (var i = 0; i < enc_points.length; ++i) {
  408 + createPoint(enc_points[i][0], enc_points[i][1], enc_levels[i]);
  409 + }
  410 +
  411 + createEncodings(true);
  412 +}
  413 +
  414 +function createMarker(point, color) {
  415 + var f = new GIcon();
  416 + f.image = "http://labs.google.com/ridefinder/images/mm_20_" + color
  417 + + ".png";
  418 + f.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
  419 + f.iconSize = new GSize(12,20);
  420 + f.shadowSize = new GSize(22,20);
  421 + f.iconAnchor = new GPoint(6,20);
  422 + f.infoWindowAnchor = new GPoint(6,1);
  423 + f.infoShadowAnchor = new GPoint(13,13);
  424 +
  425 + newMarker = new GMarker(point,
  426 + {icon: f,
  427 + draggable: true});
  428 +
  429 + return newMarker;
  430 +}
  431 +
  432 +// Create the Google Map to be used.
  433 +function createMap() {
  434 + if (!GBrowserIsCompatible()) {
  435 + alert('Your browser is not compatible with the Google Maps API');
  436 + return;
  437 + }
  438 +
  439 + document.map = new GMap2(document.getElementById("map_canvas"));
  440 + document.map.setCenter(new GLatLng(37.4419, -122.1419), 13);
  441 + document.map.addControl(new GSmallMapControl());
  442 + document.map.addControl(new GMapTypeControl());
  443 +
  444 + GEvent.addListener(document.map, "click", function(overlay, point) {
  445 + document.getElementById('txtLatitude').value = point.y;
  446 + document.getElementById('txtLongitude').value = point.x;
  447 +
  448 + if (marker == null) {
  449 + marker = createMarker(point, "green");
  450 + marker.enableDragging();
  451 +
  452 + GEvent.addListener(marker, "drag", function() {
  453 + document.getElementById('txtLatitude').value = marker.getPoint().y;
  454 + document.getElementById('txtLongitude').value = marker.getPoint().x;
  455 + });
  456 +
  457 + document.map.addOverlay(marker);
  458 + } else {
  459 + marker.setPoint(point);
  460 + }
  461 + });
  462 +}
  463 +
  464 +// Move the map to the selected point in the point list.
  465 +function jumpToPoint() {
  466 + var pointList = document.getElementById('pointList');
  467 + if (pointList.selectedIndex >= 0) {
  468 + var point = points[pointList.selectedIndex];
  469 + document.map.setCenter(new GLatLng(point.Latitude, point.Longitude),
  470 + document.map.getZoom());
  471 + }
  472 +}

0 comments on commit 87d7e1e

Please sign in to comment.
Something went wrong with that request. Please try again.