Roads API Inspector

The Roads API Inspector is an interactive tool that you can use to try out the Roads API . Here are some suggestions to get started with the tool:

  • Copy a Roads API request URL into the text field and select Plot a Course, to explore the results of a request. You don't need to include an API key.
  • Use the featured examples to experiment with the API.
  • Select a marker for a particular point, to see an info window with details about the point.
  • Open Street View after loading one of the examples to see the markers in the Street View panorama.
  • Toggle the Interpolatesetting to see its effect on the results.
  • Select Toggle Distancesto view or hide the distance between two original points. The unsnapped route is shown as a straight green line from point to point. Select the line to see the distance.

The featured examples are:

  • Example 1: Points along a road in Pyrmont, Sydney.
  • Example 2: Points along a road in Canberra, Australian Capital Territory.
  • Example 3: Points along a road in Canberra with a point that can't be snapped to a road.
  • Example 4: A route in Elkin, NC, with an erratic path that nicely demonstrates the results of toggling the interpolation setting.

View this example full screen .

Try it yourself

JavaScript

 // Replace with your own API key 
 var 
  
 API_KEY 
  
 = 
  
 'YOUR_API_KEY' 
 ; 
 // Icons for markers 
 var 
  
 RED_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/red-dot.png' 
 ; 
 var 
  
 GREEN_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/green-dot.png' 
 ; 
 var 
  
 BLUE_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png' 
 ; 
 var 
  
 YELLOW_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png' 
 ; 
 // URL for places requests 
 var 
  
 PLACES_URL 
  
 = 
  
 'https://maps.googleapis.com/maps/api/place/details/json?' 
  
 + 
  
 'key=' 
  
 + 
  
 API_KEY 
  
 + 
  
 '&placeid=' 
 ; 
 // URL for Speed limits 
 var 
  
 SPEED_LIMIT_URL 
  
 = 
  
 'https://roads.googleapis.com/v1/speedLimits' 
 ; 
 var 
  
 coords 
 ; 
 /** 
 * Current Roads API threshold (subject to change without notice) 
 * @const {number} 
 */ 
 var 
  
 DISTANCE_THRESHOLD_HIGH 
  
 = 
  
 300 
 ; 
 var 
  
 DISTANCE_THRESHOLD_LOW 
  
 = 
  
 200 
 ; 
 /** 
 * @type Array<ExtendedLatLng> 
 */ 
 var 
  
 originals 
  
 = 
  
 []; 
  
 // the original input points, a list of ExtendedLatLng 
 var 
  
 interpolate 
  
 = 
  
 true 
 ; 
 var 
  
 map 
 ; 
 var 
  
 placesService 
 ; 
 var 
  
 originalCoordsLength 
 ; 
 // Settingup Arrays 
 var 
  
 infoWindows 
  
 = 
  
 []; 
 var 
  
 markers 
  
 = 
  
 []; 
 var 
  
 placeIds 
  
 = 
  
 []; 
 var 
  
 polylines 
  
 = 
  
 []; 
 var 
  
 snappedCoordinates 
  
 = 
  
 []; 
 var 
  
 distPolylines 
  
 = 
  
 []; 
 // Symbol that gets animated along the polyline 
 var 
  
 lineSymbol 
  
 = 
  
 { 
  
 path 
 : 
  
 google 
 . 
 maps 
 . 
 SymbolPath 
 . 
 CIRCLE 
 , 
  
 scale 
 : 
  
 8 
 , 
  
 strokeColor 
 : 
  
 '#005db5' 
 , 
  
 strokeWidth 
 : 
  
 '#005db5' 
 }; 
 // Example 1 - Frolick around Sydney 
 var 
  
 eg1 
  
 = 
  
 '-33.870315,151.196532|-33.869979,151.197355|' 
  
 + 
  
 '-33.870044,151.197712|-33.870358,151.198206|' 
  
 + 
  
 '-33.870595,151.198376|-33.870640,151.198398|' 
  
 + 
  
 '-33.870620,151.198449|-33.870951,151.198525|' 
  
 + 
  
 '-33.871040,151.198528|-33.872031,151.198413' 
 ; 
 // Example 2 - Lap around Canberra 
 var 
  
 eg2 
  
 = 
  
 '-35.274346,149.130168|-35.278012,149.129583|' 
  
 + 
  
 '-35.280329,149.129073|-35.280999,149.129293|' 
  
 + 
  
 '-35.281441,149.129846|-35.281945,149.130034|' 
  
 + 
  
 '-35.282825,149.129567|-35.283022,149.128811|' 
  
 + 
  
 '-35.284734,149.128366' 
 ; 
 // Example 3 - Path with unsnappable point 
 var 
  
 eg3 
  
 = 
  
 '-35.274346,149.094000|-35.278012,149.129583|' 
  
 + 
  
 '-35.280329,149.129073|-35.280999,149.129293|' 
  
 + 
  
 '-35.281441,149.129846' 
 ; 
 // Example 4 - Drive erratically in Elkin 
 var 
  
 eg4 
  
 = 
  
 '36.28881,-80.8525|36.287038,-80.85313|36.286161,-80.85369|' 
  
 + 
  
 '36.28654,-80.85418|36.2846,-80.84766|36.28355,-80.84669' 
 ; 
 // Initialize 
 function 
  
 initialize 
 () 
  
 { 
  
 $ 
 ( 
 '#eg1' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg1 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg2' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg2 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg3' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg3 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg4' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg4 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#toggle' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 if 
  
 ( 
 $ 
 ( 
 '#panel' 
 ). 
 css 
 ( 
 "display" 
 ) 
  
 != 
  
 'none' 
 ) 
  
 { 
  
 $ 
 ( 
 '#toggle' 
 ). 
 html 
 ( 
 "+" 
 ); 
  
 $ 
 ( 
 '#panel' 
 ). 
 hide 
 (); 
  
 } 
  
 else 
  
 { 
  
 $ 
 ( 
 '#toggle' 
 ). 
 html 
 ( 
 "&mdash;" 
 ); 
  
 $ 
 ( 
 '#panel' 
 ). 
 show 
 (); 
  
 } 
  
 }); 
  
 // Centre the map on Sydney 
  
 var 
  
 mapOptions 
  
 = 
  
 { 
  
 center 
 : 
  
 { 
 'lat' 
 : 
  
 - 
 33.870315 
 , 
  
 'lng' 
 : 
  
 151.196532 
 }, 
  
 zoom 
 : 
  
 14 
  
 }; 
  
 // Map object 
  
 map 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Map 
 ( 
 document 
 . 
 getElementById 
 ( 
 'map' 
 ), 
  
 mapOptions 
 ); 
  
 // Places object 
  
 placesService 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesService 
 ( 
 map 
 ); 
  
 // Reset the map to a clean state and reset all variables 
  
 // used for displaying each request 
  
 function 
  
 clearMap 
 () 
  
 { 
  
 // Clear the polyline 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 polylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 polylines 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all markers 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 markers 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 markers 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all the distance polylines 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 distPolylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 distPolylines 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all info windows 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
  
 // Empty everything 
  
 polylines 
  
 = 
  
 []; 
  
 markers 
  
 = 
  
 []; 
  
 distPolylines 
  
 = 
  
 []; 
  
 snappedCoordinates 
  
 = 
  
 []; 
  
 placeIds 
  
 = 
  
 []; 
  
 infoWindows 
  
 = 
  
 []; 
  
 $ 
 ( 
 '#unsnappedPoints' 
 ). 
 empty 
 (); 
  
 $ 
 ( 
 '#warningMessage' 
 ). 
 empty 
 (); 
  
 } 
  
 // Parse the value in the input element 
  
 // to get all coordinates 
  
 function 
  
 parseCoordsFromQuery 
 ( 
 input 
 ) 
  
 { 
  
 var 
  
 coords 
 ; 
  
 input 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 if 
  
 ( 
 input 
 . 
 split 
 ( 
 'path=' 
 ). 
 length 
 > 
 1 
 ) 
  
 { 
  
 input 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 // Split on the ampersand to get all params 
  
 var 
  
 parts 
  
 = 
  
 input 
 . 
 split 
 ( 
 '&' 
 ); 
  
 // Check each part to see if it starts with 'path=' 
  
 // grabbing out the coordinates if it does 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 parts 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 parts 
 [ 
 i 
 ]. 
 split 
 ( 
 'path=' 
 ). 
 length 
 > 
 1 
 ) 
  
 { 
  
 coords 
  
 = 
  
 parts 
 [ 
 i 
 ]. 
 split 
 ( 
 'path=' 
 )[ 
 1 
 ]; 
  
 break 
 ; 
  
 } 
  
 } 
  
 } 
  
 else 
  
 { 
  
 coords 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 } 
  
 // Parse the "Lat,Lng|..." coordinates into an array of ExtendedLatLng 
  
 originals 
  
 = 
  
 []; 
  
 var 
  
 points 
  
 = 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 points 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 point 
  
 = 
  
 points 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 ); 
  
 originals 
 . 
 push 
 ({ 
 lat 
 : 
  
 Number 
 ( 
 point 
 [ 
 0 
 ]), 
  
 lng 
 : 
  
 Number 
 ( 
 point 
 [ 
 1 
 ]), 
  
 index 
 : 
 i 
 }); 
  
 } 
  
 return 
  
 coords 
 ; 
  
 } 
  
 // Clear the map of any old data and plot the request 
  
 $ 
 ( 
 '#plot' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 clearMap 
 (); 
  
 bendAndSnap 
 (); 
  
 drawDistance 
 (); 
  
 e 
 . 
 preventDefault 
 (); 
  
 }); 
  
 // Make AJAX request to the snapToRoadsAPI 
  
 // with coordinates parsed from text input element. 
  
 function 
  
 bendAndSnap 
 () 
  
 { 
  
 coords 
  
 = 
  
 parseCoordsFromQuery 
 ( 
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ()); 
  
 location 
 . 
 hash 
  
 = 
  
 coords 
 ; 
  
 $ 
 . 
 ajax 
 ({ 
  
 type 
 : 
  
 'GET' 
 , 
  
 url 
 : 
  
 'https://roads.googleapis.com/v1/snapToRoads' 
 , 
  
 data 
 : 
  
 { 
  
 interpolate 
 : 
  
 $ 
 ( 
 '#interpolate' 
 ). 
 is 
 ( 
 ':checked' 
 ), 
  
 key 
 : 
  
 API_KEY 
 , 
  
 path 
 : 
  
 coords 
  
 }, 
  
 success 
 : 
  
 function 
 ( 
 data 
 ) 
  
 { 
  
 $ 
 ( 
 '#requestURL' 
 ). 
 html 
 ( 
 '<a target="blank" href="' 
  
 + 
  
 this 
 . 
 url 
  
 + 
  
 '">Request URL</a>' 
 ); 
  
 processSnapToRoadResponse 
 ( 
 data 
 ); 
  
 drawSnappedPolyline 
 ( 
 snappedCoordinates 
 ); 
  
 drawOriginals 
 ( 
 originals 
 ); 
  
 fitBounds 
 ( 
 markers 
 ); 
  
 }, 
  
 error 
 : 
  
 function 
 () 
  
 { 
  
 $ 
 ( 
 '#requestURL' 
 ). 
 html 
 ( 
 '<strong>That query didn\'t work :(</strong>' 
  
 + 
  
 '<p>Try looking at the <a href="' 
  
 + 
  
 this 
 . 
 url 
  
 + 
  
 '">Request URL</a></p>' 
 ); 
  
 clearMap 
 (); 
  
 } 
  
 }); 
  
 } 
  
 // Toggle the distance polylines of the original points to show on the maps 
  
 $ 
 ( 
 '#distance' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 distPolylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 distPolylines 
 [ 
 i 
 ]. 
 setVisible 
 ( 
 ! 
 distPolylines 
 [ 
 i 
 ]. 
 getVisible 
 ()); 
  
 } 
  
 // Clear all infoWindows associated with distance polygons on toggle 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 infoWindows 
 [ 
 i 
 ]. 
 dist 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
  
 } 
  
 e 
 . 
 preventDefault 
 (); 
  
 }); 
  
 /** 
 * Compute the distance between each original point and create a polyline 
 * for each pair. Polylines are initially hidden on creation 
 */ 
  
 function 
  
 drawDistance 
 () 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originals 
 . 
 length 
  
 - 
  
 1 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 origin 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLng 
 ( 
 originals 
 [ 
 i 
 ]); 
  
 var 
  
 destination 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLng 
 ( 
 originals 
 [ 
 i 
 + 
 1 
 ]); 
  
 var 
  
 distance 
  
 = 
  
 google 
 . 
 maps 
 . 
 geometry 
 . 
 spherical 
 . 
 computeDistanceBetween 
 ( 
 origin 
 , 
  
 destination 
 ); 
  
 // Round the distance value to two decimal places 
  
 distance 
  
 = 
  
 Math 
 . 
 round 
 ( 
 distance 
  
 * 
  
 100 
 ) 
  
 / 
  
 100 
 ; 
  
 var 
  
 color 
 ; 
  
 var 
  
 weight 
 ; 
  
 if 
  
 ( 
 distance 
 > 
 DISTANCE_THRESHOLD_HIGH 
 ) 
  
 { 
  
 color 
  
 = 
  
 '#CC0022' 
 ; 
  
 weight 
  
 = 
  
 7 
 ; 
  
 } 
  
 else 
  
 if 
  
 ( 
 distance 
 < 
 DISTANCE_THRESHOLD_HIGH 
  
&&  
 distance 
 > 
 DISTANCE_THRESHOLD_LOW 
 ) 
  
 { 
  
 color 
  
 = 
  
 '#FF6600' 
 ; 
  
 weight 
  
 = 
  
 6 
 ; 
  
 } 
  
 else 
  
 { 
  
 color 
  
 = 
  
 '#22CC00' 
 ; 
  
 weight 
  
 = 
  
 5 
 ; 
  
 } 
  
 var 
  
 polyline 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 strokeColor 
 : 
  
 color 
 , 
  
 strokeOpacity 
 : 
  
 0.4 
 , 
  
 strokeWeight 
 : 
  
 weight 
 , 
  
 geodesic 
 : 
  
 true 
 , 
  
 visible 
 : 
  
 false 
 , 
  
 map 
 : 
  
 map 
  
 }); 
  
 polyline 
 . 
 setPath 
 ([ 
 origin 
 , 
  
 destination 
 ]); 
  
 distPolylines 
 . 
 push 
 ( 
 polyline 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 addPolyWindow 
 ( 
 polyline 
 , 
  
 distance 
 , 
  
 i 
 )); 
  
 } 
  
 } 
  
 /** 
 * Add an info window to the polyline displaying the original 
 * points and the distance 
 */ 
  
 function 
  
 addPolyWindow 
 ( 
 polyline 
 , 
  
 distance 
 , 
  
 index 
 ) 
  
 { 
  
 var 
  
 infoWindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 content 
  
 = 
  
 '<div style="width:100%"><p>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 index 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>Coords:</strong> (' 
  
 + 
  
 originals 
 [ 
 index 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 originals 
 [ 
 index 
 ]. 
 lng 
  
 + 
  
 ')' 
  
 + 
  
 '<br>to<br>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 ( 
 index 
 + 
 1 
 ) 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>Coords:</strong> (' 
  
 + 
  
 originals 
 [ 
 index 
 + 
 1 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 originals 
 [ 
 index 
 + 
 1 
 ]. 
 lng 
  
 + 
  
 ')<br><br>' 
  
 + 
  
 '<strong>Distance:  </strong>' 
  
 + 
  
 distance 
  
 + 
  
 ' m<br>' 
 ; 
  
 if 
  
 ( 
 distance 
 > 
 DISTANCE_THRESHOLD_HIGH 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<span style="color:#CC0022;font-style:italic">' 
  
 + 
  
 '*Large distance (>300m) may affect snapping</span><br>' 
  
 + 
  
 'Please see <a href="https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap#parameter_usage" ' 
  
 + 
  
 'target="_blank">Roads API documentation</a>' 
 ; 
  
 } 
  
 content 
  
 += 
  
 '</p></div>' 
 ; 
  
 infoWindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 infoWindow 
 . 
 dist 
  
 = 
  
 true 
 ; 
  
 polyline 
 . 
 addListener 
 ( 
 'click' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 infoWindow 
 . 
 setPosition 
 ( 
 e 
 . 
 latLng 
 ); 
  
 infoWindow 
 . 
 open 
 ( 
 map 
 ); 
  
 }); 
  
 polyline 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 polyline 
 . 
 setOptions 
 ({ 
 strokeOpacity 
 : 
  
 1.0 
 }); 
  
 }); 
  
 polyline 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 polyline 
 . 
 setOptions 
 ({ 
 strokeOpacity 
 : 
  
 0.4 
 }); 
  
 }); 
  
 return 
  
 infoWindow 
 ; 
  
 } 
  
 // Parse the value in the input element 
  
 // to get all coordinates 
  
 function 
  
 getMissingPoints 
 ( 
 originalIndexes 
 , 
  
 originalCoordsLength 
 ) 
  
 { 
  
 var 
  
 unsnappedPoints 
  
 = 
  
 []; 
  
 var 
  
 coordsArray 
  
 = 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ); 
  
 var 
  
 hasMissingCoords 
  
 = 
  
 false 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originalCoordsLength 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 originalIndexes 
 . 
 indexOf 
 ( 
 i 
 ) 
 < 
 0 
 ) 
  
 { 
  
 hasMissingCoords 
  
 = 
  
 true 
 ; 
  
 var 
  
 latlng 
  
 = 
  
 { 
  
 'lat' 
 : 
  
 parseFloat 
 ( 
 coordsArray 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 )[ 
 0 
 ]), 
  
 'lng' 
 : 
  
 parseFloat 
 ( 
 coordsArray 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 )[ 
 1 
 ]) 
  
 }; 
  
 unsnappedPoints 
 . 
 push 
 ( 
 latlng 
 ); 
  
 latlng 
 . 
 unsnapped 
  
 = 
  
 true 
 ; 
  
 } 
  
 } 
  
 return 
  
 unsnappedPoints 
 ; 
  
 } 
  
 // Parse response from snapToRoads API request 
  
 // Store all coordinates in response 
  
 // Calls functions to add markers to map for unsnapped coordinates 
  
 function 
  
 processSnapToRoadResponse 
 ( 
 data 
 ) 
  
 { 
  
 var 
  
 originalIndexes 
  
 = 
  
 []; 
  
 var 
  
 unsnappedMessage 
  
 = 
  
 '' 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 data 
 . 
 snappedPoints 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 latlng 
  
 = 
  
 { 
  
 'lat' 
 : 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 location 
 . 
 latitude 
 , 
  
 'lng' 
 : 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 location 
 . 
 longitude 
  
 }; 
  
 var 
  
 interpolated 
  
 = 
  
 true 
 ; 
  
 if 
  
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
  
 != 
  
 undefined 
 ) 
  
 { 
  
 interpolated 
  
 = 
  
 false 
 ; 
  
 originalIndexes 
 . 
 push 
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
 ); 
  
 latlng 
 . 
 originalIndex 
  
 = 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
 ; 
  
 } 
  
 latlng 
 . 
 interpolated 
  
 = 
  
 interpolated 
 ; 
  
 snappedCoordinates 
 . 
 push 
 ( 
 latlng 
 ); 
  
 placeIds 
 . 
 push 
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 placeId 
 ); 
  
 // Cross-reference the original point and this snapped point. 
  
 latlng 
 . 
 related 
  
 = 
  
 originals 
 [ 
 latlng 
 . 
 originalIndex 
 ]; 
  
 originals 
 [ 
 latlng 
 . 
 originalIndex 
 ]. 
 related 
  
 = 
  
 latlng 
 ; 
  
 } 
  
 var 
  
 unsnappedPoints 
  
 = 
  
 getMissingPoints 
 ( 
  
 originalIndexes 
 , 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ). 
 length 
  
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 unsnappedPoints 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 unsnappedPoints 
 [ 
 i 
 ]); 
  
 var 
  
 infowindow 
  
 = 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 unsnappedPoints 
 [ 
 i 
 ], 
  
 i 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 infowindow 
 ); 
  
 unsnappedMessage 
  
 += 
  
 unsnappedPoints 
 [ 
 i 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 unsnappedPoints 
 [ 
 i 
 ]. 
 lng 
  
 + 
  
 '<br>' 
 ; 
  
 } 
  
 if 
  
 ( 
 unsnappedPoints 
 . 
 length 
 ) 
  
 { 
  
 unsnappedMessage 
  
 = 
  
 '<strong>' 
  
 + 
  
 'These points weren\'t snapped: ' 
  
 + 
  
 '</strong><br>' 
  
 + 
  
 unsnappedMessage 
 ; 
  
 $ 
 ( 
 '#unsnappedPoints' 
 ). 
 html 
 ( 
 unsnappedMessage 
 ); 
  
 } 
  
 if 
  
 ( 
 data 
 . 
 warningMessage 
 ) 
  
 { 
  
 $ 
 ( 
 '#warningMessage' 
 ). 
 html 
 ( 
 '<span style="color:#CC0022;' 
  
 + 
  
 'font-style:italic;font-size:12px">' 
  
 + 
  
 data 
 . 
 warningMessage 
  
 + 
  
 '<br/>' 
  
 + 
  
 '<a target="_blank" href="https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap">https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap</a>' 
 ); 
  
 $ 
 ( 
 '#distance' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 } 
  
 } 
  
 // Draw the polyline for the snapToRoads API response 
  
 // Call functions to add markers and infowindows for each snapped 
  
 // point along the polyline. 
  
 function 
  
 drawSnappedPolyline 
 ( 
 snappedCoords 
 ) 
  
 { 
  
 var 
  
 snappedPolyline 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 path 
 : 
  
 snappedCoords 
 , 
  
 strokeColor 
 : 
  
 '#005db5' 
 , 
  
 strokeWeight 
 : 
  
 6 
 , 
  
 icons 
 : 
  
 [{ 
  
 icon 
 : 
  
 lineSymbol 
 , 
  
 offset 
 : 
  
 '100%' 
  
 }] 
  
 }); 
  
 snappedPolyline 
 . 
 setMap 
 ( 
 map 
 ); 
  
 animateCircle 
 ( 
 snappedPolyline 
 ); 
  
 polylines 
 . 
 push 
 ( 
 snappedPolyline 
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 snappedCoords 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 snappedCoords 
 [ 
 i 
 ]); 
  
 var 
  
 infoWindow 
  
 = 
  
 addDetailedInfoWindow 
 ( 
 marker 
 , 
  
 snappedCoords 
 [ 
 i 
 ], 
  
 placeIds 
 [ 
 i 
 ]); 
  
 infoWindows 
 . 
 push 
 ( 
 infoWindow 
 ); 
  
 } 
  
 } 
  
 // Draw the original input. 
  
 // Call functions to add markers and infowindows for each point. 
  
 function 
  
 drawOriginals 
 ( 
 originalCoords 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originalCoords 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 originalCoords 
 [ 
 i 
 ]); 
  
 var 
  
 infoWindow 
  
 = 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 originalCoords 
 [ 
 i 
 ], 
  
 i 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 infoWindow 
 ); 
  
 } 
  
 } 
  
 // Infowindow used for unsnappable coordinates 
  
 function 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 coords 
 , 
  
 index 
 ) 
  
 { 
  
 var 
  
 infowindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 content 
  
 = 
  
 '<div style="width:99%"><p>' 
  
 + 
  
 '<strong>Lat/Lng:</strong><br>' 
  
 + 
  
 '(' 
  
 + 
  
 coords 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
  
 + 
  
 ')<br>' 
  
 + 
  
 ( 
 index 
  
 != 
  
 undefined 
  
 ? 
  
 '<strong>Index: </strong>' 
  
 + 
  
 index 
  
 : 
  
 '' 
 ) 
  
 + 
  
 '</p></div>' 
 ; 
  
 infowindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 marker 
 , 
  
 'click' 
 , 
  
 function 
 () 
  
 { 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ); 
  
 }); 
  
 return 
  
 infowindow 
 ; 
  
 } 
  
 // Infowindow used for snapped points 
  
 // Makes request to Places Details API to get data about each 
  
 // Place ID. 
  
 // Requests speed limit of each location using Roads SpeedLimit API 
  
 function 
  
 addDetailedInfoWindow 
 ( 
 marker 
 , 
  
 coords 
 , 
  
 placeId 
 ) 
  
 { 
  
 var 
  
 infowindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 placesRequestUrl 
  
 = 
  
 PLACES_URL 
  
 + 
  
 placeId 
 ; 
  
 var 
  
 detailsUrl 
  
 = 
  
 '<a target="_blank" href="' 
  
 + 
  
 placesRequestUrl 
  
 + 
  
 '">' 
  
 + 
  
 placeId 
  
 + 
  
 '</a></li>' 
 ; 
  
 // On click we make a request to the Places API 
  
 // This is to avoid OVER_QUERY_LIMIT if we just requested everything 
  
 // at the same time 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 marker 
 , 
  
 'click' 
 , 
  
 function 
 () 
  
 { 
  
 content 
  
 = 
  
 '<div style="width:99%"><p>' 
 ; 
  
 function 
  
 finishInfoWindow 
 ( 
 placeDetails 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>Place Details: </strong>' 
  
 + 
  
 placeDetails 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>' 
  
 + 
  
 ( 
 coords 
 . 
 interpolated 
  
 ? 
  
 'Coords' 
  
 : 
  
 'Snapped coords' 
 ) 
  
 + 
  
 ': </strong>' 
  
 + 
  
 '(' 
  
 + 
  
 coords 
 . 
 lat 
 . 
 toFixed 
 ( 
 5 
 ) 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
 . 
 toFixed 
 ( 
 5 
 ) 
  
 + 
  
 ')<br>' 
 ; 
  
 if 
  
 ( 
 ! 
 ( 
 coords 
 . 
 interpolated 
 )) 
  
 { 
  
 var 
  
 original 
  
 = 
  
 originals 
 [ 
 coords 
 . 
 originalIndex 
 ]; 
  
 content 
  
 += 
  
 '<strong>Original coords: </strong>' 
  
 + 
  
 '(' 
  
 + 
  
 original 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 original 
 . 
 lng 
  
 + 
  
 ')<br>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 coords 
 . 
 originalIndex 
 ; 
  
 } 
  
 content 
  
 += 
  
 '</p></div>' 
 ; 
  
 infowindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ); 
  
 }; 
  
 getPlaceDetails 
 ( 
 placeId 
 , 
  
 function 
 ( 
 place 
 ) 
  
 { 
  
 if 
  
 ( 
 place 
 . 
 name 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>' 
  
 + 
  
 place 
 . 
 name 
  
 + 
  
 '</strong><br>' 
 ; 
  
 } 
  
 getSpeedLimit 
 ( 
 placeId 
 , 
  
 function 
 ( 
 data 
 ) 
  
 { 
  
 if 
  
 ( 
 data 
 . 
 speedLimits 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>Speed Limit: </strong>' 
  
 + 
  
 data 
 . 
 speedLimits 
 [ 
 0 
 ]. 
 speedLimit 
  
 + 
  
 ' km/h <br>' 
 ; 
  
 } 
  
 finishInfoWindow 
 ( 
 detailsUrl 
 ); 
  
 }); 
  
 }, 
  
 function 
 () 
  
 { 
  
 finishInfoWindow 
 ( 
 "<em>None available</em>" 
 ); 
  
 }); 
  
 }); 
  
 return 
  
 infowindow 
 ; 
  
 } 
  
 // Avoid infoWindows staying open if the pano changes 
  
 listenForPanoChange 
 (); 
  
 // If the user came to the page with a particular path or URL, 
  
 // immediately plot it. 
  
 if 
  
 ( 
 location 
 . 
 hash 
 . 
 length 
 > 
 1 
 ) 
  
 { 
  
 coords 
  
 = 
  
 parseCoordsFromQuery 
 ( 
 location 
 . 
 hash 
 . 
 slice 
 ( 
 1 
 )); 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 coords 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 click 
 (); 
  
 } 
 } 
  
 // End init function 
 // Call the initialize function once everything has loaded 
 google 
 . 
 maps 
 . 
 event 
 . 
 addDomListener 
 ( 
 window 
 , 
  
 'load' 
 , 
  
 initialize 
 ); 
 // Load the control panel in a floating div if it is not loaded in an iframe 
 // after the textarea has been rendered 
 $ 
 ( 
 "#coords" 
 ). 
 ready 
 ( 
 function 
 () 
  
 { 
  
 if 
  
 ( 
 ! 
 window 
 . 
 frameElement 
 ) 
  
 { 
  
 $ 
 ( 
 '#panel' 
 ). 
 addClass 
 ( 
 "floating panel" 
 ); 
  
 $ 
 ( 
 '#button-div' 
 ). 
 addClass 
 ( 
 "button-div" 
 ); 
  
 $ 
 ( 
 '#coords' 
 ). 
 removeClass 
 ( 
 "coords-large" 
 ). 
 addClass 
 ( 
 "coords-small" 
 ); 
  
 $ 
 ( 
 '#toggle' 
 ). 
 show 
 (); 
  
 $ 
 ( 
 '#map' 
 ). 
 height 
 ( 
 '100%' 
 ); 
  
 } 
 }); 
 /** 
 *  latlng literal with extra properties to use with the RoadsAPI 
 *  @typedef {Object} ExtendedLatLng 
 *   lat:string|float 
 *   lng:string|float 
 *   interpolated:boolean 
 *   unsnapped:boolean 
 */ 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers while the mouse is over it. 
 * @param {ExtendedLatLng} from - The origin of the line 
 * @param {ExtendedLatLng} to - The destination of the line 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addOverline 
 ( 
 from 
 , 
  
 to 
 ) 
  
 { 
  
 return 
  
 addLine 
 ( 
 "overline" 
 , 
  
 from 
 , 
  
 to 
 , 
  
 '#ff77ff' 
 , 
  
 4 
 , 
  
 1.0 
 , 
  
 2.0 
 , 
  
 false 
 ); 
 } 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers while the mouse is NOT over it. 
 * @param {ExtendedLatLng} from - The origin of the line 
 * @param {ExtendedLatLng} to - The destination of the line 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addOutline 
 ( 
 from 
 , 
  
 to 
 ) 
  
 { 
  
 return 
  
 addLine 
 ( 
 "outline" 
 , 
  
 from 
 , 
  
 to 
 , 
  
 '#bb33bb' 
 , 
  
 2 
 , 
  
 0.5 
 , 
  
 1.35 
 , 
  
 true 
 ); 
 } 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers. 
 * @param {string}         attrib  - The attribute to use for managing the line 
 * @param {ExtendedLatLng} from    - The origin of the line 
 * @param {ExtendedLatLng} to      - The destination of the line 
 * @param {string}         color   - The color of the line 
 * @param {number}         weight  - The weight of the line 
 * @param {number}         opacity - The opacity of the line (0..1) 
 * @param {number}         scale   - The scale of the arrow-head (pt) 
 * @param {boolean}        visible - The visibility of the line 
 * @return {!Object}       line    - the polyline object created 
 */ 
 function 
  
 addLine 
 ( 
 attrib 
 , 
  
 from 
 , 
  
 to 
 , 
  
 color 
 , 
  
 weight 
 , 
  
 opacity 
 , 
  
 scale 
 , 
  
 visible 
 ) 
  
 { 
  
 from 
 [ 
 attrib 
 ] 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 path 
 : 
  
 [ 
 from 
 , 
  
 to 
 ], 
  
 strokeColor 
 : 
  
 color 
 , 
  
 strokeWeight 
 : 
  
 weight 
 , 
  
 strokeOpacity 
 : 
  
 opacity 
 , 
  
 icons 
 : 
 [{ 
  
 offset 
 : 
  
 "0%" 
 , 
  
 icon 
 : 
  
 { 
  
 scale 
 : 
  
 scale 
 /*pt*/ 
 , 
  
 path 
 : 
  
 google 
 . 
 maps 
 . 
 SymbolPath 
 . 
 BACKWARD_CLOSED_ARROW 
  
 } 
  
 }] 
  
 }); 
  
 from 
 [ 
 attrib 
 ]. 
 setVisible 
 ( 
 visible 
 ); 
  
 from 
 [ 
 attrib 
 ]. 
 setMap 
 ( 
 map 
 ); 
  
 to 
 [ 
 attrib 
 ] 
  
 = 
  
 from 
 [ 
 attrib 
 ]; 
  
 polylines 
 . 
 push 
 ( 
 from 
 [ 
 attrib 
 ]); 
  
 return 
  
 from 
 [ 
 attrib 
 ]; 
 } 
 /** 
 * Add a pair of lines to the map for highlighting the connection between two 
 * markers; one visible while the mouse is over the marker (the "overline"), 
 * the other while it is not (the "outline"). 
 * @param {ExtendedLatLng} from - The origin of the line (the original input) 
 * @param {ExtendedLatLng} to - The destination of the line (the snapped point) 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ) 
  
 { 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 overline 
 ) 
  
 { 
  
 addOverline 
 ( 
 coords 
 , 
  
 coords 
 . 
 related 
 ); 
  
 } 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 outline 
 ) 
  
 { 
  
 addOutline 
 ( 
 coords 
 , 
  
 coords 
 . 
 related 
 ); 
  
 } 
  
 marker 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 coords 
 . 
 outline 
 . 
 setVisible 
 ( 
 false 
 ); 
  
 coords 
 . 
 overline 
 . 
 setVisible 
 ( 
 true 
 ); 
  
 coords 
 . 
 related 
 . 
 marker 
 . 
 setOpacity 
 ( 
 1.0 
 ); 
  
 }); 
  
 marker 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 coords 
 . 
 overline 
 . 
 setVisible 
 ( 
 false 
 ); 
  
 coords 
 . 
 outline 
 . 
 setVisible 
 ( 
 true 
 ); 
  
 coords 
 . 
 related 
 . 
 marker 
 . 
 setOpacity 
 ( 
 0.5 
 ); 
  
 }); 
 } 
 /** 
 * Add a marker to the map and check for special 'interpolated' 
 * and 'unsnapped' properties to control which colour marker is used 
 * @param {ExtendedLatLng} coords - Coords of where to add the marker 
 * @return {!Object} marker - the marker object created 
 */ 
 function 
  
 addMarker 
 ( 
 coords 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Marker 
 ({ 
  
 position 
 : 
  
 coords 
 , 
  
 title 
 : 
  
 coords 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
 , 
  
 map 
 : 
  
 map 
 , 
  
 opacity 
 : 
  
 0.5 
 , 
  
 icon 
 : 
  
 RED_MARKER 
  
 }); 
  
 // Coord should NEVER be interpolated AND unsnapped 
  
 if 
  
 ( 
 coords 
 . 
 interpolated 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 BLUE_MARKER 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 related 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 YELLOW_MARKER 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 coords 
 . 
 originalIndex 
  
 != 
  
 undefined 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 RED_MARKER 
 ); 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ); 
  
 } 
  
 else 
  
 { 
  
 marker 
 . 
 setIcon 
 ({ 
 url 
 : 
  
 GREEN_MARKER 
 , 
  
 scaledSize 
 : 
  
 { 
 width 
 : 
  
 20 
 , 
  
 height 
 : 
  
 20 
 }}); 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ); 
  
 } 
  
 // Make markers change opacity when the mouse scrubs across them 
  
 marker 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 marker 
 . 
 setOpacity 
 ( 
 1.0 
 ); 
  
 }); 
  
 marker 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 marker 
 . 
 setOpacity 
 ( 
 0.5 
 ); 
  
 }); 
  
 coords 
 . 
 marker 
  
 = 
  
 marker 
 ; 
  
 // Save a reference for easy access later 
  
 markers 
 . 
 push 
 ( 
 marker 
 ); 
  
 return 
  
 marker 
 ; 
 } 
 /** 
 * Animate an icon along a polyline 
 * @param {Object} polyline The line to animate the icon along 
 */ 
 function 
  
 animateCircle 
 ( 
 polyline 
 ) 
  
 { 
  
 var 
  
 count 
  
 = 
  
 0 
 ; 
  
 // fallback icon if the poly has no icon to animate 
  
 var 
  
 defaultIcon 
  
 = 
  
 [ 
  
 { 
  
 icon 
 : 
  
 lineSymbol 
 , 
  
 offset 
 : 
  
 '100%' 
  
 } 
  
 ]; 
  
 window 
 . 
 setInterval 
 ( 
 function 
 () 
  
 { 
  
 count 
  
 = 
  
 ( 
 count 
  
 + 
  
 1 
 ) 
  
 % 
  
 200 
 ; 
  
 var 
  
 icons 
  
 = 
  
 polyline 
 . 
 get 
 ( 
 'icons' 
 ) 
  
 || 
  
 defaultIcon 
 ; 
  
 icons 
 [ 
 0 
 ]. 
 offset 
  
 = 
  
 ( 
 count 
  
 / 
  
 2 
 ) 
  
 + 
  
 '%' 
 ; 
  
 polyline 
 . 
 set 
 ( 
 'icons' 
 , 
  
 icons 
 ); 
  
 }, 
  
 20 
 ); 
 } 
 /** 
 * Fit the map bounds to the current set of markers 
 * @param {Array<Object>} markers Array of all map markers 
 */ 
 function 
  
 fitBounds 
 ( 
 markers 
 ) 
  
 { 
  
 var 
  
 bounds 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLngBounds 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 markers 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 bounds 
 . 
 extend 
 ( 
 markers 
 [ 
 i 
 ]. 
 getPosition 
 ()); 
  
 } 
  
 map 
 . 
 fitBounds 
 ( 
 bounds 
 ); 
 } 
 /** 
 * Uses Places library to get Place Details for a Place ID 
 * @param {string}   placeId         The Place ID to look up 
 * @param {Function} foundCallback   Called if the place is found 
 * @param {Function} missingCallback Called if nothing is found 
 * @param {Function} errorCallback   Called if request fails 
 */ 
 function 
  
 getPlaceDetails 
 ( 
 placeId 
 , 
  
 foundCallback 
 , 
  
 missingCallback 
 , 
  
 errorCallback 
 ) 
  
 { 
  
 var 
  
 request 
  
 = 
  
 { 
  
 placeId 
 : 
  
 placeId 
  
 }; 
  
 placesService 
 . 
 getDetails 
 ( 
 request 
 , 
  
 function 
 ( 
 place 
 , 
  
 status 
 ) 
  
 { 
  
 if 
  
 ( 
 status 
  
 == 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesServiceStatus 
 . 
 OK 
 ) 
  
 { 
  
 foundCallback 
 ( 
 place 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 status 
  
 == 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesServiceStatus 
 . 
 NOT_FOUND 
 ) 
  
 { 
  
 missingCallback 
 (); 
  
 } 
  
 else 
  
 if 
  
 ( 
 errorCallback 
 ) 
  
 { 
  
 errorCallback 
 (); 
  
 } 
  
 }); 
 } 
 /** 
 * AJAX request to the Roads Speed Limit API. 
 * Request the speed limit for the Place ID 
 * @param {string}   placeId         Place ID to request the speed limit for 
 * @param {Function} successCallback Called if request is successful 
 * @param {Function} errorCallback   Called if request fails 
 */ 
 function 
  
 getSpeedLimit 
 ( 
 placeId 
 , 
  
 successCallback 
 , 
  
 errorCallback 
 ) 
  
 { 
  
 $ 
 . 
 ajax 
 ({ 
  
 type 
 : 
  
 'GET' 
 , 
  
 url 
 : 
  
 SPEED_LIMIT_URL 
 , 
  
 data 
 : 
  
 { 
  
 placeId 
 : 
  
 placeId 
 , 
  
 key 
 : 
  
 API_KEY 
  
 }, 
  
 success 
 : 
  
 successCallback 
 , 
  
 error 
 : 
  
 errorCallback 
  
 }); 
 } 
 /** 
 * Open an infowindow on either the map or the active streetview pano 
 * @param {Object} infowindow Infowindow to be opened 
 * @param {Object} marker Marker the infowindow is anchored to 
 */ 
 function 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ) 
  
 { 
  
 // If streetView is visible display the infoWindow over the pano 
  
 // and anchor to the marker 
  
 if 
  
 ( 
 map 
 . 
 getStreetView 
 (). 
 getVisible 
 ()) 
  
 { 
  
 infowindow 
 . 
 open 
 ( 
 map 
 . 
 getStreetView 
 (), 
  
 marker 
 ); 
  
 } 
  
 // Otherwise open it on the map and anchor to the marker 
  
 else 
  
 { 
  
 infowindow 
 . 
 open 
 ( 
 map 
 , 
  
 marker 
 ); 
  
 } 
 } 
 /** 
 * Add event listener to for when the active pano changes 
 */ 
 function 
  
 listenForPanoChange 
 () 
  
 { 
  
 var 
  
 pano 
  
 = 
  
 map 
 . 
 getStreetView 
 (); 
  
 // Close all open markers when the pano changes 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 pano 
 , 
  
 'position_changed' 
 , 
  
 function 
 () 
  
 { 
  
 closeAllInfoWindows 
 ( 
 infoWindows 
 ); 
  
 }); 
 } 
 /** 
 * Close all open infoWindows 
 * @param {Array<Object>} infoWindows - all infowindow objects 
 */ 
 function 
  
 closeAllInfoWindows 
 ( 
 infoWindows 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
 } 
  

