My script shows a polyline between several markers. An additional marker (green circle) should visualize the distance already travelled.
The script works, but on high zoom-levels (12-15) the marker for the distance travelled do not "sit" on the polyline anymore, but many meters away. (see screengrab) The markers position is calculated with GetPointAtDistance from the Epolys.js-script.
See demo here: https://jsfiddle.net/faoq2jbr/1/
<div id="container">
<div id="map" style="width:100%; height: 400px;"></div>
</div>
<script>
// initialise map
function initMap() {
var options = {
center: {
lat: 51.69869842676892,
lng: 8.188009802432369
},
zoom: 14,
mapId: '1ab596deb8cb9da8',
mapTypeControl: false,
streetViewControl: false,
fullscreenControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
},
}
var map = new google.maps.Map(document.getElementById('map'), options);
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.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 += 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);
}
// Define a symbol using SVG path notation, with an opacity of 1.
const dashedLine = {
path: "M 0,-1 0,1",
strokeOpacity: 1,
scale: 8,
};
var markerCoordinates = [{
lat: 51.17230192226146,
lng: 7.005455256203302
},
{
lat: 52.017106436819546,
lng: 8.903316299753124
},
{
lat: 52.1521613855702,
lng: 9.972045956234473
},
{
lat: 52.12123086563482,
lng: 11.627830412053509
},
{
lat: 53.6301544474316,
lng: 11.415718027446243
},
{
lat: 54.08291262244958,
lng: 12.191652169789096
},
{
lat: 54.3141629859056,
lng: 13.097095856304708
}
]
// create markers
for (i = 0; i < markerCoordinates.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(markerCoordinates[i]['lat'], markerCoordinates[i]['lng']),
map: map,
optimized: true,
});
}
// create polylines
const stepsRoute = new google.maps.Polyline({
path: markerCoordinates,
geodesic: true,
strokeColor: "#c5d899",
strokeOpacity: 0.2,
icons: [{
icon: dashedLine,
offset: "0",
repeat: "35px",
}, ]
});
stepsRoute.setMap(map);
var polylineLength = google.maps.geometry.spherical.computeLength(stepsRoute.getPath());
var groupPosition = stepsRoute.GetPointAtDistance(100600);
// add marker at position of the group
var positionMarker = new google.maps.Marker({
map: map,
position: groupPosition,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10,
fillOpacity: 1,
strokeWeight: 2,
fillColor: '#5384ED',
strokeColor: '#ffffff',
},
});
};
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAvHtXFKU5QUOBQ_gpn2a0DM-3Yjx3H5jQ&callback=initMap&libraries=geometry">
´´´
Looks like the interpolation in GetPointAtDistance in the epoly code is not as accurate as (or at least consistent with) the code in the Google Maps JavaScript API v3 geometry library.
If I replace the existing interpolation:
return new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
with the google.maps.geometry.spherical.interpolate
method:
return google.maps.geometry.spherical.interpolate(p1, p2, m);
The marker ends up on the line (when the line is geodesic: true
).
code snippet:
// initialise map
function initMap() {
var options = {
center: {
lat: 51.69869842676892,
lng: 8.188009802432369
},
zoom: 14,
mapId: '1ab596deb8cb9da8',
mapTypeControl: false,
streetViewControl: false,
fullscreenControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
},
}
var map = new google.maps.Map(document.getElementById('map'), options);
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.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 += 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);
// updated to use the geometry library function
return google.maps.geometry.spherical.interpolate(p1, p2, m);
}
// Define a symbol using SVG path notation, with an opacity of 1.
const dashedLine = {
path: "M 0,-1 0,1",
strokeOpacity: 1,
scale: 8,
};
var markerCoordinates = [{
lat: 51.17230192226146,
lng: 7.005455256203302
},
{
lat: 52.017106436819546,
lng: 8.903316299753124
},
{
lat: 52.1521613855702,
lng: 9.972045956234473
},
{
lat: 52.12123086563482,
lng: 11.627830412053509
},
{
lat: 53.6301544474316,
lng: 11.415718027446243
},
{
lat: 54.08291262244958,
lng: 12.191652169789096
},
{
lat: 54.3141629859056,
lng: 13.097095856304708
}
]
// create markers
for (i = 0; i < markerCoordinates.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(markerCoordinates[i]['lat'], markerCoordinates[i]['lng']),
map: map,
optimized: true,
});
}
// create polylines
const stepsRoute = new google.maps.Polyline({
path: markerCoordinates,
geodesic: true,
strokeColor: "#c5d899",
strokeOpacity: 0.2,
icons: [{
icon: dashedLine,
offset: "0",
repeat: "35px",
}, ]
});
stepsRoute.setMap(map);
var polylineLength = google.maps.geometry.spherical.computeLength(stepsRoute.getPath());
var groupPosition = stepsRoute.GetPointAtDistance(100600);
// add marker at position of the group
var positionMarker = new google.maps.Marker({
map: map,
position: groupPosition,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10,
fillOpacity: 1,
strokeWeight: 2,
fillColor: '#5384ED',
strokeColor: '#ffffff',
},
});
var positionMarker = new google.maps.Marker({
map: map,
position: groupPosition,
});
};
/*
* Always set the map height explicitly to define the size of the div element
* that contains the map.
*/
#map {
height: 100%;
}
/*
* Optional: Makes the sample page fill the window.
*/
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Directions Service</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<!-- jsFiddle will insert css and js -->
</head>
<body>
<div id="map"></div>
<!--
The `defer` attribute causes the callback to execute after the full HTML
document has been parsed. For non-blocking uses, avoiding race conditions,
and consistent behavior across browsers, consider loading using Promises
with https://www.npmjs.com/package/@googlemaps/js-api-loader.
-->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=geometry" defer></script>
</body>
</html>