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

时间:2010-04-23 11:29:07

标签: javascript google-maps distance intervals polyline

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

5 个答案:

答案 0 :(得分:35)

几个月前有answered a similar problem关于如何在SQL Server 2008的服务器端处理这个问题,我使用Google Maps API v2将相同的算法移植到JavaScript。

为了这个例子,让我们使用一个简单的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实现Chris Veness在Calculate distance, bearing and more between Latitude/Longitude points

特别是我已经从上面的源代码中采用了以下两种方法来使用Google的GLatLng类:

这些用于使用方法GLatLng扩展Google的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()方法应用于当前点,即可传递下一个点和行程距离。返回结果并打破迭代。

    否则:

    ...目标点位于迭代中下一个点的路径中。我们需要从沿着路径行进的总距离中减去该点与下一个点之间的距离。继续修改距离的迭代。

  4. 您可能已经注意到我们可以轻松地递归地实现上述内容,而不是迭代地实现上述内容。让我们这样做:

    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()函数并且距离为2,500米,它将返回{{ 1}}在距离第一点2.5公里的那条路上。

    GLatLng

    因此,我们需要做的就是为路径上需要的每个检查点调用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. 。如果你需要3公里,5公里和10公里的三个标记,你可以简单地做:

    moveAlongPath()

    但请注意,如果我们从路径的总长度开始请求检查点,则map.addOverlay(new GMarker(moveAlongPath(points, 1000))); map.addOverlay(new GMarker(moveAlongPath(points, 5000))); map.addOverlay(new GMarker(moveAlongPath(points, 10000))); 可能会返回moveAlongPath(),因此在将返回值传递给{{{{}}之前检查返回值会更明智。 1}}。

    我们可以将它们放在一起以实现全面实施。在这个例子中,我们沿着前面定义的8.8km路径每1000米放下一个标记:

    null

    上面示例的屏幕截图,每1000米显示一个标记:

    Google Maps - Move Point Along a Path

答案 1 :(得分:4)

  

我发现了为什么我有这个   不精确。实际上在GMap的V3中,   我们没有“getLength”功能   再以Km为单位返回长度   或多边形的米。

这是所需功能的原型 - 希望这有助于进一步:

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

我使用Martin Zeitler方法处理Google Map V3及其工作正常。

(int)POStatusOptions.Ordered

答案 4 :(得分:0)

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

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

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

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