JavaScript + HTML

< ! 
 DOCTYPE 
  
 html 
>
< html 
>  
< head 
>  
< title>Roads 
  
 API 
  
 Inspector 
< / 
 title 
>  
< style 
  
 type 
 = 
 "text/css" 
>  
 html 
 , 
  
 body 
  
 { 
  
 height 
 : 
  
 100 
 % 
 ; 
  
 margin 
 : 
  
 0 
 ; 
  
 padding 
 : 
  
 0 
 ; 
  
 font 
 - 
 family 
 : 
  
 Roboto 
 , 
  
 Noto 
 , 
  
 sans 
 - 
 serif 
 ; 
  
 } 
  
 #map 
  
 { 
  
 height 
 : 
  
 500 
 px 
 ; 
  
 } 
  
 #interpolate 
  
 { 
  
 width 
 : 
  
 2 
 em 
 ; 
  
 height 
 : 
  
 2 
 em 
 ; 
  
 } 
  
 #coords 
  
 { 
  
 resize 
 : 
  
 vertical 
 ; 
  
 min 
 - 
 height 
 : 
  
 75 
 px 
 ; 
  
 max 
 - 
 height 
 : 
  
 200 
 px 
 ; 
  
 } 
  
 . 
 block 
  
 { 
  
 clear 
 : 
  
 both 
 ; 
  
 margin 
 : 
  
 1.5 
 em 
  
 auto 
 ; 
  
 text 
 - 
 align 
 : 
  
 center 
 ; 
  
 } 
  
 #legend 
  
 { 
  
 float 
 : 
  
 center 
 ; 
  
 margin 
 : 
  
 5 
 px 
  
 15 
 px 
 ; 
  
 font 
 - 
 size 
 : 
  
 13 
 px 
 ; 
  
 } 
  
 . 
 button 
  
 { 
  
 display 
 : 
  
 inline 
 - 
 block 
 ; 
  
 position 
 : 
  
 relative 
 ; 
  
 border 
 : 
  
 0 
 ; 
  
 padding 
 : 
  
 0 
  
 1.7 
 em 
 ; 
  
 min 
 - 
 width 
 : 
  
 120 
 px 
 ; 
  
 height 
 : 
  
 32 
 px 
 ; 
  
 line 
 - 
 height 
 : 
  
 32 
 px 
 ; 
  
 border 
 - 
 radius 
 : 
  
 2 
 px 
 ; 
  
 font 
 - 
 size 
 : 
  
 0.9 
 em 
 ; 
  
 background 
 - 
 color 
 : 
  
 #fff 
 ; 
  
 color 
 : 
  
 # 
 646464 
 ; 
  
 } 
  
 . 
 button 
 . 
 narrow 
  
 { 
  
 width 
 : 
  
 60 
 px 
 ; 
  
 } 
  
 . 
 button 
 . 
 grey 
  
 { 
  
 background 
 - 
 color 
 : 
  
 #eee 
 ; 
  
 } 
  
 . 
 button 
 . 
 blue 
  
 { 
  
 background 
 - 
 color 
 : 
  
 # 
 4285 
 f4 
 ; 
  
 color 
 : 
  
 #fff 
 ; 
  
 } 
  
 . 
 button 
 . 
 green 
  
 { 
  
 background 
 - 
 color 
 : 
  
 # 
 0 
 f9d58 
 ; 
  
 color 
 : 
  
 #fff 
 ; 
  
 } 
  
 . 
 button 
 . 
 raised 
  
 { 
  
 transition 
 : 
  
 box 
 - 
 shadow 
  
 0.2 
 s 
  
 cubic 
 - 
 bezier 
 ( 
 0.4 
 , 
  
 0 
 , 
  
 0.2 
 , 
  
 1 
 ); 
  
 transition 
 - 
 delay 
 : 
  
 0.2 
 s 
 ; 
  
 box 
 - 
 shadow 
 : 
  
 0 
  
 2 
 px 
  
 5 
 px 
  
 0 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.26 
 ); 
  
 } 
  
 . 
 button 
 . 
 raised 
 : 
 active 
  
 { 
  
 box 
 - 
 shadow 
 : 
  
 0 
  
 8 
 px 
  
 17 
 px 
  
 0 
  
 rgba 
 ( 
 0 
 , 
  
 0 
 , 
  
 0 
 , 
  
 0.2 
 ); 
  
 transition 
 - 
 delay 
 : 
  
 0 
 s 
 ; 
  
 } 
  
 . 
 floating 
  
 { 
  
 position 
 : 
  
 absolute 
 ; 
  
 top 
 : 
  
 10 
 px 
 ; 
  
 right 
 : 
  
 10 
 px 
 ; 
  
 z 
 - 
 index 
 : 
  
 5 
 ; 
  
 background 
 - 
 color 
 : 
  
 rgba 
 ( 
 255 
 , 
  
 255 
 , 
  
 255 
 , 
  
 0.75 
 ); 
  
 padding 
 : 
  
 1 
 px 
 ; 
  
 border 
 : 
  
 1 
 px 
  
 solid 
  
 # 
 999 
 ; 
  
 text 
 - 
 align 
 : 
  
 center 
 ; 
  
 line 
 - 
 height 
 : 
  
 18 
 px 
 ; 
  
 } 
  
 . 
 floating 
 . 
 panel 
  
 { 
  
 width 
 : 
  
 400 
 px 
 ; 
  
 } 
  
 . 
 coords 
 - 
 small 
  
 { 
  
 width 
 : 
  
 350 
 px 
 ; 
  
 } 
  
 . 
 coords 
 - 
 large 
  
 { 
  
 width 
 : 
  
 400 
 px 
 ; 
  
 } 
  
 . 
 button 
 - 
 div 
  
 { 
  
 padding 
 : 
  
 0 
 px 
  
 50 
 px 
 ; 
  
 width 
 : 
  
 300 
 px 
 ; 
  
 line 
 - 
 height 
 : 
  
 40 
 px 
 ; 
  
 } 
  
 #toggle 
  
 { 
  
 width 
 : 
  
 25 
 px 
 ; 
  
 z 
 - 
 index 
 : 
  
 10 
 ; 
  
 cursor 
 : 
  
 default 
 ; 
  
 font 
 - 
 size 
 : 
  
 2 
 em 
 ; 
  
 padding 
 : 
  
 1 
 px 
 ; 
  
 color 
 : 
  
 # 
 999 
 ; 
  
 display 
 : 
  
 none 
 ; 
  
 } 
  
