Google Maps API V3 - 边界内最近的路线

Posted

技术标签:

【中文标题】Google Maps API V3 - 边界内最近的路线【英文标题】:Google Maps API V3 - Closest places to route within boundary 【发布时间】:2013-06-29 20:05:38 【问题描述】:

我正在搜索位于路线边界内的地点。我想按与路线的距离顺序返回结果。

我在附近的搜索请求中尝试了rankby=distance,但它不起作用,因为它需要位置和半径,而我有一个 LatLngBounds 对象和一条路线。这甚至可能吗?

这是我的脚本沿边界内的路线返回地点。

http://jsfiddle.net/FMpBU/

它返回结果,但以随机顺序...

注意:目标是使用边界而不是位置。

【问题讨论】:

我认为您需要提出多个请求。将路线划分为段并为每个段发出请求。计算您与他们的距离并按此进行排序很容易。 【参考方案1】:

在我们考虑按与路线的距离对结果进行排序之前,我们必须有一些基本的函数来计算距离。我们马上就遇到了障碍,因为地球当然是一个球体,球体上的距离比平面上的距离更复杂。

但是,在相对较小的距离(例如,Tamiami 到 Miami)上,我们可以将距离视为在飞机上,并获得合理的结果。如果我们可以接受这种近似,我们只需要一种方法来确定点和线段之间的最小距离。我没有重新发明***,而是改编了this SO answer的一些代码:

    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(距离平方)函数为我们节省一点额外的计算量。

Google 路线查询的结果包含一个名为 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 );

最后,为了额外的功劳,我使用了半正弦公式(改编自 this SO answer)

    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 仅使用每个线段中的第一个点来计算最近距离。 感谢您提供出色且有据可查的解决方案!你有惊人的技能朋友。效果很好。 谢谢,Cyber​​Junkie!这样做很有趣,而且我对我的下一个项目有了一些想法,所以这听起来像是一场胜利。【参考方案2】:

如果您使用Place Search Request 方法,您可以使用bounds 参数而不是位置和半径。

否则,您可以使用 Google Maps API 方法,将框的中心作为圆的中心,将框的对角线的一半作为圆的半径。这将为您提供包围框的圆圈内的所有位置。然后,您可以扫描整个集合,删除圆圈内但框外的所有位置。

在方法调用中添加 rankby=distance 将按与指定位置(例如框的中心)的距离顺序对结果进行排序。如果您需要按与路线的距离排序,则需要计算与路线的距离。最简单的方法是使用矢量投影计算路线中每个线段的垂线。如果投影点不在线段的一端,则距离是到线段的端点。沿矢量的距离符号将告诉您要测试哪一端。到最近线段的距离就是到路线的最短距离。

Google Maps API V3 中最接近的方法是:DistanceMatrixService,它查找多个源位置和目标位置之间的距离。还有一些其他方法可能有助于部分计算,但我看不到任何找到路线/折线和地点/点之间距离的方法。

【讨论】:

【参考方案3】:

这是一个想法。您必须获取每个地点距路线起点的每个距离并将该值添加为列表的属性,然后根据该属性的值对列表进行排序。

这是一个如何根据属性值对列表进行排序的示例。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);
);

示例: http://jsfiddle.net/995dY/

【讨论】:

以上是关于Google Maps API V3 - 边界内最近的路线的主要内容,如果未能解决你的问题,请参考以下文章

使用 Google Maps API V3,确定标记是不是在 KML 图层边界内

Google Maps API v3:未删除标记

Google Maps Api v3 - getBounds 未定义

Google Maps API V3:限制地图范围[重复]

Google Maps API v3:我可以在 fitBounds 之后设置缩放吗?

将 Google Maps API v3 搜索结果限制在某个国家/地区,同时将它们偏向某个位置