Search code examples
javascriptgoogle-mapsgoogle-maps-api-3google-polyline

Google maps draw distance traveled polyline


I would like to draw a 'distance traveled' polyline over a preset route using V3 of the google maps API.

The polyline would need to run through multiple waypoints/legs.

I am currently using the DirectionsService to draw the complete route.

I am also using epolys.js to get the marker position for the distance traveled.

I am copying the complete route into a new polyline, but I would only like to copy the route up to the marker position.

Demo link: http://codepen.io/1983ron/pen/wKMVQr

And here's where I'm currently at with the JS

var geocoder;
var map;
var marker;
var gmarkers = [];
var METERS_TO_MILES = 0.000621371192;
var walked = (Math.round(670 * 1609.344));

//ICON
var iconImage = new google.maps.MarkerImage(
    'http://www.ronnieatkinson.co.uk/clients/wc2015/maps/01/mapIcons/marker_red.png',       //MARKER URL
    new google.maps.Size(20, 34),   //MARKER SIZE (WxH)
    new google.maps.Point(0,0),     //MARKER ORIGIN
    new google.maps.Point(9, 34)    //MARKER ANCHOR
);

//SHADOW
var iconShadow = new google.maps.MarkerImage(
    'http://www.google.com/mapfiles/shadow50.png',  //SHADOW URL
    new google.maps.Size(37, 34),                   //SHADOW SIZE (WxH)
    new google.maps.Point(0,0),                     //SHADOW ORIGIN
    new google.maps.Point(9, 34)                    //SHADOW ANCHOR
);

//INFO WINDOW
var infowindow = new google.maps.InfoWindow({ 
    size: new google.maps.Size(150,50)
});

//CREATE MARKER
function createMarker(latlng, label, html) {
    var contentString = '<b>'+label+'</b><br>'+html;
    var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        shadow: iconShadow,
        icon: iconImage,
        title: label,
        zIndex: Math.round(latlng.lat()*-100000)<<5
    });

    marker.myname = label;
    gmarkers.push(marker);

    google.maps.event.addListener(marker, 'click', function() {
        infowindow.setContent(contentString); 
        infowindow.open(map,marker);
    });
    return marker;
}

function initialize() {
    var latlng = new google.maps.LatLng(51.555967, -0.279736);
    var myOptions = {
        zoom: 9,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.TERRAIN
    };

    map = new google.maps.Map(document.getElementById("map"), myOptions);

    var rendererOptions = {
        map: map,
        suppressMarkers: true,
    };

    directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);

    //Edinburgh to Eden Project
    var point1 = new google.maps.LatLng(58.981386, -2.973751); //Orkney Rugby Football Club
    var point2 = new google.maps.LatLng(55.881517, -4.342057); //Scotstoun Stadium
    var point3 = new google.maps.LatLng(54.998080, -7.319812); //Guildhall St
    var point4 = new google.maps.LatLng(54.563716, -5.942729); //Belfast Harlequins RFC
    var point5 = new google.maps.LatLng(53.271057, -9.054253); //Hotel Spanish Arch
    var point6 = new google.maps.LatLng(52.674222, -8.642515); //Thomond Park
    var point7 = new google.maps.LatLng(53.291755, -3.713769); //Colwyn Leisure Centre
    var point8 = new google.maps.LatLng(51.748180, -3.618567); //Glynneath Rugby Football Club

    var wps = [
        { location: point1 },
        { location: point2 },
        { location: point3 },
        { location: point4 },
        { location: point5 },
        { location: point6 },
        { location: point7 },
        { location: point8 }
    ];

    //START
    var org = new google.maps.LatLng (55.945315, -3.205309); //EDINBURGH 

    //FINISH
    var dest = new google.maps.LatLng (50.360130, -4.744717); //EDEN PROJECT

    var request = {
        origin: org,
        destination: dest,
        waypoints: wps,
        travelMode: google.maps.DirectionsTravelMode.WALKING,
        //unitSystem: google.maps.UnitSystem.IMPERIAL
    };

    directionsService = new google.maps.DirectionsService();
    directionsService.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            //SHOW ROUTE
            directionsDisplay.setDirections(response);

            //COPY POLY FROM DIRECTION SERVICE
            var polyline = new google.maps.Polyline({
                path: [],
                strokeColor: '#FF0000',
                strokeWeight: 3
            });

            var bounds = new google.maps.LatLngBounds();
            var legs = response.routes[0].legs;
            for (i=0;i<legs.length;i++) {
                var steps = legs[i].steps;
                for (j=0;j<steps.length;j++) {
                    var nextSegment = steps[j].path;
                    for (k=0;k<nextSegment.length;k++) {
                        polyline.getPath().push(nextSegment[k]);
                        bounds.extend(nextSegment[k]);
                    }
                }
            }

            polyline.setMap(map);
            map.fitBounds(bounds);

            //alert(walked);

            //ADD MARKER TO NEW POLYLINE AT 'X' DISTANCE
            createMarker(polyline.GetPointAtDistance(walked), "You are here", (Math.round( walked * METERS_TO_MILES * 10 ) / 10)+' miles');

            //GET THE TOTAL DISTANCE
            var distance= 0;
            //var METERS_TO_MILES = 0.000621371192;
            for(i = 0; i < response.routes[0].legs.length; i++){
                //FOR EACH LEG GET THE DISTANCE AND ADD IT TO THE TOTAL
                distance += parseFloat(response.routes[0].legs[i].distance.value);
            }
            //alert((Math.round( distance * METERS_TO_MILES * 10 ) / 10)+' miles');
            //alert(distance); //METERS

        } else if(status == google.maps.DirectionsStatus.MAX_WAYPOINTS_EXCEEDED){
            alert ('Max waypoints exceeded');
        } else {
            alert ('failed to get directions');
        }
    });
};window.onload = function() { initialize(); };