< / 
 style 
>  
< script 
  
 type 
 = 
 "text/javascript" 
  
 src 
 = 
 "https://maps.googleapis.com/maps/api/js?libraries=places,geometry&key=AIzaSyAAUHO6lMMnE2VZMRYmAfVbCYCgsEEqNyM" 
>  
< / 
 script 
>  
< script 
  
 src 
 = 
 "https://www.gstatic.com/external_hosted/jquery2.min.js" 
>< / 
 script 
>  
< script 
>  
 // Replace with your own API key 
 var 
  
 API_KEY 
  
 = 
  
 'YOUR_API_KEY' 
 ; 
 // Icons for markers 
 var 
  
 RED_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/red-dot.png' 
 ; 
 var 
  
 GREEN_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/green-dot.png' 
 ; 
 var 
  
 BLUE_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png' 
 ; 
 var 
  
 YELLOW_MARKER 
  
 = 
  
 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png' 
 ; 
 // URL for places requests 
 var 
  
 PLACES_URL 
  
 = 
  
 'https://maps.googleapis.com/maps/api/place/details/json?' 
  
 + 
  
 'key=' 
  
 + 
  
 API_KEY 
  
 + 
  
 '&placeid=' 
 ; 
 // URL for Speed limits 
 var 
  
 SPEED_LIMIT_URL 
  
 = 
  
 'https://roads.googleapis.com/v1/speedLimits' 
 ; 
 var 
  
 coords 
 ; 
 /** 
 * Current Roads API threshold (subject to change without notice) 
 * @const {number} 
 */ 
 var 
  
 DISTANCE_THRESHOLD_HIGH 
  
 = 
  
 300 
 ; 
 var 
  
 DISTANCE_THRESHOLD_LOW 
  
 = 
  
 200 
 ; 
 /** 
 * @type Array<ExtendedLatLng> 
 */ 
 var 
  
 originals 
  
 = 
  
 []; 
  
 // the original input points, a list of ExtendedLatLng 
 var 
  
 interpolate 
  
 = 
  
 true 
 ; 
 var 
  
 map 
 ; 
 var 
  
 placesService 
 ; 
 var 
  
 originalCoordsLength 
 ; 
 // Settingup Arrays 
 var 
  
 infoWindows 
  
 = 
  
 []; 
 var 
  
 markers 
  
 = 
  
 []; 
 var 
  
 placeIds 
  
 = 
  
 []; 
 var 
  
 polylines 
  
 = 
  
 []; 
 var 
  
 snappedCoordinates 
  
 = 
  
 []; 
 var 
  
 distPolylines 
  
 = 
  
 []; 
 // Symbol that gets animated along the polyline 
 var 
  
 lineSymbol 
  
 = 
  
 { 
  
 path 
 : 
  
 google 
 . 
 maps 
 . 
 SymbolPath 
 . 
 CIRCLE 
 , 
  
 scale 
 : 
  
 8 
 , 
  
 strokeColor 
 : 
  
 '#005db5' 
 , 
  
 strokeWidth 
 : 
  
 '#005db5' 
 }; 
 // Example 1 - Frolick around Sydney 
 var 
  
 eg1 
  
 = 
  
 '-33.870315,151.196532|-33.869979,151.197355|' 
  
 + 
  
 '-33.870044,151.197712|-33.870358,151.198206|' 
  
 + 
  
 '-33.870595,151.198376|-33.870640,151.198398|' 
  
 + 
  
 '-33.870620,151.198449|-33.870951,151.198525|' 
  
 + 
  
 '-33.871040,151.198528|-33.872031,151.198413' 
 ; 
 // Example 2 - Lap around Canberra 
 var 
  
 eg2 
  
 = 
  
 '-35.274346,149.130168|-35.278012,149.129583|' 
  
 + 
  
 '-35.280329,149.129073|-35.280999,149.129293|' 
  
 + 
  
 '-35.281441,149.129846|-35.281945,149.130034|' 
  
 + 
  
 '-35.282825,149.129567|-35.283022,149.128811|' 
  
 + 
  
 '-35.284734,149.128366' 
 ; 
 // Example 3 - Path with unsnappable point 
 var 
  
 eg3 
  
 = 
  
 '-35.274346,149.094000|-35.278012,149.129583|' 
  
 + 
  
 '-35.280329,149.129073|-35.280999,149.129293|' 
  
 + 
  
 '-35.281441,149.129846' 
 ; 
 // Example 4 - Drive erratically in Elkin 
 var 
  
 eg4 
  
 = 
  
 '36.28881,-80.8525|36.287038,-80.85313|36.286161,-80.85369|' 
  
 + 
  
 '36.28654,-80.85418|36.2846,-80.84766|36.28355,-80.84669' 
 ; 
 // Initialize 
 function 
  
 initialize 
 () 
  
 { 
  
 $ 
 ( 
 '#eg1' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg1 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg2' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg2 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg3' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg3 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#eg4' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 eg4 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 }); 
  
 $ 
 ( 
 '#toggle' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 if 
  
 ( 
 $ 
 ( 
 '#panel' 
 ). 
 css 
 ( 
 "display" 
 ) 
  
 != 
  
 'none' 
 ) 
  
 { 
  
 $ 
 ( 
 '#toggle' 
 ). 
 html 
 ( 
 "+" 
 ); 
  
 $ 
 ( 
 '#panel' 
 ). 
 hide 
 (); 
  
 } 
  
 else 
  
 { 
  
 $ 
 ( 
 '#toggle' 
 ). 
 html 
 ( 
 "&mdash;" 
 ); 
  
 $ 
 ( 
 '#panel' 
 ). 
 show 
 (); 
  
 } 
  
 }); 
  
 // Centre the map on Sydney 
  
 var 
  
 mapOptions 
  
 = 
  
 { 
  
 center 
 : 
  
 { 
 'lat' 
 : 
  
 - 
 33.870315 
 , 
  
 'lng' 
 : 
  
 151.196532 
 }, 
  
 zoom 
 : 
  
 14 
  
 }; 
  
 // Map object 
  
 map 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Map 
 ( 
 document 
 . 
 getElementById 
 ( 
 'map' 
 ), 
  
 mapOptions 
 ); 
  
 // Places object 
  
 placesService 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesService 
 ( 
 map 
 ); 
  
 // Reset the map to a clean state and reset all variables 
  
 // used for displaying each request 
  
 function 
  
 clearMap 
 () 
  
 { 
  
 // Clear the polyline 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 polylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 polylines 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all markers 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 markers 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 markers 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all the distance polylines 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 distPolylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 distPolylines 
 [ 
 i 
 ]. 
 setMap 
 ( 
 null 
 ); 
  
 } 
  
 // Clear all info windows 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
  
 // Empty everything 
  
 polylines 
  
 = 
  
 []; 
  
 markers 
  
 = 
  
 []; 
  
 distPolylines 
  
 = 
  
 []; 
  
 snappedCoordinates 
  
 = 
  
 []; 
  
 placeIds 
  
 = 
  
 []; 
  
 infoWindows 
  
 = 
  
 []; 
  
 $ 
 ( 
 '#unsnappedPoints' 
 ). 
 empty 
 (); 
  
 $ 
 ( 
 '#warningMessage' 
 ). 
 empty 
 (); 
  
 } 
  
 // Parse the value in the input element 
  
 // to get all coordinates 
  
 function 
  
 parseCoordsFromQuery 
 ( 
 input 
 ) 
  
 { 
  
 var 
  
 coords 
 ; 
  
 input 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 if 
  
 ( 
 input 
 . 
 split 
 ( 
 'path=' 
 ). 
 length 
 > 
 1 
 ) 
  
 { 
  
 input 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 // Split on the ampersand to get all params 
  
 var 
  
 parts 
  
 = 
  
 input 
 . 
 split 
 ( 
 '&' 
 ); 
  
 // Check each part to see if it starts with 'path=' 
  
 // grabbing out the coordinates if it does 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 parts 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 parts 
 [ 
 i 
 ]. 
 split 
 ( 
 'path=' 
 ). 
 length 
 > 
 1 
 ) 
  
 { 
  
 coords 
  
 = 
  
 parts 
 [ 
 i 
 ]. 
 split 
 ( 
 'path=' 
 )[ 
 1 
 ]; 
  
 break 
 ; 
  
 } 
  
 } 
  
 } 
  
 else 
  
 { 
  
 coords 
  
 = 
  
 decodeURIComponent 
 ( 
 input 
 ); 
  
 } 
  
 // Parse the "Lat,Lng|..." coordinates into an array of ExtendedLatLng 
  
 originals 
  
 = 
  
 []; 
  
 var 
  
 points 
  
 = 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 points 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 point 
  
 = 
  
 points 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 ); 
  
 originals 
 . 
 push 
 ({ 
 lat 
 : 
  
 Number 
 ( 
 point 
 [ 
 0 
 ]), 
  
 lng 
 : 
  
 Number 
 ( 
 point 
 [ 
 1 
 ]), 
  
 index 
 : 
 i 
 }); 
  
 } 
  
 return 
  
 coords 
 ; 
  
 } 
  
 // Clear the map of any old data and plot the request 
  
 $ 
 ( 
 '#plot' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 clearMap 
 (); 
  
 bendAndSnap 
 (); 
  
 drawDistance 
 (); 
  
 e 
 . 
 preventDefault 
 (); 
  
 }); 
  
 // Make AJAX request to the snapToRoadsAPI 
  
 // with coordinates parsed from text input element. 
  
 function 
  
 bendAndSnap 
 () 
  
 { 
  
 coords 
  
 = 
  
 parseCoordsFromQuery 
 ( 
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ()); 
  
 location 
 . 
 hash 
  
 = 
  
 coords 
 ; 
  
 $ 
 . 
 ajax 
 ({ 
  
 type 
 : 
  
 'GET' 
 , 
  
 url 
 : 
  
 'https://roads.googleapis.com/v1/snapToRoads' 
 , 
  
 data 
 : 
  
 { 
  
 interpolate 
 : 
  
 $ 
 ( 
 '#interpolate' 
 ). 
 is 
 ( 
 ':checked' 
 ), 
  
 key 
 : 
  
 API_KEY 
 , 
  
 path 
 : 
  
 coords 
  
 }, 
  
 success 
 : 
  
 function 
 ( 
 data 
 ) 
  
 { 
  
 $ 
 ( 
 '#requestURL' 
 ). 
 html 
 ( 
 '<a target="blank" href="' 
  
 + 
  
 this 
 . 
 url 
  
 + 
  
 '">Request URL</a>' 
 ); 
  
 processSnapToRoadResponse 
 ( 
 data 
 ); 
  
 drawSnappedPolyline 
 ( 
 snappedCoordinates 
 ); 
  
 drawOriginals 
 ( 
 originals 
 ); 
  
 fitBounds 
 ( 
 markers 
 ); 
  
 }, 
  
 error 
 : 
  
 function 
 () 
  
 { 
  
 $ 
 ( 
 '#requestURL' 
 ). 
 html 
 ( 
 '<strong>That query didn\'t work :(</strong>' 
  
 + 
  
 '<p>Try looking at the <a href="' 
  
 + 
  
 this 
 . 
 url 
  
 + 
  
 '">Request URL</a></p>' 
 ); 
  
 clearMap 
 (); 
  
 } 
  
 }); 
  
 } 
  
 // Toggle the distance polylines of the original points to show on the maps 
  
 $ 
 ( 
 '#distance' 
 ). 
 click 
 ( 
 function 
 ( 
 e 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 distPolylines 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 distPolylines 
 [ 
 i 
 ]. 
 setVisible 
 ( 
 ! 
 distPolylines 
 [ 
 i 
 ]. 
 getVisible 
 ()); 
  
 } 
  
 // Clear all infoWindows associated with distance polygons on toggle 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 infoWindows 
 [ 
 i 
 ]. 
 dist 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
  
 } 
  
 e 
 . 
 preventDefault 
 (); 
  
 }); 
  
 /** 
 * Compute the distance between each original point and create a polyline 
 * for each pair. Polylines are initially hidden on creation 
 */ 
  
 function 
  
 drawDistance 
 () 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originals 
 . 
 length 
  
 - 
  
 1 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 origin 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLng 
 ( 
 originals 
 [ 
 i 
 ]); 
  
 var 
  
 destination 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLng 
 ( 
 originals 
 [ 
 i 
 + 
 1 
 ]); 
  
 var 
  
 distance 
  
 = 
  
 google 
 . 
 maps 
 . 
 geometry 
 . 
 spherical 
 . 
 computeDistanceBetween 
 ( 
 origin 
 , 
  
 destination 
 ); 
  
 // Round the distance value to two decimal places 
  
 distance 
  
 = 
  
 Math 
 . 
 round 
 ( 
 distance 
  
 * 
  
 100 
 ) 
  
 / 
  
 100 
 ; 
  
 var 
  
 color 
 ; 
  
 var 
  
 weight 
 ; 
  
 if 
  
 ( 
 distance 
 > 
 DISTANCE_THRESHOLD_HIGH 
 ) 
  
 { 
  
 color 
  
 = 
  
 '#CC0022' 
 ; 
  
 weight 
  
 = 
  
 7 
 ; 
  
 } 
  
 else 
  
 if 
  
 ( 
 distance 
 < 
 DISTANCE_THRESHOLD_HIGH 
  
&&  
 distance 
 > 
 DISTANCE_THRESHOLD_LOW 
 ) 
  
 { 
  
 color 
  
 = 
  
 '#FF6600' 
 ; 
  
 weight 
  
 = 
  
 6 
 ; 
  
 } 
  
 else 
  
 { 
  
 color 
  
 = 
  
 '#22CC00' 
 ; 
  
 weight 
  
 = 
  
 5 
 ; 
  
 } 
  
 var 
  
 polyline 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 strokeColor 
 : 
  
 color 
 , 
  
 strokeOpacity 
 : 
  
 0.4 
 , 
  
 strokeWeight 
 : 
  
 weight 
 , 
  
 geodesic 
 : 
  
 true 
 , 
  
 visible 
 : 
  
 false 
 , 
  
 map 
 : 
  
 map 
  
 }); 
  
 polyline 
 . 
 setPath 
 ([ 
 origin 
 , 
  
 destination 
 ]); 
  
 distPolylines 
 . 
 push 
 ( 
 polyline 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 addPolyWindow 
 ( 
 polyline 
 , 
  
 distance 
 , 
  
 i 
 )); 
  
 } 
  
 } 
  
 /** 
 * Add an info window to the polyline displaying the original 
 * points and the distance 
 */ 
  
 function 
  
 addPolyWindow 
 ( 
 polyline 
 , 
  
 distance 
 , 
  
 index 
 ) 
  
 { 
  
 var 
  
 infoWindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 content 
  
 = 
  
 '<div style="width:100%"><p>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 index 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>Coords:</strong> (' 
  
 + 
  
 originals 
 [ 
 index 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 originals 
 [ 
 index 
 ]. 
 lng 
  
 + 
  
 ')' 
  
 + 
  
 '<br>to<br>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 ( 
 index 
 + 
 1 
 ) 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>Coords:</strong> (' 
  
 + 
  
 originals 
 [ 
 index 
 + 
 1 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 originals 
 [ 
 index 
 + 
 1 
 ]. 
 lng 
  
 + 
  
 ')<br><br>' 
  
 + 
  
 '<strong>Distance:  </strong>' 
  
 + 
  
 distance 
  
 + 
  
 ' m<br>' 
 ; 
  
 if 
  
 ( 
 distance 
 > 
 DISTANCE_THRESHOLD_HIGH 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<span style="color:#CC0022;font-style:italic">' 
  
 + 
  
 '*Large distance (>300m) may affect snapping</span><br>' 
  
 + 
  
 'Please see <a href="https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap#parameter_usage" ' 
  
 + 
  
 'target="_blank">Roads API documentation</a>' 
 ; 
  
 } 
  
 content 
  
 += 
  
 '</p></div>' 
 ; 
  
 infoWindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 infoWindow 
 . 
 dist 
  
 = 
  
 true 
 ; 
  
 polyline 
 . 
 addListener 
 ( 
 'click' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 infoWindow 
 . 
 setPosition 
 ( 
 e 
 . 
 latLng 
 ); 
  
 infoWindow 
 . 
 open 
 ( 
 map 
 ); 
  
 }); 
  
 polyline 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 polyline 
 . 
 setOptions 
 ({ 
 strokeOpacity 
 : 
  
 1.0 
 }); 
  
 }); 
  
 polyline 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 polyline 
 . 
 setOptions 
 ({ 
 strokeOpacity 
 : 
  
 0.4 
 }); 
  
 }); 
  
 return 
  
 infoWindow 
 ; 
  
 } 
  
 // Parse the value in the input element 
  
 // to get all coordinates 
  
 function 
  
 getMissingPoints 
 ( 
 originalIndexes 
 , 
  
 originalCoordsLength 
 ) 
  
 { 
  
 var 
  
 unsnappedPoints 
  
 = 
  
 []; 
  
 var 
  
 coordsArray 
  
 = 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ); 
  
 var 
  
 hasMissingCoords 
  
 = 
  
 false 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originalCoordsLength 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 originalIndexes 
 . 
 indexOf 
 ( 
 i 
 ) 
 < 
 0 
 ) 
  
 { 
  
 hasMissingCoords 
  
 = 
  
 true 
 ; 
  
 var 
  
 latlng 
  
 = 
  
 { 
  
 'lat' 
 : 
  
 parseFloat 
 ( 
 coordsArray 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 )[ 
 0 
 ]), 
  
 'lng' 
 : 
  
 parseFloat 
 ( 
 coordsArray 
 [ 
 i 
 ]. 
 split 
 ( 
 ',' 
 )[ 
 1 
 ]) 
  
 }; 
  
 unsnappedPoints 
 . 
 push 
 ( 
 latlng 
 ); 
  
 latlng 
 . 
 unsnapped 
  
 = 
  
 true 
 ; 
  
 } 
  
 } 
  
 return 
  
 unsnappedPoints 
 ; 
  
 } 
  
 // Parse response from snapToRoads API request 
  
 // Store all coordinates in response 
  
 // Calls functions to add markers to map for unsnapped coordinates 
  
 function 
  
 processSnapToRoadResponse 
 ( 
 data 
 ) 
  
 { 
  
 var 
  
 originalIndexes 
  
 = 
  
 []; 
  
 var 
  
 unsnappedMessage 
  
 = 
  
 '' 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 data 
 . 
 snappedPoints 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 latlng 
  
 = 
  
 { 
  
 'lat' 
 : 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 location 
 . 
 latitude 
 , 
  
 'lng' 
 : 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 location 
 . 
 longitude 
  
 }; 
  
 var 
  
 interpolated 
  
 = 
  
 true 
 ; 
  
 if 
  
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
  
 != 
  
 undefined 
 ) 
  
 { 
  
 interpolated 
  
 = 
  
 false 
 ; 
  
 originalIndexes 
 . 
 push 
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
 ); 
  
 latlng 
 . 
 originalIndex 
  
 = 
  
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 originalIndex 
 ; 
  
 } 
  
 latlng 
 . 
 interpolated 
  
 = 
  
 interpolated 
 ; 
  
 snappedCoordinates 
 . 
 push 
 ( 
 latlng 
 ); 
  
 placeIds 
 . 
 push 
 ( 
 data 
 . 
 snappedPoints 
 [ 
 i 
 ]. 
 placeId 
 ); 
  
 // Cross-reference the original point and this snapped point. 
  
 latlng 
 . 
 related 
  
 = 
  
 originals 
 [ 
 latlng 
 . 
 originalIndex 
 ]; 
  
 originals 
 [ 
 latlng 
 . 
 originalIndex 
 ]. 
 related 
  
 = 
  
 latlng 
 ; 
  
 } 
  
 var 
  
 unsnappedPoints 
  
 = 
  
 getMissingPoints 
 ( 
  
 originalIndexes 
 , 
  
 coords 
 . 
 split 
 ( 
 '|' 
 ). 
 length 
  
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 unsnappedPoints 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 unsnappedPoints 
 [ 
 i 
 ]); 
  
 var 
  
 infowindow 
  
 = 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 unsnappedPoints 
 [ 
 i 
 ], 
  
 i 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 infowindow 
 ); 
  
 unsnappedMessage 
  
 += 
  
 unsnappedPoints 
 [ 
 i 
 ]. 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 unsnappedPoints 
 [ 
 i 
 ]. 
 lng 
  
 + 
  
 '<br>' 
 ; 
  
 } 
  
 if 
  
 ( 
 unsnappedPoints 
 . 
 length 
 ) 
  
 { 
  
 unsnappedMessage 
  
 = 
  
 '<strong>' 
  
 + 
  
 'These points weren\'t snapped: ' 
  
 + 
  
 '</strong><br>' 
  
 + 
  
 unsnappedMessage 
 ; 
  
 $ 
 ( 
 '#unsnappedPoints' 
 ). 
 html 
 ( 
 unsnappedMessage 
 ); 
  
 } 
  
 if 
  
 ( 
 data 
 . 
 warningMessage 
 ) 
  
 { 
  
 $ 
 ( 
 '#warningMessage' 
 ). 
 html 
 ( 
 '<span style="color:#CC0022;' 
  
 + 
  
 'font-style:italic;font-size:12px">' 
  
 + 
  
 data 
 . 
 warningMessage 
  
 + 
  
 '<br/>' 
  
 + 
  
 '<a target="_blank" href="https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap">https://developers.google.com/maps/' 
  
 + 
  
 'documentation/roads/snap</a>' 
 ); 
  
 $ 
 ( 
 '#distance' 
 ). 
 trigger 
 ( 
 'click' 
 ); 
  
 } 
  
 } 
  
 // Draw the polyline for the snapToRoads API response 
  
 // Call functions to add markers and infowindows for each snapped 
  
 // point along the polyline. 
  
 function 
  
 drawSnappedPolyline 
 ( 
 snappedCoords 
 ) 
  
 { 
  
 var 
  
 snappedPolyline 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 path 
 : 
  
 snappedCoords 
 , 
  
 strokeColor 
 : 
  
 '#005db5' 
 , 
  
 strokeWeight 
 : 
  
 6 
 , 
  
 icons 
 : 
  
 [{ 
  
 icon 
 : 
  
 lineSymbol 
 , 
  
 offset 
 : 
  
 '100%' 
  
 }] 
  
 }); 
  
 snappedPolyline 
 . 
 setMap 
 ( 
 map 
 ); 
  
 animateCircle 
 ( 
 snappedPolyline 
 ); 
  
 polylines 
 . 
 push 
 ( 
 snappedPolyline 
 ); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 snappedCoords 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 snappedCoords 
 [ 
 i 
 ]); 
  
 var 
  
 infoWindow 
  
 = 
  
 addDetailedInfoWindow 
 ( 
 marker 
 , 
  
 snappedCoords 
 [ 
 i 
 ], 
  
 placeIds 
 [ 
 i 
 ]); 
  
 infoWindows 
 . 
 push 
 ( 
 infoWindow 
 ); 
  
 } 
  
 } 
  
 // Draw the original input. 
  
 // Call functions to add markers and infowindows for each point. 
  
 function 
  
 drawOriginals 
 ( 
 originalCoords 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 originalCoords 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 addMarker 
 ( 
 originalCoords 
 [ 
 i 
 ]); 
  
 var 
  
 infoWindow 
  
 = 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 originalCoords 
 [ 
 i 
 ], 
  
 i 
 ); 
  
 infoWindows 
 . 
 push 
 ( 
 infoWindow 
 ); 
  
 } 
  
 } 
  
 // Infowindow used for unsnappable coordinates 
  
 function 
  
 addBasicInfoWindow 
 ( 
 marker 
 , 
  
 coords 
 , 
  
 index 
 ) 
  
 { 
  
 var 
  
 infowindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 content 
  
 = 
  
 '<div style="width:99%"><p>' 
  
 + 
  
 '<strong>Lat/Lng:</strong><br>' 
  
 + 
  
 '(' 
  
 + 
  
 coords 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
  
 + 
  
 ')<br>' 
  
 + 
  
 ( 
 index 
  
 != 
  
 undefined 
  
 ? 
  
 '<strong>Index: </strong>' 
  
 + 
  
 index 
  
 : 
  
 '' 
 ) 
  
 + 
  
 '</p></div>' 
 ; 
  
 infowindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 marker 
 , 
  
 'click' 
 , 
  
 function 
 () 
  
 { 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ); 
  
 }); 
  
 return 
  
 infowindow 
 ; 
  
 } 
  
 // Infowindow used for snapped points 
  
 // Makes request to Places Details API to get data about each 
  
 // Place ID. 
  
 // Requests speed limit of each location using Roads SpeedLimit API 
  
 function 
  
 addDetailedInfoWindow 
 ( 
 marker 
 , 
  
 coords 
 , 
  
 placeId 
 ) 
  
 { 
  
 var 
  
 infowindow 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 InfoWindow 
 (); 
  
 var 
  
 placesRequestUrl 
  
 = 
  
 PLACES_URL 
  
 + 
  
 placeId 
 ; 
  
 var 
  
 detailsUrl 
  
 = 
  
 '<a target="_blank" href="' 
  
 + 
  
 placesRequestUrl 
  
 + 
  
 '">' 
  
 + 
  
 placeId 
  
 + 
  
 '</a></li>' 
 ; 
  
 // On click we make a request to the Places API 
  
 // This is to avoid OVER_QUERY_LIMIT if we just requested everything 
  
 // at the same time 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 marker 
 , 
  
 'click' 
 , 
  
 function 
 () 
  
 { 
  
 content 
  
 = 
  
 '<div style="width:99%"><p>' 
 ; 
  
 function 
  
 finishInfoWindow 
 ( 
 placeDetails 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>Place Details: </strong>' 
  
 + 
  
 placeDetails 
  
 + 
  
 '<br>' 
  
 + 
  
 '<strong>' 
  
 + 
  
 ( 
 coords 
 . 
 interpolated 
  
 ? 
  
 'Coords' 
  
 : 
  
 'Snapped coords' 
 ) 
  
 + 
  
 ': </strong>' 
  
 + 
  
 '(' 
  
 + 
  
 coords 
 . 
 lat 
 . 
 toFixed 
 ( 
 5 
 ) 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
 . 
 toFixed 
 ( 
 5 
 ) 
  
 + 
  
 ')<br>' 
 ; 
  
 if 
  
 ( 
 ! 
 ( 
 coords 
 . 
 interpolated 
 )) 
  
 { 
  
 var 
  
 original 
  
 = 
  
 originals 
 [ 
 coords 
 . 
 originalIndex 
 ]; 
  
 content 
  
 += 
  
 '<strong>Original coords: </strong>' 
  
 + 
  
 '(' 
  
 + 
  
 original 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 original 
 . 
 lng 
  
 + 
  
 ')<br>' 
  
 + 
  
 '<strong>Original Index: </strong>' 
  
 + 
  
 coords 
 . 
 originalIndex 
 ; 
  
 } 
  
 content 
  
 += 
  
 '</p></div>' 
 ; 
  
 infowindow 
 . 
 setContent 
 ( 
 content 
 ); 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ); 
  
 }; 
  
 getPlaceDetails 
 ( 
 placeId 
 , 
  
 function 
 ( 
 place 
 ) 
  
 { 
  
 if 
  
 ( 
 place 
 . 
 name 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>' 
  
 + 
  
 place 
 . 
 name 
  
 + 
  
 '</strong><br>' 
 ; 
  
 } 
  
 getSpeedLimit 
 ( 
 placeId 
 , 
  
 function 
 ( 
 data 
 ) 
  
 { 
  
 if 
  
 ( 
 data 
 . 
 speedLimits 
 ) 
  
 { 
  
 content 
  
 += 
  
 '<strong>Speed Limit: </strong>' 
  
 + 
  
 data 
 . 
 speedLimits 
 [ 
 0 
 ]. 
 speedLimit 
  
 + 
  
 ' km/h <br>' 
 ; 
  
 } 
  
 finishInfoWindow 
 ( 
 detailsUrl 
 ); 
  
 }); 
  
 }, 
  
 function 
 () 
  
 { 
  
 finishInfoWindow 
 ( 
 "<em>None available</em>" 
 ); 
  
 }); 
  
 }); 
  
 return 
  
 infowindow 
 ; 
  
 } 
  
 // Avoid infoWindows staying open if the pano changes 
  
 listenForPanoChange 
 (); 
  
 // If the user came to the page with a particular path or URL, 
  
 // immediately plot it. 
  
 if 
  
 ( 
 location 
 . 
 hash 
 . 
 length 
 > 
 1 
 ) 
  
 { 
  
 coords 
  
 = 
  
 parseCoordsFromQuery 
 ( 
 location 
 . 
 hash 
 . 
 slice 
 ( 
 1 
 )); 
  
 $ 
 ( 
 '#coords' 
 ). 
 val 
 ( 
 coords 
 ); 
  
 $ 
 ( 
 '#plot' 
 ). 
 click 
 (); 
  
 } 
 } 
  
 // End init function 
 // Call the initialize function once everything has loaded 
 google 
 . 
 maps 
 . 
 event 
 . 
 addDomListener 
 ( 
 window 
 , 
  
 'load' 
 , 
  
 initialize 
 ); 
 // Load the control panel in a floating div if it is not loaded in an iframe 
 // after the textarea has been rendered 
 $ 
 ( 
 "#coords" 
 ). 
 ready 
 ( 
 function 
 () 
  
 { 
  
 if 
  
 ( 
 ! 
 window 
 . 
 frameElement 
 ) 
  
 { 
  
 $ 
 ( 
 '#panel' 
 ). 
 addClass 
 ( 
 "floating panel" 
 ); 
  
 $ 
 ( 
 '#button-div' 
 ). 
 addClass 
 ( 
 "button-div" 
 ); 
  
 $ 
 ( 
 '#coords' 
 ). 
 removeClass 
 ( 
 "coords-large" 
 ). 
 addClass 
 ( 
 "coords-small" 
 ); 
  
 $ 
 ( 
 '#toggle' 
 ). 
 show 
 (); 
  
 $ 
 ( 
 '#map' 
 ). 
 height 
 ( 
 '100%' 
 ); 
  
 } 
 }); 
  
 /** 
 *  latlng literal with extra properties to use with the RoadsAPI 
 *  @typedef {Object} ExtendedLatLng 
 *   lat:string|float 
 *   lng:string|float 
 *   interpolated:boolean 
 *   unsnapped:boolean 
 */ 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers while the mouse is over it. 
 * @param {ExtendedLatLng} from - The origin of the line 
 * @param {ExtendedLatLng} to - The destination of the line 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addOverline 
 ( 
 from 
 , 
  
 to 
 ) 
  
 { 
  
 return 
  
 addLine 
 ( 
 "overline" 
 , 
  
 from 
 , 
  
 to 
 , 
  
 '#ff77ff' 
 , 
  
 4 
 , 
  
 1.0 
 , 
  
 2.0 
 , 
  
 false 
 ); 
 } 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers while the mouse is NOT over it. 
 * @param {ExtendedLatLng} from - The origin of the line 
 * @param {ExtendedLatLng} to - The destination of the line 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addOutline 
 ( 
 from 
 , 
  
 to 
 ) 
  
 { 
  
 return 
  
 addLine 
 ( 
 "outline" 
 , 
  
 from 
 , 
  
 to 
 , 
  
 '#bb33bb' 
 , 
  
 2 
 , 
  
 0.5 
 , 
  
 1.35 
 , 
  
 true 
 ); 
 } 
 /** 
 * Add a line to the map for highlighting the connection between two 
 * markers. 
 * @param {string}         attrib  - The attribute to use for managing the line 
 * @param {ExtendedLatLng} from    - The origin of the line 
 * @param {ExtendedLatLng} to      - The destination of the line 
 * @param {string}         color   - The color of the line 
 * @param {number}         weight  - The weight of the line 
 * @param {number}         opacity - The opacity of the line (0..1) 
 * @param {number}         scale   - The scale of the arrow-head (pt) 
 * @param {boolean}        visible - The visibility of the line 
 * @return {!Object}       line    - the polyline object created 
 */ 
 function 
  
 addLine 
 ( 
 attrib 
 , 
  
 from 
 , 
  
 to 
 , 
  
 color 
 , 
  
 weight 
 , 
  
 opacity 
 , 
  
 scale 
 , 
  
 visible 
 ) 
  
 { 
  
 from 
 [ 
 attrib 
 ] 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Polyline 
 ({ 
  
 path 
 : 
  
 [ 
 from 
 , 
  
 to 
 ], 
  
 strokeColor 
 : 
  
 color 
 , 
  
 strokeWeight 
 : 
  
 weight 
 , 
  
 strokeOpacity 
 : 
  
 opacity 
 , 
  
 icons 
 : 
 [{ 
  
 offset 
 : 
  
 "0%" 
 , 
  
 icon 
 : 
  
 { 
  
 scale 
 : 
  
 scale 
 /*pt*/ 
 , 
  
 path 
 : 
  
 google 
 . 
 maps 
 . 
 SymbolPath 
 . 
 BACKWARD_CLOSED_ARROW 
  
 } 
  
 }] 
  
 }); 
  
 from 
 [ 
 attrib 
 ]. 
 setVisible 
 ( 
 visible 
 ); 
  
 from 
 [ 
 attrib 
 ]. 
 setMap 
 ( 
 map 
 ); 
  
 to 
 [ 
 attrib 
 ] 
  
 = 
  
 from 
 [ 
 attrib 
 ]; 
  
 polylines 
 . 
 push 
 ( 
 from 
 [ 
 attrib 
 ]); 
  
 return 
  
 from 
 [ 
 attrib 
 ]; 
 } 
 /** 
 * Add a pair of lines to the map for highlighting the connection between two 
 * markers; one visible while the mouse is over the marker (the "overline"), 
 * the other while it is not (the "outline"). 
 * @param {ExtendedLatLng} from - The origin of the line (the original input) 
 * @param {ExtendedLatLng} to - The destination of the line (the snapped point) 
 * @return {!Object} line - the polyline object created 
 */ 
 function 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ) 
  
 { 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 overline 
 ) 
  
 { 
  
 addOverline 
 ( 
 coords 
 , 
  
 coords 
 . 
 related 
 ); 
  
 } 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 outline 
 ) 
  
 { 
  
 addOutline 
 ( 
 coords 
 , 
  
 coords 
 . 
 related 
 ); 
  
 } 
  
 marker 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 coords 
 . 
 outline 
 . 
 setVisible 
 ( 
 false 
 ); 
  
 coords 
 . 
 overline 
 . 
 setVisible 
 ( 
 true 
 ); 
  
 coords 
 . 
 related 
 . 
 marker 
 . 
 setOpacity 
 ( 
 1.0 
 ); 
  
 }); 
  
 marker 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 coords 
 . 
 overline 
 . 
 setVisible 
 ( 
 false 
 ); 
  
 coords 
 . 
 outline 
 . 
 setVisible 
 ( 
 true 
 ); 
  
 coords 
 . 
 related 
 . 
 marker 
 . 
 setOpacity 
 ( 
 0.5 
 ); 
  
 }); 
 } 
 /** 
 * Add a marker to the map and check for special 'interpolated' 
 * and 'unsnapped' properties to control which colour marker is used 
 * @param {ExtendedLatLng} coords - Coords of where to add the marker 
 * @return {!Object} marker - the marker object created 
 */ 
 function 
  
 addMarker 
 ( 
 coords 
 ) 
  
 { 
  
 var 
  
 marker 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 Marker 
 ({ 
  
 position 
 : 
  
 coords 
 , 
  
 title 
 : 
  
 coords 
 . 
 lat 
  
 + 
  
 ',' 
  
 + 
  
 coords 
 . 
 lng 
 , 
  
 map 
 : 
  
 map 
 , 
  
 opacity 
 : 
  
 0.5 
 , 
  
 icon 
 : 
  
 RED_MARKER 
  
 }); 
  
 // Coord should NEVER be interpolated AND unsnapped 
  
 if 
  
 ( 
 coords 
 . 
 interpolated 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 BLUE_MARKER 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 ! 
 coords 
 . 
 related 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 YELLOW_MARKER 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 coords 
 . 
 originalIndex 
  
 != 
  
 undefined 
 ) 
  
 { 
  
 marker 
 . 
 setIcon 
 ( 
 RED_MARKER 
 ); 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ); 
  
 } 
  
 else 
  
 { 
  
 marker 
 . 
 setIcon 
 ({ 
 url 
 : 
  
 GREEN_MARKER 
 , 
  
 scaledSize 
 : 
  
 { 
 width 
 : 
  
 20 
 , 
  
 height 
 : 
  
 20 
 }}); 
  
 addCorrespondence 
 ( 
 coords 
 , 
  
 marker 
 ); 
  
 } 
  
 // Make markers change opacity when the mouse scrubs across them 
  
 marker 
 . 
 addListener 
 ( 
 'mouseover' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 marker 
 . 
 setOpacity 
 ( 
 1.0 
 ); 
  
 }); 
  
 marker 
 . 
 addListener 
 ( 
 'mouseout' 
 , 
  
 function 
 ( 
 mevt 
 ) 
  
 { 
  
 marker 
 . 
 setOpacity 
 ( 
 0.5 
 ); 
  
 }); 
  
 coords 
 . 
 marker 
  
 = 
  
 marker 
 ; 
  
 // Save a reference for easy access later 
  
 markers 
 . 
 push 
 ( 
 marker 
 ); 
  
 return 
  
 marker 
 ; 
 } 
 /** 
 * Animate an icon along a polyline 
 * @param {Object} polyline The line to animate the icon along 
 */ 
 function 
  
 animateCircle 
 ( 
 polyline 
 ) 
  
 { 
  
 var 
  
 count 
  
 = 
  
 0 
 ; 
  
 // fallback icon if the poly has no icon to animate 
  
 var 
  
 defaultIcon 
  
 = 
  
 [ 
  
 { 
  
 icon 
 : 
  
 lineSymbol 
 , 
  
 offset 
 : 
  
 '100%' 
  
 } 
  
 ]; 
  
 window 
 . 
 setInterval 
 ( 
 function 
 () 
  
 { 
  
 count 
  
 = 
  
 ( 
 count 
  
 + 
  
 1 
 ) 
  
 % 
  
 200 
 ; 
  
 var 
  
 icons 
  
 = 
  
 polyline 
 . 
 get 
 ( 
 'icons' 
 ) 
  
 || 
  
 defaultIcon 
 ; 
  
 icons 
 [ 
 0 
 ]. 
 offset 
  
 = 
  
 ( 
 count 
  
 / 
  
 2 
 ) 
  
 + 
  
 '%' 
 ; 
  
 polyline 
 . 
 set 
 ( 
 'icons' 
 , 
  
 icons 
 ); 
  
 }, 
  
 20 
 ); 
 } 
 /** 
 * Fit the map bounds to the current set of markers 
 * @param {Array<Object>} markers Array of all map markers 
 */ 
 function 
  
 fitBounds 
 ( 
 markers 
 ) 
  
 { 
  
 var 
  
 bounds 
  
 = 
  
 new 
  
 google 
 . 
 maps 
 . 
 LatLngBounds 
 ; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 markers 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 bounds 
 . 
 extend 
 ( 
 markers 
 [ 
 i 
 ]. 
 getPosition 
 ()); 
  
 } 
  
 map 
 . 
 fitBounds 
 ( 
 bounds 
 ); 
 } 
 /** 
 * Uses Places library to get Place Details for a Place ID 
 * @param {string}   placeId         The Place ID to look up 
 * @param {Function} foundCallback   Called if the place is found 
 * @param {Function} missingCallback Called if nothing is found 
 * @param {Function} errorCallback   Called if request fails 
 */ 
 function 
  
 getPlaceDetails 
 ( 
 placeId 
 , 
  
 foundCallback 
 , 
  
 missingCallback 
 , 
  
 errorCallback 
 ) 
  
 { 
  
 var 
  
 request 
  
 = 
  
 { 
  
 placeId 
 : 
  
 placeId 
  
 }; 
  
 placesService 
 . 
 getDetails 
 ( 
 request 
 , 
  
 function 
 ( 
 place 
 , 
  
 status 
 ) 
  
 { 
  
 if 
  
 ( 
 status 
  
 == 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesServiceStatus 
 . 
 OK 
 ) 
  
 { 
  
 foundCallback 
 ( 
 place 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 status 
  
 == 
  
 google 
 . 
 maps 
 . 
 places 
 . 
 PlacesServiceStatus 
 . 
 NOT_FOUND 
 ) 
  
 { 
  
 missingCallback 
 (); 
  
 } 
  
 else 
  
 if 
  
 ( 
 errorCallback 
 ) 
  
 { 
  
 errorCallback 
 (); 
  
 } 
  
 }); 
 } 
 /** 
 * AJAX request to the Roads Speed Limit API. 
 * Request the speed limit for the Place ID 
 * @param {string}   placeId         Place ID to request the speed limit for 
 * @param {Function} successCallback Called if request is successful 
 * @param {Function} errorCallback   Called if request fails 
 */ 
 function 
  
 getSpeedLimit 
 ( 
 placeId 
 , 
  
 successCallback 
 , 
  
 errorCallback 
 ) 
  
 { 
  
 $ 
 . 
 ajax 
 ({ 
  
 type 
 : 
  
 'GET' 
 , 
  
 url 
 : 
  
 SPEED_LIMIT_URL 
 , 
  
 data 
 : 
  
 { 
  
 placeId 
 : 
  
 placeId 
 , 
  
 key 
 : 
  
 API_KEY 
  
 }, 
  
 success 
 : 
  
 successCallback 
 , 
  
 error 
 : 
  
 errorCallback 
  
 }); 
 } 
 /** 
 * Open an infowindow on either the map or the active streetview pano 
 * @param {Object} infowindow Infowindow to be opened 
 * @param {Object} marker Marker the infowindow is anchored to 
 */ 
 function 
  
 openInfoWindow 
 ( 
 infowindow 
 , 
  
 marker 
 ) 
  
 { 
  
 // If streetView is visible display the infoWindow over the pano 
  
 // and anchor to the marker 
  
 if 
  
 ( 
 map 
 . 
 getStreetView 
 (). 
 getVisible 
 ()) 
  
 { 
  
 infowindow 
 . 
 open 
 ( 
 map 
 . 
 getStreetView 
 (), 
  
 marker 
 ); 
  
 } 
  
 // Otherwise open it on the map and anchor to the marker 
  
 else 
  
 { 
  
 infowindow 
 . 
 open 
 ( 
 map 
 , 
  
 marker 
 ); 
  
 } 
 } 
 /** 
 * Add event listener to for when the active pano changes 
 */ 
 function 
  
 listenForPanoChange 
 () 
  
 { 
  
 var 
  
 pano 
  
 = 
  
 map 
 . 
 getStreetView 
 (); 
  
 // Close all open markers when the pano changes 
  
 google 
 . 
 maps 
 . 
 event 
 . 
 addListener 
 ( 
 pano 
 , 
  
 'position_changed' 
 , 
  
 function 
 () 
  
 { 
  
 closeAllInfoWindows 
 ( 
 infoWindows 
 ); 
  
 }); 
 } 
 /** 
 * Close all open infoWindows 
 * @param {Array<Object>} infoWindows - all infowindow objects 
 */ 
 function 
  
 closeAllInfoWindows 
 ( 
 infoWindows 
 ) 
  
 { 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 infoWindows 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 infoWindows 
 [ 
 i 
 ]. 
 close 
 (); 
  
 } 
 } 
  
