如何找到沿路线给定距离的点? epoly.js 给我的结果非常不准确且无法使用

How do I find a point at a given distance along a route? epoly.js is giving me extremely inaccurate and unusable results

我正在尝试沿着 Google 地图 API 路线找到距离起点一定距离的点。我的代码可以正常工作,大多数时候它都能给我非常准确的结果。但是,当我发出很长的路线请求(例如 1,000 多公里)时,结果不太准确,而且路线越长,结果就越不准确。一旦我到达大约 3,000 公里,结果就会偏离大约 4,000 米,这对我的应用程序来说是完全不能接受的。

我用来计算点的函数来自epoly.js,代码如下:

google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {
    if (metres == 0) return this.getPath().getAt(0);
    if (metres < 0) return null;
    if (this.getPath().getLength() < 2) return null;
    var dist=0;
    var olddist=0;
    for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
        olddist = dist;
        dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
    }
    if (dist < metres) {
        return null;
    }
    var p1= this.getPath().getAt(i-2);
    var p2= this.getPath().getAt(i-1);
    var m = (metres-olddist)/(dist-olddist);
    return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
}

这个epoly.js函数有这个依赖函数:

google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
    var EarthRadiusMeters = 6378137.0;
    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 = EarthRadiusMeters * c;
    return d;
}

是什么导致结果在远距离上如此不准确?我该怎么做才能使结果更准确? (我需要精确到大约 1-3 米)Google 地图 API 有办法做到这一点还是 epoly.js 我最好的选择?如果是这样,那么我可以对上述代码进行哪些更改以使其给出更准确的结果?

我一直在寻找这个问题的答案,但我能找到的所有内容要么推荐 epoly.js,要么显示执行与 epoly.js 完全相同计算的代码片段。似乎 Google 没有任何内置的方法来执行此操作,但是我在某些应用程序中看到了类似的操作,例如 https://routeview.org,您可以清楚地看到橙色人在追踪即使一次导航数千公里,路线也很完美,所以我不得不相信更高水平的准确度是可能的。

这里有两个屏幕截图说明了短途请求和长途请求。请注意,它在短距离内非常准确,但在较长距离内变得非常不准确。另请注意,第二张图片中的标记是从远处查看的。它可能看起来离小路很近,但实际上它在一座大山的另一边大约有 5000 米远。 (第一张图片中的标记是从非常近距离观察的,即使近距离观察也不会明显偏离路径)

此图片适用于 20 公里的路线:

此图片适用于 3326 公里的路线:

这是演示我的问题的代码。放大标记可以看到它以远射错过了路线。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Example</title>
    </head>
    <body>
        <div id="map" style="width: 600px;height: 600px;"></div>

        <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY_HERE&v=weekly&channel=2"></script>

        <script>
            google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
                var EarthRadiusMeters = 6378137.0;
                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 = EarthRadiusMeters * c;
                return d;
            }

            google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
                if (metres == 0) return this.getPath().getAt(0);
                if (metres < 0) return null;
                if (this.getPath().getLength() < 2) return null;
                var dist=0;
                var olddist=0;
                for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
                    olddist = dist;
                    dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
                }
                if (dist < metres) {
                    return null;
                }
                var p1= this.getPath().getAt(i-2);
                var p2= this.getPath().getAt(i-1);
                var m = (metres-olddist)/(dist-olddist);
                return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
            }
        </script>

        <script>
            const map = new google.maps.Map(document.getElementById("map"),{
                center: { lat: 37, lng: -100 },
                zoom: 4,
                clickableIcons: false
            });

            const directionsRenderer = new google.maps.DirectionsRenderer();

            directionsRenderer.setMap(map);

            const directions = new google.maps.DirectionsService();

            directions.route({
                origin: "seattle, wa",
                destination: "chicago, il",
                travelMode: "DRIVING"
            },(data) => {
                const polyline = new google.maps.Polyline({
                    path: data.routes[0].overview_path
                });

                new google.maps.Marker({
                    map: map,
                    position: polyline.GetPointAtDistance(400000)
                });
                directionsRenderer.setDirections(data);
            });
        </script>
        
    </body>
</html>

(您需要提供自己的 API 密钥)

编辑:

我发现问题出在 Google 端。 Google 给我一条“足够接近”的折线。 Epoly.js 正在完美地追踪那条折线,但那条折线根本不与路线本身对齐。此图演示了问题:

深蓝色线是Google的“足够接近”的折线,浅蓝色线是实际路线所在的位置。

