如果您可以修改您的数据库结构,有一种超级简单的方法可以做到这一点:代替(或除了)存储纬度和经度,将您的位置坐标转换为 3D 空间,其中包含 x、y 和z 以米为单位。然后你就可以做
SELECT * FROM location
WHERE location.x BETWEEN center.x - 300 AND center.x + 300
AND location.y BETWEEN center.y - 300 AND center.y + 300
AND location.z BETWEEN center.z - 300 AND center.z + 300
这将很好地缩减您的列表,并且您可以对结果集进行半正弦计算。
如果您坚持使用只有经度和纬度的数据库,您仍然可以缩小搜索范围。纬度很容易:只要您忽略靠近两极时出现的复杂情况,北纬或南纬1度总是对应111公里的距离。这意味着 300 m 的距离是 0.0027... 纬度,尽管您不妨保守一点,使用 0.003 或 0.004。
经度有点棘手,因为转换因子会根据您的南北距离而变化,但它仍然不是太复杂:您只需乘以纬度的余弦即可。
distance = cos(latitude) * 111.19... km/degree * delta_angle
在赤道,与纬度相同:赤道经度变化 1 度为 111 公里。在北(或南) 80 度处,乘以 cos(80 degrees) = 0.17... 的系数,结果经度变化 1 度只有 19.3 公里。出于您的目的,您可以将其反转并找到要选择为300 m / cos(latitude) / (111.19... km/degree) = (0.0027... degrees) / cos(latitude) 的经度范围。该系数与第一段中的数量相同;这不是巧合。
棘手的问题出现在坐标系的不连续点附近,例如当您靠近两极时。当你开始插入像 89.9996 度这样的纬度时,你会明白为什么:
0.0027... degrees / cos(89.9996 degrees) = 386... degrees
好吧,如果整个圆只有 360 度,那怎么可能呢?从某种意义上说,这表明您已经到达了 300 m 半径一直延伸到杆周围并返回到包括您的起始位置的地步。那时,您不妨只搜索数据库中离极点足够近的所有点。当然,您应该真正从 89.999 度左右开始执行此操作,因为您正在搜索的区域的 600 m 直径几乎完全环绕极点。
在国际日期变更线(或者更准确地说是“逆子午线”)(嗯,靠近)还有另一个问题,与从 -180 度到 +180 度经度的跳跃有关。赤道上的 +179.9999 度和 -179.9999 度的点将具有非常不同的坐标,即使它们在地理上相距仅几米。由于您只是将其作为更详细搜索的初步过滤器,因此最简单的方法可能是通过逆子午线 0.006 度(大约是 300 m 半径圆的直径)内的每个点,然后是半正弦计算将确定这些点是否实际接近。
总而言之,您可以使用我上面提到的纬度和经度界限,只需为两极和反子午线添加特殊情况。在某种伪 SQL/代码混合中:
IF abs(center.latitude) > 89.999
SELECT * FROM location WHERE abs(location.latitude - center.latitude) < 0.003
ELSE
IF abs(center.longitude) > 179.997
SELECT * FROM location
WHERE abs(location.latitude - center.latitude) < 0.003
AND 180 - abs(location.longitude) < (0.006 / cos(center.latitude))
ELSE
SELECT * FROM location
WHERE abs(location.latitude - center.latitude) < 0.003
AND abs(location.longitude - center.longitude) < (0.003 / cos(center.latitude))
ENDIF
ENDIF
如果你想要一个简洁的陈述,而代价是不得不测试两倍的点,你只能比较经度的绝对值:
SELECT * FROM location
WHERE abs(location.latitude - center.latitude) < 0.003
AND abs(abs(location.longitude) - abs(center.longitude)) <= min(0.003 / cos(center.latitude), 180)