【问题标题】:Calculating distance between 2 points fails when points are the same当点相同时,计算两点之间的距离失败
【发布时间】:2016-01-31 15:45:05
【问题描述】:

我在我的 JAVA Spring 服务器中运行以下 SQL 查询。此查询适用于几乎所有坐标,除了一对特定坐标 c = (= 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 问题还是公式有问题?

【问题讨论】:

  • 哪个 RDBMS。请标记您正在使用的那个

标签: sql database location distance latitude-longitude


【解决方案1】:

这很可能是由于floating point accuracy problems

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

设φ11和φ12为地理经纬度两点1和2,以及Δφ、Δλ它们的绝对差值;那么Δσ,它们之间的中心角,由余弦球面定律给出:

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

距离d,即弧长,对于一个半径为r的球体和以弧度给出的Δσ

<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 Server 将引发异常:

发生了无效的浮点运算。

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 Server 不提供 最大/最小 函数。解决这个问题的问题有several answers

【讨论】:

  • 非常感谢。你说得非常准确。我正在使用 Round(, 10),这项工作对我来说令人满意。
猜你喜欢
  • 2010-10-30
  • 2011-04-23
  • 2014-11-14
  • 1970-01-01
  • 2017-08-27
  • 2015-08-16
相关资源
最近更新 更多