Я пытаюсь создать Google Map, где пользователь может вывести маршрут на печать, он шел/выполнял/ездил на велосипеде, и посмотрите, сколько времени он работал. GPolyline
класс с он getLength()
метод очень полезен в этом отношении (по крайней мере, для Google Maps API V2), но я хотел добавить маркеры на основе расстояния, например, маркер для 1 км, 5 км, 10 км, и т.д., но кажется, что нет никакого очевидного способа найти точку на ломаной линии на основе того, как далеко вдоль строки это. Какие-либо предложения?
Имея ответил на аналогичную проблему пару месяцев назад о том, как решить эту проблему на стороне сервера в SQL Server 2008, я портирую тот же алгоритм на JavaScript, используя API Карт Google v2 .
Для этого примера давайте воспользуемся простой 4-точечной полилинией общей длиной около 8 800 метров. Приведенный ниже фрагмент определит эту ломаную линию и отобразит ее на карте:
var map = new GMap2(document.getElementById('map_canvas'));
var points = [
new GLatLng(47.656, -122.360),
new GLatLng(47.656, -122.343),
new GLatLng(47.690, -122.310),
new GLatLng(47.690, -122.270)
];
var polyline = new GPolyline(points, '#f00', 6);
map.setCenter(new GLatLng(47.676, -122.343), 12);
map.addOverlay(polyline);
Теперь, прежде чем мы подойдем к фактическому алгоритму, нам понадобится функция, которая возвращает точку назначения, когда задана начальная точка, конечная точка и расстояние до Путешествуйте по этой линии, к счастью, есть несколько удобных реализаций JavaScript Криса Венесса в Вычислить расстояние, азимут и многое другое между точками широты / долготы .
В частности, я адаптировал следующие два метода из вышеуказанного источника для работы с классом Google GLatLng
:
Это были используется для расширения класса Google GLatLng
с помощью метода moveTowards ()
, который при задании другой точки и расстояния в метрах возвращает еще один GLatLng
вдоль этой строки когда расстояние проходит от исходной точки до точки, переданной в качестве параметра.
GLatLng.prototype.moveTowards = function(point, distance) {
var lat1 = this.lat().toRad();
var lon1 = this.lng().toRad();
var lat2 = point.lat().toRad();
var lon2 = point.lng().toRad();
var dLon = (point.lng() - this.lng()).toRad();
// Find the bearing from this point to the next.
var brng = Math.atan2(Math.sin(dLon) * Math.cos(lat2),
Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) *
Math.cos(dLon));
var angDist = distance / 6371000; // Earth's radius.
// Calculate the destination point, given the source and bearing.
lat2 = Math.asin(Math.sin(lat1) * Math.cos(angDist) +
Math.cos(lat1) * Math.sin(angDist) *
Math.cos(brng));
lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(angDist) *
Math.cos(lat1),
Math.cos(angDist) - Math.sin(lat1) *
Math.sin(lat2));
if (isNaN(lat2) || isNaN(lon2)) return null;
return new GLatLng(lat2.toDeg(), lon2.toDeg());
}
Имея этот метод, мы можем теперь решить проблему следующим образом:
Если расстояние в точке 2 больше расстояния, которое нам нужно пройти по пути:
... тогда конечная точка находится между этой точкой и следующей. Просто примените метод moveTowards ()
к текущей точке, передав следующую точку и пройденное расстояние. Вернуть результат и прервать итерацию.
Иначе:
... конечная точка находится дальше на пути от следующей точки в итерации. Нам нужно вычесть расстояние между этой точкой и следующей точкой из общего расстояния, которое нужно пройти по пути. Продолжайте итерацию с измененным расстоянием.
Возможно, вы заметили, что мы можем легко реализовать описанное выше рекурсивно, а не итеративно.Итак, давайте сделаем это:
function moveAlongPath(points, distance, index) {
index = index || 0; // Set index to 0 by default.
if (index < points.length) {
// There is still at least one point further from this point.
// Construct a GPolyline to use its getLength() method.
var polyline = new GPolyline([points[index], points[index + 1]]);
// Get the distance from this point to the next point in the polyline.
var distanceToNextPoint = polyline.getLength();
if (distance <= distanceToNextPoint) {
// distanceToNextPoint is within this point and the next.
// Return the destination point with moveTowards().
return points[index].moveTowards(points[index + 1], distance);
}
else {
// The destination is further from the next point. Subtract
// distanceToNextPoint from distance and continue recursively.
return moveAlongPath(points,
distance - distanceToNextPoint,
index + 1);
}
}
else {
// There are no further points. The distance exceeds the length
// of the full path. Return null.
return null;
}
}
С помощью вышеуказанного метода, если мы определим массив из GLatLng
точек и вызовем нашу функцию moveAlongPath ()
с этим массивом точек и расстояние 2500 метров, он вернет GLatLng
на этом пути на расстоянии 2,5 км от первой точки.
var points = [
new GLatLng(47.656, -122.360),
new GLatLng(47.656, -122.343),
new GLatLng(47.690, -122.310),
new GLatLng(47.690, -122.270)
];
var destinationPointOnPath = moveAlongPath(points, 2500);
// destinationPointOnPath will be a GLatLng on the path
// at 2.5km from the start.
Поэтому все, что нам нужно сделать, это вызвать moveAlongPath ()
для каждой контрольной точки, которая нам нужна на пути. Если вам нужны три маркера на 1 км, 5 км и 10 км, вы можете просто сделать:
map.addOverlay(new GMarker(moveAlongPath(points, 1000)));
map.addOverlay(new GMarker(moveAlongPath(points, 5000)));
map.addOverlay(new GMarker(moveAlongPath(points, 10000)));
Обратите внимание, что moveAlongPath ()
может вернуть null
, если мы запросим контрольную точку дальше от общая длина пути, поэтому будет разумнее проверить возвращаемое значение, прежде чем передавать его в new GMarker ()
.
Мы можем собрать это вместе для полной реализации. В этом примере мы сбрасываем маркер каждые 1000 метров вдоль пути длиной 8,8 км, определенного ранее:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps - Moving point along a path</title>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false"
type="text/javascript"></script>
</head>
<body onunload="GUnload()">
<div id="map_canvas" style="width: 500px; height: 300px;"></div>
<script type="text/javascript">
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
GLatLng.prototype.moveTowards = function(point, distance) {
var lat1 = this.lat().toRad();
var lon1 = this.lng().toRad();
var lat2 = point.lat().toRad();
var lon2 = point.lng().toRad();
var dLon = (point.lng() - this.lng()).toRad();
// Find the bearing from this point to the next.
var brng = Math.atan2(Math.sin(dLon) * Math.cos(lat2),
Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) *
Math.cos(dLon));
var angDist = distance / 6371000; // Earth's radius.
// Calculate the destination point, given the source and bearing.
lat2 = Math.asin(Math.sin(lat1) * Math.cos(angDist) +
Math.cos(lat1) * Math.sin(angDist) *
Math.cos(brng));
lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(angDist) *
Math.cos(lat1),
Math.cos(angDist) - Math.sin(lat1) *
Math.sin(lat2));
if (isNaN(lat2) || isNaN(lon2)) return null;
return new GLatLng(lat2.toDeg(), lon2.toDeg());
}
function moveAlongPath(points, distance, index) {
index = index || 0; // Set index to 0 by default.
if (index < points.length) {
// There is still at least one point further from this point.
// Construct a GPolyline to use the getLength() method.
var polyline = new GPolyline([points[index], points[index + 1]]);
// Get the distance from this point to the next point in the polyline.
var distanceToNextPoint = polyline.getLength();
if (distance <= distanceToNextPoint) {
// distanceToNextPoint is within this point and the next.
// Return the destination point with moveTowards().
return points[index].moveTowards(points[index + 1], distance);
}
else {
// The destination is further from the next point. Subtract
// distanceToNextPoint from distance and continue recursively.
return moveAlongPath(points,
distance - distanceToNextPoint,
index + 1);
}
}
else {
// There are no further points. The distance exceeds the length
// of the full path. Return null.
return null;
}
}
var map = new GMap2(document.getElementById('map_canvas'));
var points = [
new GLatLng(47.656, -122.360),
new GLatLng(47.656, -122.343),
new GLatLng(47.690, -122.310),
new GLatLng(47.690, -122.270)
];
var polyline = new GPolyline(points, '#f00', 6);
var nextMarkerAt = 0; // Counter for the marker checkpoints.
var nextPoint = null; // The point where to place the next marker.
map.setCenter(new GLatLng(47.676, -122.343), 12);
// Draw the path on the map.
map.addOverlay(polyline);
// Draw the checkpoint markers every 1000 meters.
while (true) {
// Call moveAlongPath which will return the GLatLng with the next
// marker on the path.
nextPoint = moveAlongPath(points, nextMarkerAt);
if (nextPoint) {
// Draw the marker on the map.
map.addOverlay(new GMarker(nextPoint));
// Add +1000 meters for the next checkpoint.
nextMarkerAt += 1000;
}
else {
// moveAlongPath returned null, so there are no more check points.
break;
}
}
</script>
</body>
</html>
Снимок экрана приведенного выше примера, показывающий маркер каждые 1000 метров:
Возможно, лучшим подходом было бы вычислить, где находятся эти точки.
В качестве основного алгоритма вы можете перебрать все точки ломаной линии и вычислить совокупное расстояние - если следующий сегмент приведет вас к вашему расстоянию, вы можете интерполировать точку, в которой расстояние было достигнуто, а затем просто добавить точку интереса на вашей карте для этого.