【问题标题】:Algorithm to find all Latitude Longitude locations within a certain distance from a given Lat Lng location查找距给定 Lat Lng 位置一定距离内的所有纬度经度位置的算法
【发布时间】:2011-06-29 05:04:35
【问题描述】:

给定一个包含纬度 + 经度位置的地点数据库,例如 40.8120390、-73.4889650,我如何找到特定位置给定距离内的所有位置?

从DB中选择所有位置,然后逐个遍历,获取与起始位置的距离,看看它们是否在指定距离内,似乎效率不高。有没有一种好方法可以缩小数据库中最初选择的位置?一旦我有(或没有?)一组缩小的位置,我是否仍要逐个检查距离,还是有更好的方法?

我这样做的语言并不重要。谢谢!

【问题讨论】:

  • 这可能是你需要的:en.wikipedia.org/wiki/K-d_tree
  • 一个 SQL 查询不能解决它吗? SELECT * FROM Places WHERE (Lat - :Lat)^2 + (Long - :Long)^2
  • @Ashu,nOiAd,不幸的是我不得不放弃那个项目,所以我最终没有选择解决方案。如果你们在项目中使用其中一种解决方案,我和其他人将非常感谢你们的 cmets 在这里。

标签: algorithm geolocation gps location latitude-longitude


【解决方案1】:

首先比较纬度之间的距离。每个纬度相距约 69 英里(111 公里)。范围从赤道的 68.703 英里(110.567 公里)到两极的 69.407(111.699 公里)不等(由于地球略呈椭圆形)。两个位置之间的距离将等于或大于它们的纬度之间的距离。

请注意,这不适用于经度 - 每个经度的长度取决于纬度。但是,如果您的数据仅限于某个区域(例如单个国家/地区) - 您也可以计算经度的最小和最大范围。


继续进行假设为球形地球的低精度、快速距离计算:

坐标为 {lat1,lon1} 和 {lat2,lon2} 的两点之间的大圆距离 d 由下式给出:

d = acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lon1-lon2))

一个数学上等价的公式是:

d = 2*asin(sqrt((sin((lat1-lat2)/2))^2 +
    cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2))

d 是以弧度表示的距离

distance_km ≈ radius_km * distance_radians ≈ 6371 * d