Any help would be appreciated.


Solution

  • Calculate the length of the line as you create it. Once it becomes greater than or equal to the distance "walked", stop adding points to it.

    var lengthMeters = 0;
    var legs = response.routes[0].legs;
    for (i = 0; i < legs.length; i++) {
        var steps = legs[i].steps;
        for (j = 0; j < steps.length; j++) {
            var nextSegment = steps[j].path;
            for (k = 0; k < nextSegment.length; k++) {
                if (lengthMeters <= walked) { 
                    // if polyline is less than distance "walked", keep adding to it
                    polyline.getPath().push(nextSegment[k]);
                    if (polyline.getPath().getLength() > 1) {
                       var lastPt = polyline.getPath().getLength() - 1;
                       lengthMeters += google.maps.geometry.spherical.computeDistanceBetween(polyline.getPath().getAt(lastPt - 1), polyline.getPath().getAt(lastPt));
                    }
                }
                bounds.extend(nextSegment[k]);
             }
        }
    }
    polyline.setMap(map);
    

    proof of concept fiddle

    screenshot of resulting map

    code snippet:

    var geocoder;
    var map;
    var marker;
    var gmarkers = [];
    var METERS_TO_MILES = 0.000621371192;
    var walked = (Math.round(670 * 1609.344));
    
    //ICON
    var iconImage = {
    
      url: 'http://www.ronnieatkinson.co.uk/clients/wc2015/maps/01/mapIcons/marker_red.png', //MARKER URL
      size: new google.maps.Size(20, 34), //MARKER SIZE (WxH)
      origin: new google.maps.Point(0, 0), //MARKER ORIGIN
      anchor: new google.maps.Point(9, 34) //MARKER ANCHOR
    };
    
    //INFO WINDOW
    var infowindow = new google.maps.InfoWindow({
      size: new google.maps.Size(150, 50)
    });
    
    //CREATE MARKER
    function createMarker(latlng, label, html) {
      var contentString = '<b>' + label + '</b><br>' + html;
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        icon: iconImage,
        title: label,
        zIndex: Math.round(latlng.lat() * -100000) << 5
      });
    
      marker.myname = label;
      gmarkers.push(marker);
    
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.setContent(contentString);
        infowindow.open(map, marker);
      });
      return marker;
    }
    
    function initialize() {
      var latlng = new google.maps.LatLng(51.555967, -0.279736);
      var myOptions = {
        zoom: 9,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.TERRAIN
      };
    
      map = new google.maps.Map(document.getElementById("map"), myOptions);
    
      var rendererOptions = {
        map: map,
        suppressMarkers: true,
      };
    
      directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
    
      //Edinburgh to Eden Project
      var point1 = new google.maps.LatLng(58.981386, -2.973751); //Orkney Rugby Football Club
      var point2 = new google.maps.LatLng(55.881517, -4.342057); //Scotstoun Stadium
      var point3 = new google.maps.LatLng(54.998080, -7.319812); //Guildhall St
      var point4 = new google.maps.LatLng(54.563716, -5.942729); //Belfast Harlequins RFC
      var point5 = new google.maps.LatLng(53.271057, -9.054253); //Hotel Spanish Arch
      var point6 = new google.maps.LatLng(52.674222, -8.642515); //Thomond Park
      var point7 = new google.maps.LatLng(53.291755, -3.713769); //Colwyn Leisure Centre
      var point8 = new google.maps.LatLng(51.748180, -3.618567); //Glynneath Rugby Football Club
    
      var wps = [{
        location: point1
      }, {
        location: point2
      }, {
        location: point3
      }, {
        location: point4
      }, {
        location: point5
      }, {
        location: point6
      }, {
        location: point7
      }, {
        location: point8
      }];
    
      //START
      var org = new google.maps.LatLng(55.945315, -3.205309); //EDINBURGH 
    
      //FINISH
      var dest = new google.maps.LatLng(50.360130, -4.744717); //EDEN PROJECT
    
      var request = {
        origin: org,
        destination: dest,
        waypoints: wps,
        travelMode: google.maps.DirectionsTravelMode.WALKING,
        //unitSystem: google.maps.UnitSystem.IMPERIAL
      };
    
      directionsService = new google.maps.DirectionsService();
      directionsService.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
          //SHOW ROUTE
          directionsDisplay.setDirections(response);
    
          //COPY POLY FROM DIRECTION SERVICE
          var polyline = new google.maps.Polyline({
            path: [],
            strokeColor: '#FF0000',
            strokeWeight: 3
          });
    
          var bounds = new google.maps.LatLngBounds();
          var lengthMeters = 0;
          var legs = response.routes[0].legs;
          for (i = 0; i < legs.length; i++) {
            var steps = legs[i].steps;
            for (j = 0; j < steps.length; j++) {
              var nextSegment = steps[j].path;
              for (k = 0; k < nextSegment.length; k++) {
    
                if (lengthMeters <= walked) {
                  polyline.getPath().push(nextSegment[k]);
                  if (polyline.getPath().getLength() > 1) {
                    var lastPt = polyline.getPath().getLength() - 1;
                    lengthMeters += google.maps.geometry.spherical.computeDistanceBetween(polyline.getPath().getAt(lastPt - 1), polyline.getPath().getAt(lastPt));
                  }
                }
                bounds.extend(nextSegment[k]);
    
              }
            }
          }
    
          polyline.setMap(map);
          map.fitBounds(bounds);
    
          //alert(walked);
    
          //ADD MARKER TO NEW POLYLINE AT 'X' DISTANCE
          createMarker(polyline.GetPointAtDistance(walked), "You are here", (Math.round(walked * METERS_TO_MILES * 10) / 10) + ' miles');
    
          //GET THE TOTAL DISTANCE
          var distance = 0;
          //var METERS_TO_MILES = 0.000621371192;
          for (i = 0; i < response.routes[0].legs.length; i++) {
            //FOR EACH LEG GET THE DISTANCE AND ADD IT TO THE TOTAL
            distance += parseFloat(response.routes[0].legs[i].distance.value);
          }
          //alert((Math.round( distance * METERS_TO_MILES * 10 ) / 10)+' miles');
          //alert(distance); //METERS
    
        } else if (status == google.maps.DirectionsStatus.MAX_WAYPOINTS_EXCEEDED) {
          alert('Max waypoints exceeded');
        } else {
          alert('failed to get directions');
        }
      });
    };
    window.onload = function() {
      initialize();
    };
    
    /*********************************************************************\
    *                                                                     *
    * epolys.js                                          by Mike Williams *
    * updated to API v3                                  by Larry Ross    *
    *                                                                     *
    * A Google Maps API Extension                                         *
    *                                                                     *
    * Adds various Methods to google.maps.Polygon and google.maps.Polyline *
    *                                                                     *
    * .Contains(latlng) returns true is the poly contains the specified   *
    *                   GLatLng                                           *
    *                                                                     *
    * .Area()           returns the approximate area of a poly that is    *
    *                   not self-intersecting                             *
    *                                                                     *
    * .Distance()       returns the length of the poly path               *
    *                                                                     *
    * .Bounds()         returns a GLatLngBounds that bounds the poly      *
    *                                                                     *
    * .GetPointAtDistance() returns a GLatLng at the specified distance   *
    *                   along the path.                                   *
    *                   The distance is specified in metres               *
    *                   Reurns null if the path is shorter than that      *
    *                                                                     *
    * .GetPointsAtDistance() returns an array of GLatLngs at the          *
    *                   specified interval along the path.                *
    *                   The distance is specified in metres               *
    *                                                                     *
    * .GetIndexAtDistance() returns the vertex number at the specified    *
    *                   distance along the path.                          *
    *                   The distance is specified in metres               *
    *                   Returns null if the path is shorter than that      *
    *                                                                     *
    * .Bearing(v1?,v2?) returns the bearing between two vertices          *
    *                   if v1 is null, returns bearing from first to last *
    *                   if v2 is null, returns bearing from v1 to next    *
    *                                                                     *
    *                                                                     *
    ***********************************************************************
    *                                                                     *
    *   This Javascript is provided by Mike Williams                      *
    *   Blackpool Community Church Javascript Team                        *
    *   http://www.blackpoolchurch.org/                                   *
    *   http://econym.org.uk/gmap/                                        *
    *                                                                     *
    *   This work is licenced under a Creative Commons Licence            *
    *   http://creativecommons.org/licenses/by/2.0/uk/                    *
    *                                                                     *
    ***********************************************************************
    *                                                                     *
    * Version 1.1       6-Jun-2007                                        *
    * Version 1.2       1-Jul-2007 - fix: Bounds was omitting vertex zero *
    *                                add: Bearing                         *
    * Version 1.3       28-Nov-2008  add: GetPointsAtDistance()           *
    * Version 1.4       12-Jan-2009  fix: GetPointsAtDistance()           *
    * Version 3.0       11-Aug-2010  update to v3                         *
    *                                                                     *
    \*********************************************************************/
    
    // === first support methods that don't (yet) exist in v3
    google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
      var EarthRadiusMeters = 6378137.0; // meters
      var lat1 = this.lat();
      var lon1 = this.lng();
      var lat2 = newLatLng.lat();
      var lon2 = newLatLng.lng();
      var dLat = (lat2 - lat1) * Math.PI / 180;
      var dLon = (lon2 - lon1) * Math.PI / 180;
      var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
      var d = EarthRadiusMeters * c;
      return d;
    }
    
    google.maps.LatLng.prototype.latRadians = function() {
      return this.lat() * Math.PI / 180;
    }
    
    google.maps.LatLng.prototype.lngRadians = function() {
      return this.lng() * Math.PI / 180;
    }
    
    // === A method which returns the length of a path in metres ===
    google.maps.Polygon.prototype.Distance = function() {
      var dist = 0;
      for (var i = 1; i < this.getPath().getLength(); i++) {
        dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1));
      }
      return dist;
    }
    
    // === A method which returns a GLatLng of a point a given distance along the path ===
    // === Returns null if the path is shorter than the specified distance ===
    google.maps.Polygon.prototype.GetPointAtDistance = function(metres) {
      // some awkward special cases
      if (metres == 0) return this.getPath().getAt(0);
      if (metres < 0) return null;
      if (this.getPath().getLength() < 2) return null;
      var dist = 0;
      var olddist = 0;
      for (var i = 1;
        (i < this.getPath().getLength() && dist < metres); i++) {
        olddist = dist;
        dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1));
      }
      if (dist < metres) {
        return null;
      }
      var p1 = this.getPath().getAt(i - 2);
      var p2 = this.getPath().getAt(i - 1);
      var m = (metres - olddist) / (dist - olddist);
      return new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
    }
    
    // === A method which returns an array of GLatLngs of points a given interval along the path ===
    google.maps.Polygon.prototype.GetPointsAtDistance = function(metres) {
      var next = metres;
      var points = [];
      // some awkward special cases
      if (metres <= 0) return points;
      var dist = 0;
      var olddist = 0;
      for (var i = 1;
        (i < this.getPath().getLength()); i++) {
        olddist = dist;
        dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1));
        while (dist > next) {
          var p1 = this.getPath().getAt(i - 1);
          var p2 = this.getPath().getAt(i);
          var m = (next - olddist) / (dist - olddist);
          points.push(new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m));
          next += metres;
        }
      }
      return points;
    }
    
    // === A method which returns the Vertex number at a given distance along the path ===
    // === Returns null if the path is shorter than the specified distance ===
    google.maps.Polygon.prototype.GetIndexAtDistance = function(metres) {
      // some awkward special cases
      if (metres == 0) return this.getPath().getAt(0);
      if (metres < 0) return null;
      var dist = 0;
      var olddist = 0;
      for (var i = 1;
        (i < this.getPath().getLength() && dist < metres); i++) {
        olddist = dist;
        dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1));
      }
      if (dist < metres) {
        return null;
      }
      return i;
    }
    
    // === Copy all the above functions to GPolyline ===
    google.maps.Polyline.prototype.Distance = google.maps.Polygon.prototype.Distance;
    google.maps.Polyline.prototype.GetPointAtDistance = google.maps.Polygon.prototype.GetPointAtDistance;
    google.maps.Polyline.prototype.GetPointsAtDistance = google.maps.Polygon.prototype.GetPointsAtDistance;
    google.maps.Polyline.prototype.GetIndexAtDistance = google.maps.Polygon.prototype.GetIndexAtDistance;
    html,
    body,
    #map,
    #container {
      height: 100%;
      width: 100%;
      background: #000;
      padding: 0px;
      margin: 0px;
    }
    <div id="container">
      <div id="map"></div>
    </div>
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk">
    </script>