谷歌地图API V3 - 边界内距路线最近的地点

3
我正在寻找位于路线范围内的地点。我想按照距离路线的远近顺序返回结果。
在我的附近搜索请求中,我尝试使用rankby=distance,但它不起作用,因为它需要一个位置和半径,而我有一个LatLngBounds对象和一条路线。这可能吗?
这是我的脚本,返回路线范围内的地点。 http://jsfiddle.net/FMpBU/ 它会返回结果,但是顺序是随机的...
注意:目标是使用边界而不是位置。

我认为你需要进行多次请求。将路线分成若干段,并对每个段进行请求。计算你与它们之间的距离并按照距离排序很容易实现。 - transilvlad
3个回答

7

在我们考虑按距离从路径排序之前,我们必须有一些基本的函数来计算距离。这里我们立即遇到了问题,因为地球当然是一个球体,球面上的距离比平面上的距离更加复杂。

但是,在相对较短的距离(例如从Tamiami到Miami),我们可以将距离视为在平面上,并获得合理的结果。如果我们能接受这个近似值,我们只需要一种确定点与线段之间最小距离的方法。为了避免重复造轮子,我从这个SO答案中适用了一些代码:

    function sqr(x) { return x * x }
    function dist2(v, w) { return sqr(v.x - w.x) + sqr(v.y - w.y) }
    function distToSegment2(p, v, w) {
      return dist2(getClosestPoint(p,v,w));
    }
    function getClosestPoint( p, v, w ) {
      var l2 = dist2(v, w);
      if (l2 === 0) return v;   // line is actually a point; just return one ofthe two points
      var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
      // point is closest to v, return v
      if (t < 0) return v;
      // point is closest to w, return w
      if (t > 1) return w;
      // point is closets to some midpoint, return that
      return { x: v.x + t * (w.x - v.x), y: v.y + t * (w.y - v.y) };
    }
    function distToSegment(p, v, w) { return Math.sqrt(distToSegmentSquared(p, v, w)); }

现在我们目前仅使用距离进行排序,因此不必进行额外的平方根计算步骤,可以直接使用dist2(距离平方)函数来节省一些计算。从谷歌路线查询结果中包含一个名为overview_path的数组,其中包含用于在地图上绘制路径的所有线段。我们将使用这些线段来确定最接近的点:
    function closestPointOnPath_Cartesian( place, path, cb ) {
        var min = Number.MAX_VALUE;
        var closestPoint = null;
        for( var i=0; i<path.length-1; i++ ) {
            var v = { x: path[i].lng(), y: path[i].lat() };
            var w = { x: path[i+1].lng(), y: path[i+1].lat() };
            var p1 = { x: place.geometry.location.lng(), 
                        y: place.geometry.location.lat() };
            var p2 = getClosestPoint( p1, v, w );
            var d2 = dist2( p1, p2 );
            if( d2 < min ) {
                min = d2;
                closestPoint = new google.maps.LatLng( p2.y, p2.x );
            }
        }
        cb( closestPoint, min );
    }

请注意,我小心地给这个函数命名,以表明它计算的是笛卡尔距离,这可能会在长路线或靠近极点的路线上出现问题。
现在我们有了这个函数,我们只需要用距离注释每个结果,以便我们可以对其进行排序,然后实际执行排序。
for( var i=0; i<results.length; i++ ) {
        closestPointOnPath_Cartesian( results[i], 
            result.routes[0].overview_path, 
            function( closestPoint, coordDist2 ){
                results[i].closestPointOnPath = closestPoint;
                results[i].coordDist2 = coordDist2;
                results[i].geoDistKm = geoDistanceKm( results[i].geometry.location, closestPoint );
        });
    }
    // sort results by relative distance (coordDist2)
    results.sort( function(a,b) { return a.coordDist2 - b.coordDist2; } );

为了调试可视化目的,我添加了一些代码来绘制从路径上最接近的点到每个位置的线条:
       var distLine = new google.maps.Polyline({
               path: [place.closestPointOnPath, place.geometry.location],
               strokeColor: '#ff0000',
               strokeOpacity: 1.0,
               strokeWeight: 2
           });
           distLine.setMap( map );

