给定具有纬度+经度位置的位置数据库,例如40.8120390,-73.4889650,我如何查找特定位置给定距离内的所有位置?
从数据库中选择所有位置然后逐个浏览它们,从起始位置获取距离以查看它们是否在指定距离之内,效率似乎并不高。有什么好方法可以缩小数据库中最初选择的位置的范围?一旦找到了(或没有?)缩小的位置范围,是否仍要逐个检查位置,还是有更好的方法?
我使用的语言并不重要。谢谢!
给定具有纬度+经度位置的位置数据库,例如40.8120390,-73.4889650,我如何查找特定位置给定距离内的所有位置?
从数据库中选择所有位置然后逐个浏览它们,从起始位置获取距离以查看它们是否在指定距离之内,效率似乎并不高。有什么好方法可以缩小数据库中最初选择的位置的范围?一旦找到了(或没有?)缩小的位置范围,是否仍要逐个检查位置,还是有更好的方法?
我使用的语言并不重要。谢谢!
Answers:
首先比较纬度之间的距离。每个纬度相距约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
该方法的计算要求很小。但是,对于小距离,结果非常准确。
然后,如果它在给定距离内(或多或少),则使用更准确的方法。
尽管我也可以使用Vincenty逆公式,但我知道GeographicLib是最准确的实现。
如果使用RDBMS,则将纬度设置为主键,将经度设置为主键。如上所述,查询纬度范围或纬度/经度范围,然后计算结果集的精确距离。
请注意,所有主要RDBMS的现代版本均本地支持地理数据类型和查询。
根据当前用户的纬度,经度和您要查找的距离,下面给出了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
PostgreSQL GIS扩展可能会有所帮助-例如,它可能已经实现了您正在考虑实现的许多功能。
坦克的瑜伽主持人
我的数据库中有来自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;
正如biziclop所说,某种度量空间树可能是您的最佳选择。我有使用kd树和四叉树进行这些范围查询的经验,它们的速度非常快;他们也不难写。我建议您研究这些结构之一,因为它们还可以让您回答其他有趣的问题,例如“我的数据集中最接近该点的是什么?”
您需要的是空间搜索。您可以使用Solr Spatial搜索。它还内置了lat / long数据类型,请在此处检查。
您可以将纬度-经度转换为UTM格式,该格式是可以帮助您计算距离的公制格式。然后,您可以轻松确定点是否落入特定位置。
因为您说任何语言都是可以接受的,所以自然的选择是PostGIS:
SELECT * FROM places
WHERE ST_DistanceSpheroid(geom, $location, $spheroid) < $max_metres;
如果要使用WGS基准,则应设置$spheroid
为'SPHEROID["WGS 84",6378137,298.257223563]'
假设您已places
按该geom
列编制索引,那么这样做应该相当有效。
感谢@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
您可以检查一下这个方程式,我认为这会有所帮助
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;