当点相同时计算两点之间的距离失败

Calculating distance between 2 points fails when points are the same

我 运行 在我的 JAVA Spring 服务器中跟踪 SQL 查询。此查询适用于几乎所有坐标,除了一对特定坐标 c = <23.065079, 72.511478> (= to_lat, to_long):

SELECT * 
FROM   karpool.ride 
WHERE  Acos(Sin(Radians(23.065079)) * Sin(Radians(to_lat)) + 
            Cos(Radians(23.065079)) * Cos(Radians(to_lat)) * 
            Cos(Radians(to_lon) - Radians(72.511478))) * 6371 <= 10; 

我的数据库中有很多位置距离 c 10 公里以内。通过上面的查询,我得到了所有这些位置的距离,除了与 c 完全匹配的距离。在这种情况下,返回的距离应该为 0,但查询失败。

这是SQL问题还是公式有问题?

这很可能是由于 floating point accuracy problems

首先,使用的公式是Great circle distance formula:

Let φ11 and φ12 be the geographical latitude and longitude of two points 1 and 2, and Δφ,Δλ their absolute differences; then Δσ, the central angle between them, is given by the spherical law of cosines:

Δσ = arccos ( sin φ<sub>1</sub> ∙ sin φ<sub>2</sub> + cos φ<sub>1</sub> ∙ cos φ<sub>2</sub> ∙ cos (Δλ) ).

The distance d, i.e. the arc length, for a sphere of radius r and Δσ given in radians

<em>d</em> = <em>r</em> Δσ.

现在如果两个点相同,则Δλ = 0,因此cos(Δλ) = cos(0) = 1,第一个公式简化为:

Δσ = arccos (sin φ ∙ sin φ + cos φ ∙ cos φ).

arccos 的参数变成了 Pythagorean trigonometric identity,因此等于 1。

所以上面减少到:

Δσ = arccos (1).

问题

domain of the arccosine 是:−1 ≤ x ≤ 1,因此值为 1 我们处于定义域的边界。

由于 1 的值是多个浮点运算(正弦、余弦、乘法)的结果,因此该值可能 不完全是 1,而是类似于1.0000000000004。这就带来了一个问题,因为该值 超出计算反余弦的范围 。数据库引擎对这种情况的反应不同:

SQL 服务器将引发异常:

An invalid floating point operation occurred.

MySql 只会将表达式计算为 null.

解决方案

不知何故,传递给反余弦的参数应该保持在 −1 ≤ x ≤ 1 的范围内。这样做的一种方法是将参数四舍五入到足够大的小数位数以保留一些精度,但小到足以舍去由浮点运算引起的超出此范围的任何多余部分。

大多数数据库引擎都有一个 round 函数,可以向该函数提供第二个参数来指定要保留的位数,因此 SQL 看起来像这个(保留 6 位小数):

SELECT * 
FROM   karpool.ride 
WHERE  Acos(Round(
          Sin(Radians(23.065079)) * Sin(Radians(to_lat)) + 
          Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
          Cos(Radians(to_lon) - Radians(72.511478)),
          6
       )) * 6371 <= 10;

或者,您可以使用某些数据库引擎提供的函数 greatestleast,将任何多余的值变为 1 (或 -1):

SELECT * 
FROM   karpool.ride 
WHERE  Acos(Greatest(Least(
          Sin(Radians(23.065079)) * Sin(Radians(to_lat)) + 
          Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
          Cos(Radians(to_lon) - Radians(72.511478)),
          1), -1)
       ) * 6371 <= 10;

注意SQL服务器不提供greatest/least功能。克服这个问题有 several answers.