如何根据沿线的距离在谷歌地图折线上添加标记?
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
类:
这些用于扩展 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();
因此,如果有人难以弄清楚为什么标记放错了位置,试试这个,也许它会有所帮助。
【讨论】:
以上是关于如何根据沿线的距离在谷歌地图折线上添加标记?的主要内容,如果未能解决你的问题,请参考以下文章