(6371 公里是average radius of the earth

此方法的计算要求最低。但是,对于小距离,结果非常准确。


然后,如果它在给定的距离内,或多或少,使用更准确的方法。

GeographicLib 是我所知道的最准确的实现,尽管Vincenty inverse formula 也可以使用。


如果您使用的是 RDBMS,请将纬度设置为主键,将经度设置为辅助键。如上所述查询纬度范围或纬度/经度范围,然后计算结果集的准确距离。

请注意,所有主要 RDBMS 的现代版本都原生支持地理数据类型和查询。

【讨论】:

  • 请注意,第一个链接已损坏。
  • @kunruh:谢谢。该链接指向 Ed Williams 的航空处方集,该处方集现在似乎已离线。我已经用公式替换了链接。
  • 这个链接解释了几乎所有与这个主题相关的movable-type.co.uk/scripts/…
【解决方案2】:

根据当前用户的经纬度和想要查找的距离,下面给出sql查询。

SELECT * FROM(
    SELECT *,(((acos(sin((@latitude*pi()/180)) * sin((Latitude*pi()/180))+cos((@latitude*pi()/180)) * cos((Latitude*pi()/180)) * cos(((@longitude - Longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) as distance FROM Distances) t
WHERE distance <= @distance

@latitude 和@longitude 是该点的纬度和经度。 纬度和经度是距离表的列。 pi 的值为 22/7

【讨论】:

  • @distance 参数是公里还是英里?
  • 我假设距离以公里为单位,否则我的脚本会出错,请有人回答上述问题。
【解决方案3】:

Tank 的 Yogihosting

我的数据库中有一组来自 Open Streep Maps 的表格,我测试成功。

以米为单位的距离工作正常。

SET @orig_lat=-8.116137;
SET @orig_lon=-34.897488;
SET @dist=1000;

SELECT *,(((acos(sin((@orig_lat*pi()/180)) * sin((dest.latitude*pi()/180))+cos((@orig_lat*pi()/180))*cos((dest.latitude*pi()/180))*cos(((@orig_lon-dest.longitude)*pi()/180))))*180/pi())*60*1.1515*1609.344) as distance FROM nodes AS dest HAVING distance < @dist ORDER BY distance ASC LIMIT 100;

【讨论】:

  • 世界不是球体!
  • 你有什么建议?
【解决方案4】:

PostgreSQL GIS extensions 可能会有所帮助 - 例如,它可能已经实现了您正在考虑实现的大部分功能。

【讨论】:

    【解决方案5】:

    正如 biziclop 所提到的,某种度量空间树可能是您的最佳选择。我有使用 kd-trees 和 quad tree 进行此类范围查询的经验,而且它们的速度非常快;它们也不难写。我建议您研究其中一种结构,因为它们还可以让您回答其他有趣的问题,例如“我的数据集中与其他点最近的点是什么?”

    【讨论】:

    • 虽然这可能是解决问题的宝贵提示,但答案确实需要证明解决方案。请edit 提供示例代码来说明您的意思。或者,考虑将其写为评论。
    • 我实际上认为这里的代码会分散注意力 - 它对于包含树结构和所选特定语言的库来说太具体了(请注意,这个问题没有用语言标记。)
    【解决方案6】:

    您需要的是空间搜索。您可以使用Solr Spatial search。它还内置了 lat/long 数据类型,check here

    【讨论】:

    • 虽然这在理论上可以回答问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
    【解决方案7】:

    您可以将经纬度转换为 UTM 格式,这是一种公制格式,可以帮助您计算距离。然后您可以轻松确定点是否落入特定位置。

    【讨论】:

    • 虽然这可能是解决问题的宝贵提示,但答案确实需要证明解决方案。请edit 提供示例代码来说明您的意思。或者,考虑将其写为评论。
    【解决方案8】:

    既然你说任何语言都可以接受,自然选择 PostGIS:

    SELECT * FROM places
    WHERE ST_DistanceSpheroid(geom, $location, $spheroid) < $max_metres;
    

    如果要使用WGS基准,应将$spheroid设置为'SPHEROID["WGS 84",6378137,298.257223563]'

    假设您已通过 geom 列对 places 进行索引,这应该是相当有效的。

    【讨论】:

      【解决方案9】:

      感谢@yogihosting 提供的解决方案,我能够从mysql 的无模式列中获得类似的结果,代码如下所示:

      // @params - will be bound to named query parameters
      $criteria = [];
      $criteria['latitude'] = '9.0285183';
      $criteria['longitude'] = '7.4869546';
      $criteria['distance'] = 500;
      $criteria['skill'] = 'software developer';
      
      // Get doctrine connection 
      $conn = $this->getEntityManager()->getConnection();
      
              $sql = '
                     SELECT DISTINCT m.uuid AS phone, (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
                    cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
                    cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) AS distance FROM member_profile AS m 
                     INNER JOIN member_card_subscription mcs ON mcs.primary_identity = m.uuid
                     WHERE mcs.end > now() AND JSON_SEARCH(m.skill_logic, "one", :skill) IS NOT NULL  AND (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
                    cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
                    cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) <= :distance ORDER BY distance
                     ';
              $stmt = $conn->prepare($sql);
              $stmt->execute(['latitude'=>$criteria['latitude'], 'longitude'=>$criteria['longitude'], 'skill'=>$criteria['skill'], 'distance'=>$criteria['distance']]);
              var_dump($stmt->fetchAll());
      

      请注意上面的代码sn-p使用的是doctrine DB连接和PHP

      【讨论】:

        【解决方案10】:

        你可以检查这个等式 我认为这会有所帮助

        SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;
        

        【讨论】:

        • 虽然此代码可能有助于解决问题,但它并没有解释为什么和/或如何回答问题。提供这种额外的背景将显着提高其长期教育价值。请edit您的答案添加解释,包括适用的限制和假设。尤其是魔法值 3959 和 37 是从哪里来的?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-06
        • 2012-06-22
        • 2016-05-16
        • 1970-01-01
        • 2021-05-10
        • 1970-01-01
        相关资源
        最近更新 更多