最后,为了额外加分,我使用了haversine公式(改编自这个SO答案

    Number.prototype.toRad = function() {
       return this * Math.PI / 180;
    }
    // geographic distance courtesy the haversine formula
    function geoDistanceKm(p1,p2) {
        var R = 6371; // km 
        var x1 = p2.lat()-p1.lat();
        var dLat = x1.toRad();  
        var x2 = p2.lng()-p1.lng();
        var dLon = x2.toRad();  
        var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
            Math.cos(p1.lat().toRad()) * Math.cos(p2.lat().toRad()) * 
            Math.sin(dLon/2) * Math.sin(dLon/2);  
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
        return R * c;
    }

现在我们可以在结果列表中显示真正的地理距离。

注意:由于我们在平面上计算距离,对于长途路线或靠近极点的路线,可能会出现顺序与哈弗辛公式确定的地理距离(正确)顺序不匹配的情况。要解决这个问题需要推导一个算法来确定球体上的点/段距离,这是另一天的任务。

您可以在此处查看完整的解决方案:http://jsfiddle.net/UqLTE/4/


我刚刚阅读了@Enzino在下面的解决方案,并学到了有一个函数google.maps.geometry.spherical.computeDistanceBetween可以代替我上面的geoDistanceKm函数。请看一下我的评论,关于他的方法存在的弱点。有趣的是分析一下我们两个解决方案中哪一个会引起更多问题:我的平面计算作为球面几何的近似,还是Enzino的方法只使用每个线段中的第一个点来计算最近距离。 - Ethan Brown
感谢您提供的出色且文档完备的解决方案!您是一位技能非凡的朋友。它完美地运行。 - CyberJunkie
1
谢谢,CyberJunkie!这次做得很开心,我也为下一个工作项目得到了一些灵感,所以看起来是个全面的胜利。 - Ethan Brown

1

如果您使用的是Place Search Request方法,可以使用bounds参数代替location和radius。

否则,您可以使用Google Maps API方法,以框的中心作为圆的中心,以框对角线的一半作为圆的半径。这将给您所有包含在框内的圆内的地点。然后,您可以扫描该集合并删除任何在圆内但在框外的地点。

将rankby=distance添加到方法调用中会按距离从指定位置(例如框的中心)排序结果。如果您需要按距离从路线排序,则需要计算距离。最简单的方法是使用向量投影计算每个线段到垂线。如果投影点在线段的一端之外,则距离为该线段末端的端点。沿着向量的距离符号将告诉您要测试哪个端点。最靠近线段的距离是到路线的最短距离。

Google Maps API V3中最接近的方法是:DistanceMatrixService,它可以找到多个源和目的地之间的距离。还有一些其他的方法可能会在计算的某些部分上有所帮助,但我没有看到任何可以找到路径/折线和地点/点之间距离的方法。

0
这是一个想法。你需要获取每个地点与路线起点的距离,并将该值作为列表的属性添加,然后根据属性值对列表进行排序。
以下是如何根据属性值对列表进行排序的示例。

HTML
<ul id="place-list">
<li data-id="4">Tokyo 4</li>
<li data-id="0">Paris 0</li>
<li data-id="5">Frankfurt 5</li>
<li data-id="2">London 2</li>
<li data-id="1">greece 1</li>
<li data-id="3">Munich 3</li>
</ul>
<button id="asc">ASC</button>
<button id="desc">DESC</button>

jQuery

var sortArray = function (items, inverse) {
    var inverse = inverse || false;

    var sortedArray = items.map(function () {
        return {
            id: $(this).data("id"),
            element: $(this)[0].outerHTML
        };
    });

    var appendTo = items.parent();
    items.remove();

    sortedArray.sort(function (a, b) {
        return a.id > b.id ? (inverse ? -1 : 1) : (inverse ? 1 : -1);
    });

    sortedArray.each(function () {
        $(appendTo).append(this.element);
    });
}

$("#asc").click(function () {
    sortArray($("#place-list").find("li"));
});

$("#desc").click(function () {
    sortArray($("#place-list").find("li"), true);
});

EXAMPLE: http://jsfiddle.net/995dY/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接