Javascript 如何根据沿线的距离在谷歌地图折线上添加标记?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2698112/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to add markers on Google Maps polylines based on distance along the line?
提问by mikl
I am trying to create a Google Map where the user can plot the route he walked/ran/bicycled and see how long he ran. The GPolylineclass with it's getLength()method is very helpful in this regard (at least for Google Maps API V2), but I wanted to add markers based on distance, for example a marker for 1 km, 5 km, 10 km, etc., but it seems that there is no obvious way to find a point on a polyline based on how far along the line it is. Any suggestions?
我正在尝试创建一个谷歌地图,用户可以在其中绘制他步行/跑步/骑自行车的路线并查看他跑了多长时间。GPolyline带有它的getLength()方法的类在这方面非常有帮助(至少对于 Google Maps API V2),但我想根据距离添加标记,例如 1 公里、5 公里、10 公里等的标记,但它似乎没有明显的方法可以根据折线上的距离在折线上找到一个点。有什么建议?
回答by Daniel Vassallo
Having answered a similar problema couple of months ago on how to tackle this on the server-side in SQL Server 2008, I am porting the same algorithm to JavaScript using the Google Maps API v2.
已经回答了类似的问题,如何在SQL Server 2008中解决这个在服务器端的几个月前,我移植使用相同的算法的JavaScript谷歌地图API第2版。
For the sake of this example, let's use a simple 4-point polyline, with a total length of circa 8,800 meters. The snippet below will define this polyline and will render it on the map:
在本示例中,让我们使用一个简单的 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);
Now before we approach the actual algorithm, we will need a function that returns the destination point when given a start point, an end point, and the distance to travel along that line, Luckily, there are a few handy JavaScript implementations by Chris Veness at Calculate distance, bearing and more between Latitude/Longitude points.
现在,在我们接近实际算法之前,我们需要一个函数,当给定起点、终点和沿该线行进的距离时,该函数返回目的地点,幸运的是,Chris Veness 提供了一些方便的 JavaScript 实现,位于计算纬度/经度点之间的距离、方位等。
In particular I have adapted the following two methods from the above source to work with Google's GLatLngclass:
特别是,我已经改编了上述来源中的以下两种方法以与 Google 的GLatLng课程一起使用:
These were used to extend Google's GLatLngclass with a method moveTowards(), which when given another point and a distance in meters, it will return another GLatLngalong that line when the distance is travelled from the original point towards the point passed as a parameter.
这些用于使用方法扩展谷歌的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());
}
Having this method, we can now tackle the problem as follows:
有了这个方法,我们现在可以解决这个问题如下:
- Iterate through each point of the path.
- Find the distance between the current point in the iteration to the next point.
If the distance in point 2 is greater the distance we need to travel on the path:
...then the destination point is between this point and the next. Simply apply the
moveTowards()method to the current point, passing the next point and the distance to travel. Return the result and break the iteration.Else:
...the destination point is further in the path from the next point in the iteration. We need to subtract the distance between this point and the next point from the total distance to travel along the path. Continue through the iteration with the modified distance.
- 遍历路径的每个点。
- 求迭代中的当前点到下一个点之间的距离。
如果点 2 中的距离大于我们需要在路径上行驶的距离:
...那么目的地点在这个点和下一个点之间。只需将该
moveTowards()方法应用于当前点,通过下一个点和要行驶的距离。返回结果并中断迭代。别的:
...目标点在迭代中的下一个点的路径中更远。我们需要从沿路径行进的总距离中减去该点和下一个点之间的距离。使用修改后的距离继续迭代。
You may have noticed that we can easily implement the above recursively, instead of iteratively. So let's do it:
您可能已经注意到,我们可以轻松地递归而不是迭代地实现上述内容。所以让我们这样做:
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;
}
}
With the above method, if we define an array of GLatLngpoints, and we invoke our moveAlongPath()function with this array of points and with a distance of 2,500 meters, it will return a GLatLngon that path at 2.5km from the first point.
使用上述方法,如果我们定义一个GLatLng点数组,并moveAlongPath()使用这个点数组调用我们的函数,距离为 2,500 米,它将返回GLatLng距离第一个点 2.5km 的路径上的a 。
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.
Therefore all we need to do is to call moveAlongPath()for each check point we need on the path. If you need three markers at 1km, 5km and 10km, you can simply do:
因此,我们需要做的就是调用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)));
Note however that moveAlongPath()may return nullif we request a check point further from the total length of the path, so it will be wiser to check for the return value before passing it to new GMarker().
但是请注意,如果我们请求距离路径总长度更远的检查点,则moveAlongPath()可能会返回null,因此在将返回值传递给 之前检查返回值会更明智new GMarker()。
We can put this together for the full implementation. In this example we are dropping a marker every 1,000 meters along the 8.8km path defined earlier:
我们可以将其放在一起以进行全面实施。在这个例子中,我们沿着前面定义的 8.8km 路径每 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>
Screenshot of the above example, showing a marker every 1,000 meters:
上面示例的屏幕截图,每 1,000 米显示一个标记:


回答by Martin Zeitler
I found out why I had the inexactitude. Actually in V3 of GMap, we don't have the function "getLength" anymore that return the length in Km or Meters of the polyLine.
我发现了为什么我有不精确。实际上在 GMap 的 V3 中,我们不再有函数“getLength”来返回折线的公里或米的长度。
here's the prototypes for the required function - hope this helps any further:
这是所需功能的原型 - 希望这有助于进一步:
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;
}
回答by Rowland Shaw
Possibly the best approach would be to calculate where these points are.
可能最好的方法是计算这些点的位置。
As a basic algorithm you could iterate over all the points in the Polyline, and calculate the cumulative distance - if the next segment puts you over your distance, you can interpolate the point where the distance has been reached - then simply add a point of interest to your map for that.
作为一种基本算法,您可以迭代折线中的所有点,并计算累积距离 - 如果下一段让您超过您的距离,您可以插入已达到距离的点 - 然后只需添加一个兴趣点到你的地图。
回答by Kishor N R
I have used Martin Zeitler method to work with Google Map V3 and its working fine.
我已经使用 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;
}
回答by Xarathor
I wanted to port Daniel Vassalo's answerto iOS, but it wasn't worked properly and some markers were misplaced until I changed
我想将Daniel Vassalo 的答案移植到 iOS,但它无法正常工作,并且在我更改之前有些标记放错了位置
var dLon = (point.lng() - this.lng()).toRad();
to
到
var dLon = point.lng().toRad() - this.lng().toRad();
So if anyone having a trouble to figure out why are the markers are misplaced, try this and maybe it will help.
因此,如果有人无法弄清楚为什么标记放错了地方,请尝试此操作,也许会有所帮助。