< / 
 script 
>  
< / 
 head 
>  
< body 
>  
< div 
  
 class 
 = 
 "floating" 
  
 id 
 = 
 "toggle" 
>& mdash 
 ; 
< /div 
>  
< div 
  
 id 
 = 
 "panel" 
>  
< div 
  
 class 
 = 
 "block" 
>  
< strong>Sample 
  
 Queries 
< / 
 strong 
>  
< div 
  
 id 
 = 
 "button-div" 
>  
< button 
  
 id 
 = 
 "eg1" 
  
 class 
 = 
 "button raised blue" 
> EXAMPLE 
  
 1 
< / 
 button 
>  
< button 
  
 id 
 = 
 "eg2" 
  
 class 
 = 
 "button raised blue" 
> EXAMPLE 
  
 2 
< / 
 button 
>  
< button 
  
 id 
 = 
 "eg3" 
  
 class 
 = 
 "button raised blue" 
> EXAMPLE 
  
 3 
< / 
 button 
>  
< button 
  
 id 
 = 
 "eg4" 
  
 class 
 = 
 "button raised blue" 
> EXAMPLE 
  
 4 
< / 
 button 
>  
< / 
 div 
>  
< / 
 div 
>  
< form 
  
 id 
 = 
 "controls" 
>  
< div 
  
 class 
 = 
 "block" 