只是想我会把它留在这里供将来对此感到困惑的其他人使用。

不要对长路径使用 overview_path,它对近距离缩放级别不准确。使用连接的步骤多段线:

var legs = response.routes[0].legs;
for (i=0;i<legs.length;i++) {
  var steps = legs[i].steps;
  for (j=0;j<steps.length;j++) {
    var nextSegment = steps[j].path;
    for (k=0;k<nextSegment.length;k++) {
      polyline.getPath().push(nextSegment[k]);
    }
  }
} 

live example

你的原码(使用overview_path):

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Example</title>
    </head>
    <body>
        <div id="map" style="width: 600px;height: 600px;"></div>

        <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&v=weekly&channel=2"></script>

        <script>
            google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
                var EarthRadiusMeters = 6378137.0;
                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 = EarthRadiusMeters * c;
                return d;
            }

            google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
                if (metres == 0) return this.getPath().getAt(0);
                if (metres < 0) return null;
                if (this.getPath().getLength() < 2) return null;
                var dist=0;
                var olddist=0;
                for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
                    olddist = dist;
                    dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
                }
                if (dist < metres) {
                    return null;
                }
                var p1= this.getPath().getAt(i-2);
                var p2= this.getPath().getAt(i-1);
                var m = (metres-olddist)/(dist-olddist);
                return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
            }
        </script>

        <script>
            const map = new google.maps.Map(document.getElementById("map"),{
                center: { lat: 37, lng: -100 },
                zoom: 14,
                clickableIcons: false
            });

            const directionsRenderer = new google.maps.DirectionsRenderer({preserveViewport: true});

            directionsRenderer.setMap(map);

            const directions = new google.maps.DirectionsService();

            directions.route({
                origin: "seattle, wa",
                destination: "chicago, il",
                travelMode: "DRIVING"
            },(data) => {
                const polyline = new google.maps.Polyline({
                    path: data.routes[0].overview_path
                });

                var marker = new google.maps.Marker({
                    map: map,
                    position: polyline.GetPointAtDistance(400000)
                });
                map.setCenter(marker.getPosition());
                directionsRenderer.setDirections(data);
            });
        </script>
        
    </body>
</html>

使用详细路径更新了代码片段:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Example</title>
    </head>
    <body>
        <div id="map" style="width: 600px;height: 600px;"></div>

        <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&v=weekly&channel=2"></script>

        <script>
            google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
                var EarthRadiusMeters = 6378137.0;
                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 = EarthRadiusMeters * c;
                return d;
            }

            google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {// Stolen from http://www.geocodezip.com/scripts/v3_epoly.js
                if (metres == 0) return this.getPath().getAt(0);
                if (metres < 0) return null;
                if (this.getPath().getLength() < 2) return null;
                var dist=0;
                var olddist=0;
                for (var i=1; (i < this.getPath().getLength() && dist < metres); i++) {
                    olddist = dist;
                    dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1));
                }
                if (dist < metres) {
                    return null;
                }
                var p1= this.getPath().getAt(i-2);
                var p2= this.getPath().getAt(i-1);
                var m = (metres-olddist)/(dist-olddist);
                return new google.maps.LatLng( p1.lat() + (p2.lat()-p1.lat())*m, p1.lng() + (p2.lng()-p1.lng())*m);
            }
        </script>

        <script>
            const map = new google.maps.Map(document.getElementById("map"),{
                center: { lat: 37, lng: -100 },
                zoom: 14,
                clickableIcons: false
            });

            const directionsRenderer = new google.maps.DirectionsRenderer({preserveViewport: true});

            directionsRenderer.setMap(map);

            const directions = new google.maps.DirectionsService();

            directions.route({
                origin: "seattle, wa",
                destination: "chicago, il",
                travelMode: "DRIVING"
            },(data) => {
                const polyline = new google.maps.Polyline();

                var legs = data.routes[0].legs;
                for (i=0;i<legs.length;i++) {
                  var steps = legs[i].steps;
                  for (j=0;j<steps.length;j++) {
                    var nextSegment = steps[j].path;
                    for (k=0;k<nextSegment.length;k++) {
                      polyline.getPath().push(nextSegment[k]);
                    }
                  }
                } 
                var marker = new google.maps.Marker({
                    map: map,
                    position: polyline.GetPointAtDistance(400000)
                });
                map.setCenter(marker.getPosition());
                directionsRenderer.setDirections(data);
            });
        </script>
        
    </body>
</html>

使用更详细路径的结果(不是 overview_path):