如何根据沿线的距离在谷歌地图折线上添加标记?

Posted

技术标签:

【中文标题】如何根据沿线的距离在谷歌地图折线上添加标记?【英文标题】:How to add markers on Google Maps polylines based on distance along the line? 【发布时间】:2011-02-11 11:46:19 【问题描述】:

我正在尝试创建一个谷歌地图,用户可以在其中绘制他步行/跑步/骑自行车的路线,并查看他跑了多长时间。 GPolyline 类及其 getLength() 方法在这方面非常有用(至少对于 Google Maps API V2),但我想根据距离添加标记,例如 1 公里、5 公里、10 公里的标记等,但似乎没有明显的方法可以根据沿线的距离在多段线上找到一个点。有什么建议吗?

【问题讨论】:

【参考方案1】:

几个月前,answered a similar problem 讨论了如何在 SQL Server 2008 的服务器端解决这个问题,我正在使用 Google Maps API v2 将相同的算法移植到 javascript

为了这个例子,让我们使用一条简单的 4 点折线,总长度约为 8,800 米。下面的 sn-p 将定义这条折线并将其渲染到地图上:

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 实现克里斯维内斯Calculate distance, bearing and more between Latitude/Longitude points。

特别是我已经从上述来源改编了以下两种方法来使用 Google 的 GLatLng 类:

Destination point given distance and bearing from start point Bearing

这些用于扩展 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点数组,并且我们用这个点数组和2500米的距离调用我们的moveAlongPath()函数,它将在该路径上返回一个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()。如果您需要 1km、5km 和 10km 的三个标记,您可以简单地这样做:

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() 之前检查返回值会更明智。

我们可以将这些放在一起进行全面实施。在此示例中,我们沿着前面定义的 8.8 公里路径每 1,000 米放置一个标记:

<!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>

以上示例的屏幕截图,每 1,000 米显示一个标记:

【讨论】:

我使用的是Google Map Api V3,你的公式看起来不错,但是当我放大到道路水平时,我可以看到google绘制的线和我的标记之间的距离。有什么理由会这样吗? @Nordes:上面的例子会发生这种情况吗?我试图放大到最大缩放级别,并且标记似乎在线。截图:img408.imageshack.us/img408/8687/gmapnospace.png 我会尝试使用您的所有代码。实际上,我只使用您在 JS 中制作的“haversine”公式。也许我在某个地方做错了。尝试使用您的代码后,我会尽快回复您。 我发现了我为什么不准确。实际上在 GMap 的 V3 中,我们不再有函数“getLength”来返回折线的公里或米的长度。此外,如果我们保持小长度的线,这似乎是正确的,但是当我们做一条大线(对角线 200 公里)时,我们可以看到线和标记之间有一些空间。这是因为Haversine 公式。该公式使用地球半径(6731 公里)的“近似值”。 @Nordes:哦,是的,就是这样。我认为getLength() 函数也假定为球形地球,因此在距离较大的 v2 演示中也应该发生同样的情况。假设一个球形地球会使数学变得更简单。【参考方案2】:

这些是所需功能的原型:

google.maps.Polygon.prototype.Distance = function() 
   var dist = 0;
   for (var i=1; i < this.getPath().getLength(); i++) 
      dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
   
   return dist;


google.maps.LatLng.prototype.distanceFrom = function(newLatLng) 
    //var R = 6371; // km (change this constant to get miles)
    var R = 6378100; // 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 = R * c;
    return d;

source

【讨论】:

【参考方案3】:

可能最好的方法是计算这些点的位置。

作为一种基本算法,您可以遍历折线中的所有点,并计算累积距离 - 如果下一段让您超过您的距离,您可以插入已达到距离的点 - 然后只需添加一个您的地图的兴趣点。

【讨论】:

是的,这应该是可行的——我只是希望有某种偷偷摸摸的方法可以让 API 做到这一点:) @mikl 这么说我可能是个受虐狂,但我认为在没有明显 API 方法的情况下制定这样的解决方案会更有趣【参考方案4】:

