Search code examples
javascriptgoogle-mapsgoogle-maps-api-3directions

Google directions route - marker at midpoint


How can I add a clickable marker in the middle of rendered route, which would then display a info window with custom data, because a info window can't be directly added to directions renderer?

 function loadRoute0() {
  var request0 = {
    origin: new google.maps.LatLng(46.56300788, 15.62779705),
    destination: new google.maps.LatLng(46.55953332, 15.62616729),
    travelMode: google.maps.TravelMode.WALKING
  };

  directionsService.route(request0, function(result, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      var renderer = new google.maps.DirectionsRenderer({
        polylineOptions: {
          strokeColor: "#00FF00"
        },
        suppressMarkers: true,
        map: map
      });
      renderer.setDirections(result);
    }
  });
 }

I read that we can use step, but I'm not sure how to implement that.

 marker.setPosition(myRoute.steps[i].start_location);

Solution

  • To calculate the midpoint of your route:

    1. capture the entire route returned from the directions service
    polyline.setPath([]);
    var route = result.routes[0];
    
    var path = result.routes[0].overview_path;
    var legs = result.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]);
        }
      }
    }
    
    1. compute its length
    function computeTotalDistance(result) {
      totalDist = 0;
      totalTime = 0;
      var myroute = result.routes[0];
      for (i = 0; i < myroute.legs.length; i++) {
        totalDist += myroute.legs[i].distance.value;
        totalTime += myroute.legs[i].duration.value;
      }
      putMarkerOnRoute(50);
    
      totalDist = totalDist / 1000.
      document.getElementById("total").innerHTML = "total distance is: " + totalDist + " km<br>total time is: " + (totalTime / 60).toFixed(2) + " minutes";
    }
    
    1. find the point at 50% of that length (one option for doing that would be to use the GetPointAtDistance function from the epoly library, ported to the v3 API).
    function putMarkerOnRoute(percentage) {
      var distance = (percentage / 100) * totalDist;
      var time = ((percentage / 100) * totalTime / 60).toFixed(2);
      if (!marker) {
        marker = createMarker(polyline.GetPointAtDistance(distance), "time: " + time, "marker");
      } else {
        marker.setPosition(polyline.GetPointAtDistance(distance));
        marker.setTitle("time:" + time);
      }
    }
    

    proof of concept fiddle

    screenshot of resulting map

    code snippet:

    function loadRoute0() {
      var request0 = {
        origin: new google.maps.LatLng(46.56300788, 15.62779705),
        destination: new google.maps.LatLng(46.55953332, 15.62616729),
        travelMode: google.maps.TravelMode.WALKING
      };
    
      directionsService.route(request0, function(result, status) {
        if (status == google.maps.DirectionsStatus.OK) {
          var renderer = new google.maps.DirectionsRenderer({
            polylineOptions: {
              strokeColor: "#00FF00"
            },
            suppressMarkers: true,
            map: map
          });
          renderer.setDirections(result);
          polyline.setPath([]);
          var route = result.routes[0];
    
          var path = result.routes[0].overview_path;
          var legs = result.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]);
              }
            }
          }
          computeTotalDistance(result);
        }
      });
    }
    var directionsService, map, marker, polyline, infowindow;
    
    function initialize() {
      map = new google.maps.Map(
        document.getElementById("map_canvas"), {
          center: new google.maps.LatLng(37.4419, -122.1419),
          zoom: 13,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });
      polyline = new google.maps.Polyline({
        path: [],
        strokeColor: '#FF0000',
        strokeWeight: 3
      });
      infowindow = new google.maps.InfoWindow();
      directionsService = new google.maps.DirectionsService();
      loadRoute0();
    }
    google.maps.event.addDomListener(window, "load", initialize);
    var totalDist = 0;
    var totalTime = 0;
    
    function computeTotalDistance(result) {
      totalDist = 0;
      totalTime = 0;
      var myroute = result.routes[0];
      for (i = 0; i < myroute.legs.length; i++) {
        totalDist += myroute.legs[i].distance.value;
        totalTime += myroute.legs[i].duration.value;
      }
      putMarkerOnRoute(50);
    
      totalDist = totalDist / 1000.
      document.getElementById("total").innerHTML = "total distance is: " + totalDist + " km<br>total time is: " + (totalTime / 60).toFixed(2) + " minutes";
    }
    
    function putMarkerOnRoute(percentage) {
      var distance = (percentage / 100) * totalDist;
      var time = ((percentage / 100) * totalTime / 60).toFixed(2);
      if (!marker) {
        marker = createMarker(polyline.GetPointAtDistance(distance), "time: " + time, "midpoint");
      } else {
        marker.setPosition(polyline.GetPointAtDistance(distance));
        marker.setTitle("time:" + time);
      }
      google.maps.event.trigger(marker, 'click');
    }
    
    function createMarker(latlng, label, html) {
      var contentString = '<b>' + label + '</b><br>' + html;
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        title: label,
        zIndex: Math.round(latlng.lat() * -100000) << 5
      });
      marker.myname = label;
    
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.setContent(contentString + "<br>" + marker.getPosition().toUrlValue(6));
        infowindow.open(map, marker);
      });
      return marker;
    }
    // modified from epoly http://econym.org.uk/gmap/epoly.htm
    // (ported to v3)
    // === 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.Polyline.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 += google.maps.geometry.spherical.computeDistanceBetween(this.getPath().getAt(i), 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);
    }
    html,
    body {
      height: 100%;
      width: 100%;
      margin: 0px;
      padding: 0px;
    }
    
    #map_canvas {
      height: 90%;
      width: 100%;
    }
    <script src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
    <div id="total"></div>
    <div id="map_canvas"></div>