Как добавить маркеры на ломаных линиях Google Maps на основе расстояния вдоль строки?

Я пытаюсь создать Google Map, где пользователь может вывести маршрут на печать, он шел/выполнял/ездил на велосипеде, и посмотрите, сколько времени он работал. GPolyline класс с он getLength() метод очень полезен в этом отношении (по крайней мере, для Google Maps API V2), но я хотел добавить маркеры на основе расстояния, например, маркер для 1 км, 5 км, 10 км, и т.д., но кажется, что нет никакого очевидного способа найти точку на ломаной линии на основе того, как далеко вдоль строки это. Какие-либо предложения?

14
задан mikl 23 April 2010 в 11:29
поделиться

2 ответа

Имея ответил на аналогичную проблему пару месяцев назад о том, как решить эту проблему на стороне сервера в 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());
}

Имея этот метод, мы можем теперь решить проблему следующим образом:

  1. Итерировать по каждой точке пути.
  2. Найдите расстояние между текущей точкой в ​​итерации и следующей точкой.
  3. Если расстояние в точке 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 метров:

Google Maps - Move Point Along a Path

35
ответ дан 1 December 2019 в 06:39
поделиться

Возможно, лучшим подходом было бы вычислить, где находятся эти точки.

В качестве основного алгоритма вы можете перебрать все точки ломаной линии и вычислить совокупное расстояние - если следующий сегмент приведет вас к вашему расстоянию, вы можете интерполировать точку, в которой расстояние было достигнуто, а затем просто добавить точку интереса на вашей карте для этого.

3
ответ дан 1 December 2019 в 06:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: