查找距给定纬度位置一定距离内的所有纬度经度位置的算法


81

给定具有纬度+经度位置的位置数据库,例如40.8120390,-73.4889650,我如何查找特定位置给定距离内的所有位置?

从数据库中选择所有位置然后逐个浏览它们,从起始位置获取距离以查看它们是否在指定距离之内,效率似乎并不高。有什么好方法可以缩小数据库中最初选择的位置的范围?一旦找到了(或没有?)缩小的位置范围,是否仍要逐个检查位置,还是有更好的方法?

我使用的语言并不重要。谢谢!


4
这可能是您需要的:en.wikipedia.org/wiki/K-d_tree
biziclop 2011年

1
一个SQL查询不能解决吗?SELECT * FROM FROM WHERE(Lat-:Lat)^ 2 +(Long-:Long)^ 2 <=:Distance ^ 2的地方(ofc,地球的球形涉及所有其他数学,这仅仅是一个例子)
Dialecticus

1
@ Ashu,nOiAd,不幸的是我不得不放弃该项目,所以我最终没有选择解决方案。如果你们在您的项目中使用一种解决方案,那么我和其他人将非常感谢您在这里对它的评论。
瓦莱拉

Answers:


41

首先比较纬度之间的距离。每个纬度相距约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公里是地球平均半径

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


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

尽管我也可以使用Vincenty逆公式,但我知道GeographicLib是最准确的实现。


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

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


抬起头来,第一个链接断开了。
kunruh

@kunruh:谢谢。该链接指向的是埃德·威廉姆斯(Ed Williams)的航空配方,该配方似乎现在已经离线。我已经用公式替换了链接。
Lior Kogan'4-4-5

该链接解释了几乎所有与此主题相关的可移动类型.co.uk
scripts

13

根据当前用户的纬度,经度和您要查找的距离,下面给出了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


1
@distance参数是KM还是Miles?
garfbradaz


5

坦克的瑜伽主持人

我的数据库中有来自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;

世界不是一个领域!
Toby Speight

你有什么建议?
Helmut Kemper


2

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


尽管这可能是解决问题的宝贵提示,但确实需要一个答案来证明解决方案。请编辑以提供示例代码以显示您的意思。或者,考虑将其写为注释。
Toby Speight

1
我实际上认为这里的代码会分散注意力-它过于特定于包含树结构和所选择的特定语言的库(请注意,此问题未使用语言标记。)
templatetypedef


0

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


1
尽管这可能是解决问题的宝贵提示,但确实需要一个答案来证明解决方案。请编辑以提供示例代码以显示您的意思。或者,考虑将其写为注释。
Toby Speight

0

因为您说任何语言都是可以接受的,所以自然的选择是PostGIS:

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

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

假设您已places按该geom列编制索引,那么这样做应该相当有效。


0

感谢@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());

请注意,以上代码段使用的是doctrine DB连接和PHP


-2

您可以检查一下这个方程式,我认为这会有所帮助

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;

尽管此代码可能有助于解决问题,但并未解释为什么和/或如何回答问题。提供这种额外的环境将大大提高其长期教育价值。请编辑您的答案以添加解释,包括适用的限制和假设。尤其是3959和37的魔力值从何而来?
Toby Speight
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.