在用户所在的经纬度范围内,查找距离用户经纬度20英里以内的经纬度。

3
我正在处理一个应用程序,用户可以搜索距离他位置附近的物品。
当用户注册我的服务时,会获取他们的经纬度坐标(实际上是从邮编中提取,然后通过Google查找转换为经纬度)。当用户添加一个项目时,会要求填写该项目所在的邮编,然后将其转换为经纬度。
我的问题是:如何使用MySQL运行查询,以便在用户位置周围20英里范围内搜索并获得该半径内的所有物品?
6个回答

2
假设精度不是问题(采用正方形而不是圆形,忽略地形),您可以这样做:
SELECT ... FROM ...
WHERE (ABS(firstLong - secondLong) < 20) AND (ABS(firstLat - secondLat) < 20);

如果你想要将其变成一个圆形,只需编写更复杂的数学公式来计算距离:SQRT(longDelta*longDelta + latDelta*latDelta) < 20


5
你的公式都假设纬度的一度长度等于经度的一度长度(只在赤道成立),并且二者都等于一英里(非常错误)。经线一度长度随着纬度变化而变化,在极点处为零。在像西雅图这样的纬度上,误差是很大的。 - John Machin
3
这个答案是错误的。正如John Machin提到的,lat/lon的单位是度,而20的单位是英里。 - TreyA

2
当存储经纬度数据时,您还可以存储所谓的“地理空间索引”,它基本上是一种同时编码两个数据的字符串。其中一个索引方案是使用Geohash算法,该算法使用一系列比特将地球分割成越来越小的网格框。
然后,当您想通过距离搜索时,首先根据Geohash缩小搜索范围,然后通过测试Euclidean distance或使用Haversine formula来过滤结果。
另一个选择是使用专门用于执行此类查询的单独数据库。例如MongoDB 原生支持地理空间索引,而CouchDB可以在geocouch的帮助下执行此操作。
回到MySQL,这个演示文稿可能会对您有所帮助:使用MySQL进行地理距离搜索

1
-1 Euclidean_distance = earth_radius * sqrt(delta_lat ** 2 + delta_lon ** 2)在厄瓜多尔可行,但在阿拉斯加却极不准确。 - John Machin

1

根据您使用的平台,有几个选项:

暴力破解 - 取出数据库中的项目,并在您的经纬度和项目坐标之间运行线性地理距离函数,类似于以下内容:

public decimal GeoDistance(decimal lat1, decimal lng1, decimal lat2, decimal lng2)
{
    double r = 6378.7; //km

    decimal p = (decimal)(Math.PI / 180.0);
    lat1 *= p; lat2 *= p; lng1 *= p; lng2 *= p;

    return (decimal)(r * (Math.Acos(Math.Sin((double)lat1) * Math.Sin((double)lat2) + Math.Cos((double)lat1) * Math.Cos((double)lat2) * Math.Cos((double)lng2 - (double)lng1))));
}

如果您正在使用MS SQL Server 2008(其他数据库引擎也可能支持),您可以使用地理方法


1
在Python中,您可以使用kdtree,尝试以下代码片段:
首先,您需要安装“pysal”。
import pysal
from pysal.cg.kdtree import KDTree    

locations = [(40.702566, -73.816859),
         (40.70546, -73.810708),
         (40.709179, -73.820574),
         (40.700486, -73.807969),
         (40.694624, -73.820593),
         (40.695132, -73.820841),
         (40.694095, -73.821334),
         (40.694165, -73.822368),
         (40.695077, -73.822817),
         (40.6747769261, -73.8092618174)] 
tree = KDTree(locations, distance_metric='Arc', radius=pysal.cg.RADIUS_EARTH_MILES)
current_point = (40.709523, -73.802472)
# get all points within 1 mile of 'current_point'
indices = tree.query_ball_point(current_point, 1)
for i in indices:
    print(locations[i])

谢谢,这正是我所需要的,而且使用起来非常简单! :) - Sverrir Sigmundarson
这似乎在大约5-10英里左右不准确。 - Mike C.
https://crosscompute.com/n/4NpIfkK5A8OzOTws1awyqfR1cgOcVlQV/-/kdtree 看起来不错,你能提供数据吗? - salah
在传递kd树之前,您应该将空间参考系统更改为UTM。https://ocefpaf.github.io/python4oceanographers/blog/2013/12/16/utm/ - salah

0

1
这应该是一条评论,而不是一个答案。 - Mark Ransom
1
@Mark,这是我试图进行的一次顶贴:P - Jon Black

0
为了提高性能,您不希望完全扫描数据库并计算每一行的距离,而是希望可以索引的条件。最简单的方法是计算一个具有最小/最大纬度和最小/最大经度的框,并使用BETWEEN来排除那些范围之外的所有内容。由于您只处理基于邮政编码的美国位置,因此不必担心+180和-180度之间的过渡。
唯一剩下的问题是在以英里为单位的条件下计算框的边界。您需要将英里转换为度数。对于纬度来说,这很容易,只需将地球的周长除以360度并乘以20;0.289625度。经度更难,因为它随着纬度的变化而变化,周长大约是cosine(latitude)*24901.461;20英里是20*360/(cos(latitude)*24901.461)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接