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

时间:2011-02-17 15:53:15

标签: algorithm geolocation gps location latitude-longitude

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

从DB中选择所有位置然后逐个浏览它们似乎并不是非常有效,从起始位置获取距离以查看它们是否在指定距离内。有没有一种很好的方法来缩小DB中最初选择的位置?一旦我有(或没有?)一组缩小的位置,我是否仍然逐个检查距离,或者有更好的方法吗?

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

11 个答案:

答案 0 :(得分:36)

首先比较纬度之间的距离。每个纬度相距约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 km是average radius of the earth

这种方法的计算要求是最小的。然而,对于小距离,结果非常准确。


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

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


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

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

答案 1 :(得分:6)

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

答案 2 :(得分:4)

尝试使用此解决方案:Geolocation Search

答案 3 :(得分:4)

Tank's 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 :(得分:2)

答案 5 :(得分:2)

正如biziclop所提到的,某种度量空间树可能是您的最佳选择。我有使用kd-trees和quad树来做这些范围查询的经验,而且它们的速度非常快;他们写起来并不难。我建议调查其中一个结构,因为它们也让你回答其他有趣的问题,比如“我的数据集中最接近另一点的是什么?”

答案 6 :(得分:1)

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

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

如果您想使用WGS数据,则应将$spheroid设为'SPHEROID["WGS 84",6378137,298.257223563]'

假设您已在places列中编入了geom索引,这应该相当有效。

答案 9 :(得分: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());

请注意,上面的代码段使用的是教义的DB连接和PHP

答案 10 :(得分:-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;