我已经使用 Martin Zeitler 方法与 Google Map V3 一起工作,并且工作正常。

 function init() 
       var mapOptions = 
            zoom: 15,
            center: new google.maps.LatLng(-6.208437004433984, 106.84543132781982),
            suppressInfoWindows: true,
                     ;

        // Get all html elements for map
        var mapElement = document.getElementById('map1');

        // Create the Google Map using elements
        map = new google.maps.Map(mapElement, mapOptions);

        var nextMarkerAt = 0;     // Counter for the marker checkpoints.
        var nextPoint = null;     // The point where to place the next marker.


        while (true) 

            var routePoints = [ new google.maps.LatLng(47.656, -122.360),
                                new google.maps.LatLng(47.656, -122.343),
                                new google.maps.LatLng(47.690, -122.310),
                                new google.maps.LatLng(47.690, -122.270)];

                nextPoint = moveAlongPath(routePoints, nextMarkerAt);

            if (nextPoint) 
              //Adding marker from localhost
                MarkerIcon = "http://192.168.1.1/star.png";
                var marker = new google.maps.Marker
                    (position: nextPoint,
                        map: map,
                        icon: MarkerIcon
                    );
                // Add +1000 meters for the next checkpoint.
                nextMarkerAt +=1000;

            
            else 
                // moveAlongPath returned null, so there are no more check points.
                break;
            
        
 


   Number.prototype.toRad = function () 
        return this * Math.PI / 180;
    

    Number.prototype.toDeg = function () 
        return this * 180 / Math.PI;
    

    function moveAlongPath(point, distance, index) 
        index = index || 0;  // Set index to 0 by default.

        var routePoints = [];

        for (var i = 0; i < point.length; i++) 
            routePoints.push(point[i]);
        

        if (index < routePoints.length) 
            // There is still at least one point further from this point.

            // Construct a GPolyline to use the getLength() method.
            var polyline = new google.maps.Polyline(
                path: [routePoints[index], routePoints[index + 1]],
                strokeColor: '#FF0000',
                strokeOpacity: 0.8,
                strokeWeight: 2,
                fillColor: '#FF0000',
                fillOpacity: 0.35
            );

            // Get the distance from this point to the next point in the polyline.
            var distanceToNextPoint = polyline.Distance();

            if (distance <= distanceToNextPoint) 
                // distanceToNextPoint is within this point and the next.
                // Return the destination point with moveTowards().
                return moveTowards(routePoints, distance,index);
            
            else 
                // The destination is further from the next point. Subtract
                // distanceToNextPoint from distance and continue recursively.
                return moveAlongPath(routePoints,
                    distance - distanceToNextPoint,
                    index + 1);
            
        
        else 
            // There are no further points. The distance exceeds the length
            // of the full path. Return null.
            return null;
        
    

    function moveTowards(point, distance,index) 

        var lat1 = point[index].lat.toRad();
        var lon1 = point[index].lng.toRad();
        var lat2 = point[index+1].lat.toRad();
        var lon2 = point[index+1].lng.toRad();
        var dLon = (point[index + 1].lng - point[index].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 google.maps.LatLng(lat2.toDeg(), lon2.toDeg());
    

    google.maps.Polyline.prototype.Distance = function () 
        var dist = 0;
        for (var i = 1; i < this.getPath().getLength(); i++) 
            dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1));
        
        return dist;
    

    google.maps.LatLng.prototype.distanceFrom = function (newLatLng) 
        //var R = 6371; // km (change this constant to get miles)
        var R = 6378100; // 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 = R * c;
        return d;
    

【讨论】:

大家好,知道我们如何在 react-google-map 包装库上做到这一点吗?【参考方案5】:

我想将 Daniel Vassalo's answer 移植到 ios,但它无法正常工作,并且在我更改之前有些标记放错了位置

var dLon = (point.lng() - this.lng()).toRad();

var dLon = point.lng().toRad() - this.lng().toRad();

因此,如果有人难以弄清楚为什么标记放错了位置,试试这个,也许它会有所帮助。

【讨论】:

以上是关于如何根据沿线的距离在谷歌地图折线上添加标记?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 rgw 距离在 android 中沿一条线在 Google Maps 折线上添加标记?

如何在谷歌地图的折线中点绘制标记?

使折线水平显示在谷歌地图上并将其缩放到中心坐标

如何在谷歌地图中获取标记和中心之间的距离

我们如何在谷歌地图中为多边形应用可编辑标记

用户如何在谷歌地图颤振上添加多个标记