考虑到纬度+经度位置的数据库,例如40.8120390,-73.4889650,我如何在特定位置的给定距离内找到所有位置?
从DB中选择所有位置然后逐个浏览它们似乎并不是非常有效,从起始位置获取距离以查看它们是否在指定距离内。有没有一种很好的方法来缩小DB中最初选择的位置?一旦我有(或没有?)一组缩小的位置,我是否仍然逐个检查距离,或者有更好的方法吗?
我这样做的语言并不重要。谢谢!
答案 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;