2

How do I return a latLng value from a given percentage along a polyLine? I have spent a day on this using interpolate and individual nodes. Is their an easier function out there that does the grunt work?

Google maps API v3 thanks!

1 Answers1

7

http://www.geocodezip.com/scripts/v3_epoly.js

written for the Google Maps Javascript API v2 ported to v3. Documentation for the v2 version

Has these two methods:

  • .Distance() returns the length of the poly path
  • .GetPointAtDistance() returns a GLatLng at the specified distance
    along the path.
    The distance is specified in metres
    Returns null if the path is shorter than that

This should work (if you include that script and your polyline variable is "polyline"):

var latlng = polyline.GetPointAtDistance(polyline.Distance()*(desired percentage)/100);

of course if you polyline isn't changing in length, it would be more efficient to compute the length once an use it each time you want to find a point on the polyline.

var polylength = polyline.Distance();
var latlng = polylength*(desired percentage)/100);

live example

code snippet:

var directionDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
var polyline = null;
var marker;
var infowindow;

function createMarker(latlng, label, html) {
  // alert("createMarker("+latlng+","+label+","+html+","+color+")");
  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,
    contentString: contentString
  });
  marker.myname = label;
  // gmarkers.push(marker);

  google.maps.event.addListener(marker, 'click', function() {
    infowindow.setContent(this.contentString);
    infowindow.open(map, marker);
  });
  return marker;
}
var myLatLng = null;
var lat;
var lng;
var zoom = 2;
var maptype;

function initialize() {
  infowindow = new google.maps.InfoWindow();
  myLatLng = new google.maps.LatLng(37.422104808, -122.0838851);
  maptype = google.maps.MapTypeId.ROADMAP;
  // If there are any parameters at eh end of the URL, they will be in  location.search
  // looking something like  "?marker=3"

  // skip the first character, we are not interested in the "?"
  var query = location.search.substring(1);

  // split the rest at each "&" character to give a list of  "argname=value"  pairs
  var pairs = query.split("&");
  for (var i = 0; i < pairs.length; i++) {
    // break each pair at the first "=" to obtain the argname and value
    var pos = pairs[i].indexOf("=");
    var argname = pairs[i].substring(0, pos).toLowerCase();
    var value = pairs[i].substring(pos + 1);

    // process each possible argname  -  use unescape() if theres any chance of spaces
    if (argname == "filename") {
      filename = unescape(value);
    }
    if (argname == "lat") {
      lat = parseFloat(value);
    }
    if (argname == "lng") {
      lng = parseFloat(value);
    }
    if (argname == "start") {
      document.getElementById("start").value = decodeURI(value);
    }
    if (argname == "end") {
      document.getElementById("end").value = decodeURI(value);
    }
    if (argname == "time") {
      document.getElementById("time").value = decodeURI(value);
      // putMarkerOnRoute(parseFloat(document.getElementById('time').value));
    }
    if (argname == "zoom") {
      zoom = parseInt(value);
    }
    if (argname == "type") {
      // from the v3 documentation 8/24/2010
      // HYBRID This map type displays a transparent layer of major streets on satellite images. 
      // ROADMAP This map type displays a normal street map. 
      // SATELLITE This map type displays satellite images. 
      // TERRAIN This map type displays maps with physical features such as terrain and vegetation. 
      if (value == "m") {
        maptype = google.maps.MapTypeId.ROADMAP;
      }
      if (value == "k") {
        maptype = google.maps.MapTypeId.SATELLITE;
      }
      if (value == "h") {
        maptype = google.maps.MapTypeId.HYBRID;
      }
      if (value == "t") {
        maptype = google.maps.MapTypeId.TERRAIN;
      }

    }
  }
  if (!isNaN(lat) && !isNaN(lng)) {
    myLatLng = new google.maps.LatLng(lat, lng);
  }
  var myOptions = {
    zoom: zoom,
    center: myLatLng,
    mapTypeId: maptype
  };
  directionsDisplay = new google.maps.DirectionsRenderer({
    suppressMarkers: true
  });

  map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
  polyline = new google.maps.Polyline({
    path: [],
    strokeColor: '#FF0000',
    strokeWeight: 3
  });
  directionsDisplay.setMap(map);
  calcRoute();
}