>  
< div 
>  
< strong><span 
  
 id 
 = 
 "requestURL" 
> Request 
  
 URL 
< / 
 span 
>  
 or 
  
 Path 
  
 ( 
 Pipe 
  
 Separated 
 ) 
< / 
 strong><br> 
  
< textarea 
  
 id 
 = 
 "coords" 
  
 class 
 = 
 "u-full-width coords-large" 
  
 type 
 = 
 "text" 
  
 placeholder 
 = 
 "-35.123,150.332 | 80.654,22.439" 
  
 id 
 = 
 "exampleEmailInput" 
>< / 
 textarea 
>  
< / 
 div 
>  
< div 
>  
< label>Interpolate 
 : 
  
< /label 
>  
< input 
  
 for 
 = 
 "interpolate" 
  
 id 
 = 
 "interpolate" 
  
 type 
 = 
 "checkbox" 
  
 checked 
 / 
>  
< /div 
>  
< /div 
>  
< div 
>  
< div 
  
 class 
 = 
 "block" 
>  
< button 
  
 id 
 = 
 "plot" 
  
 class 
 = 
 "button raised blue" 
> Plot 
  
 a 
  
 Course 
< / 
 button 
>  
< button 
  
 id 
 = 
 "distance" 
  
 class 
 = 
 "button raised blue" 