function calcRoute() {

  var start = document.getElementById("start").value;
  var end = document.getElementById("end").value;
  var travelMode = google.maps.DirectionsTravelMode.DRIVING

  var request = {
    origin: start,
    destination: end,
    travelMode: travelMode
  };
  directionsService.route(request, function(response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      polyline.setPath([]);
      var bounds = new google.maps.LatLngBounds();
      startLocation = new Object();
      endLocation = new Object();
      directionsDisplay.setDirections(response);
      var route = response.routes[0];
      var summaryPanel = document.getElementById("directions_panel");
      summaryPanel.innerHTML = "";

      // For each route, display summary information.
      var path = response.routes[0].overview_path;
      var legs = response.routes[0].legs;
      for (i = 0; i < legs.length; i++) {
        if (i == 0) {
          startLocation.latlng = legs[i].start_location;
          startLocation.address = legs[i].start_address;
          // marker = google.maps.Marker({map:map,position: startLocation.latlng});
          // marker = createMarker(legs[i].start_location,"start",legs[i].start_address,"green");
        }
        endLocation.latlng = legs[i].end_location;
        endLocation.address = legs[i].end_address;
        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);

      computeTotalDistance(response);
      putMarkerOnRoute(parseFloat(document.getElementById('percent').value));
    } else {
      alert("directions response " + status);
    }
  });
}

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;
  }
  totalDist = totalDist / 1000.
  document.getElementById("total").innerHTML = "total distance is: " + totalDist + " km<br>total time is: " + (totalTime / 60).toFixed(2) + " minutes<br>average speed is: " + (totalDist / (totalTime / 3600)).toFixed(2) + " kph";
  document.getElementById("totalTime").value = (totalTime / 60.).toFixed(2);
}

function putMarkerOnRoute(percent) {
  if (percent > 100) {
    percent = 100;
    document.getElementById('percent').value = percent;
  }

  var distance = percent / 100 * totalDist * 1000;
  // time = ((percentage/100) * totalTIme/60).toFixed(2);
  // alert("Time:"+time+" totalTime:"+totalTime+" totalDist:"+totalDist+" dist:"+distance);
  if (!marker) {
    marker = createMarker(polyline.GetPointAtDistance(distance), "percent: " + percent, "marker");
  } else {
    marker.setPosition(polyline.GetPointAtDistance(distance));
    marker.setTitle("percent:" + percent);
    marker.contentString = "<b>percent: " + percent + "</b><br>distance: " + (distance / 1000).toFixed(2) + " km<br>marker";
    google.maps.event.trigger(marker, "click");
  }
}
google.maps.event.addDomListener(window, 'load', initialize);
// from epoly_v3.js
// modified to use geometry library for length of line segments
// === 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 {
  height: 100%
}

body {
  height: 100%;
  margin: 0px;
  padding: 0px
}
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=geometry"></script>
<div id="tools">
  start:
  <input type="text" name="start" id="start" value="Hyderabad" /> end:
  <input type="text" name="end" id="end" value="Bangalore" />
  <input type="submit" onclick="calcRoute();" /><br /> percentage:
  <input type="text" name="percent" id="percent" value="0" />
  <input type="submit" onclick="putMarkerOnRoute(parseFloat(document.getElementById('percent').value));" /> &nbsp;total time:<input type="text" name="totalTime" id="totalTime" value="0" />
</div>
<div id="map_canvas" style="float:left;width:70%;height:100%;"></div>
<div id="control_panel" style="float:right;width:30%;text-align:left;padding-top:20px">
  <div id="directions_panel" style="margin:20px;background-color:#FFEE77;"></div>
  <div id="total"></div>
</div>
geocodezip
  • 158,664
  • 13
  • 220
  • 245