> Toggle 
  
 Distances 
< / 
 button 
>  
< / 
 div 
>  
< div 
  
 id 
 = 
 "legend" 
>  
< img 
  
 src 
 = 
 "https://maps.google.com/mapfiles/ms/icons/green-dot.png" 
  
 style 
 = 
 "height:16px;" 
>  
 Original 
  
< img 
  
 src 
 = 
 "https://maps.google.com/mapfiles/ms/icons/red-dot.png" 
 / 
>  
 Snapped 
  
< img 
  
 src 
 = 
 "https://maps.google.com/mapfiles/ms/icons/blue-dot.png" 
 / 
>  
 Interpolated 
  
< img 
  
 src 
 = 
 "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png" 
 / 
>  
 Unsnappable 
  
< / 
 div 
>  
< div 
>  
< p 
  
 id 
 = 
 "warningMessage" 
>< / 
 p 
>  
< p 
  
 id 
 = 
 "unsnappedPoints" 
>< / 
 p 
>  
< / 
 div 
>  
< / 
 div 
>  
< / 
 form 
>  
< / 
 div 
>  
< div 
  
 id 
 = 
 "map" 
>  
< / 
 div 
>  
< / 
 body 
>
< /html 
>
Create a Mobile Website
View Site in Mobile | Classic
Share by: