【问题标题】:Optimization of a distance calculation function距离计算功能的优化
【发布时间】:2010-10-11 12:59:18
【问题描述】:

在我的代码中,我必须在成对的纬度/经度值之间进行大量距离计算。

代码如下所示:

double result = Math.Acos(Math.Sin(lat2rad) * Math.Sin(lat1rad) 
+ Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad));

(lat2rad 例如纬度转换为弧度)。

我已将此功能确定为我的应用程序的性能瓶颈。有什么办法可以改善吗?

(我不能使用查找表,因为坐标是变化的)。我还查看了this question,其中建议使用网格之类的查找方案,这可能是一种可能性。

感谢您的宝贵时间! ;-)

【问题讨论】:

  • 你应该知道,这个算法只有在你假设地球是一个完美的球体并且近似值和真实答案之间的差异可能非常显着(至少在我的世界里)时才是正确的。 en.wikipedia.org/wiki/WGS84
  • 确实如此。您可能真的需要计算大圆路线。
  • 是的,我知道,但对于我的情况,近似值是可以的。据我所知,由于地球自转,赤道附近的偏差最大。
  • 如果(正如您在我的回答的 cmets 中所说)该行需要 分钟 才能运行 - 还有其他事情!

标签: c# geolocation


【解决方案1】:

如果您的目标是对距离进行排名(比较),那么近似值(sincos 查找表)可以大大减少所需的计算量(实现 快速拒绝。)

您的目标是仅在近似距离之间的差异(要进行排名或比较)低于某个阈值时才进行实际的三角计算。

例如使用具有 1000 个样本的查找表(即,sincos2*pi/1000 采样一次),查找不确定性最多为 0.006284。使用uncertainty calculation作为ACos的参数,累积不确定性,也就是阈值不确定性,最大​​为0.018731。

因此,如果对两个坐标集对(距离)使用 sincos 查找表评估 Math.Sin(lat2rad) * Math.Sin(lat1rad) + Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad) 会产生一定的排名(根据近似值,一个距离似乎大于另一个),并且差异模大于上述阈值,则近似有效。否则继续进行实际的三角函数计算。

【讨论】:

  • 谢谢,您的回答让我走上了正轨。我相信其他一些答案也是有效的。使用具有 50.000 个条目精度的查找表仍然足够好。提速大约是之前性能的 3 倍。
【解决方案2】:

CORDIC 算法是否适合您(在速度/准确性方面)?

【讨论】:

  • 感谢您的想法,我会尝试不同的方法,看看哪种方法最有效。
【解决方案3】:

使用来自@Brann 的灵感,我认为您可以稍微减少计算(警告它已经很长时间了,因为我做了任何这些,并且需要验证)。不过,某种形式的预计算值查找可能是最快的

你有:

1: ACOS( SIN A SIN B + COS A COS B COS(A-B) )

但是 2: COS(A-B) = SIN A SIN B + COS A COS B

改写为3:SIN A SIN B = COS(A-B) - COS A COS B

将 SIN A SIN B 替换为 1。你有:

4: ACOS(COS(A-B) - COS A COS B + COS A COS B COS(A-B) )

您预先计算 X = COS(A-B) 和 Y = COS A COS B,然后将值放入 4

给予:

ACOS(X - Y + XY)

4 次三角计算而不是 6 次!

【讨论】:

    【解决方案4】:

    改变你存储长/纬的方式:

    struct LongLat
    {
      float
        long,
        lat,
        x,y,z;
    }
    

    在创建 long/lat 时,还要计算 (x,y,z) 3D 点,该点表示以原点为中心的单位球体上的等效位置。

    现在,要确定 B 点是否比 C 点更靠近 A 点,请执行以下操作:

    // is B nearer to A than C?
    bool IsNearer (LongLat A, LongLat B, LongLat C)
    {
      return (A.x * B.x + A.y * B.y + A.z * B.z) < (A.x * C.x + A.y * C.y + A.z * C.z);
    }
    

    并得到两点之间的距离:

    float Distance (LongLat A, LongLat B)
    {
      // radius is the size of sphere your mapping long/lats onto
      return radius * acos (A.x * B.x + A.y * B.y + A.z * B.z);
    }
    

    您可以删除“半径”项,有效地标准化距离。

    【讨论】:

      【解决方案5】:

      切换到 sin/cos/acos 的查找表。会更快,有很多 c/c++ 定点库也包含这些。

      这是Memoization 上其他人的代码。如果使用的实际值更集中,这可能会起作用。

      这是Fixed Point 上的一个 SO 问题。

      【讨论】:

        【解决方案6】:

        什么是瓶颈?是正弦/余弦函数调用还是反正弦函数调用?

        如果您的正弦/余弦调用速度很慢,您可以使用以下定理来防止如此多的调用:

        1 = sin(x)^2 + cos(x)^2
        cos(x) = sqrt(1 - sin(x)^2)
        

        但我喜欢映射的想法,这样您就不必重新计算已经计算过的值。不过要小心,因为地图会很快变得非常大。

        【讨论】:

          【解决方案7】:

          您需要的值有多精确?

          如果您将值四舍五入,那么您可以存储所有查找的结果并检查是否已在每次计算之前使用它们?

          【讨论】:

            【解决方案8】:

            好吧,既然 lat 和 lon 都在一定范围内,您可以尝试使用某种形式的查找表来调用 Math.* 方法。说,Dictionary&lt;double,double&gt;

            【讨论】:

              【解决方案9】:

              我认为您可能需要重新检查您是如何发现该功能成为瓶颈的。 (IE 你是否对应用程序进行了概要分析?)

              对我来说,方程式似乎很轻,不应该造成任何麻烦。 当然,我不知道你的应用程序,你说你做了很多这样的计算。

              不过还是要考虑一下。

              【讨论】:

              • 我使用 VS 2008 Profiler 来检查我的应用程序。计算运行了相当长的一段时间(几分钟),所以我很确定采样输出是正确的。
              • 如果该行花费了 分钟,则说明有问题。
              【解决方案10】:

              正如其他人指出的那样,您确定这是您的瓶颈吗?

              我已经对正在构建的类似应用程序进行了一些性能测试,在该应用程序中我调用了一个简单的方法来使用标准三角函数返回两点之间的距离。对它的 20,000 次调用将其推到了分析输出的顶部,但我无法让它更快......这只是调用次数的剪切。

              在这种情况下,我需要减少对它的 # 调用...并不是说这是瓶颈。

              【讨论】:

              • 是的,您可以通过消除三角函数来加快速度。方法调用非常非常便宜。
              【解决方案11】:

              我使用不同的算法来计算 2 个纬度/经度位置之间的距离,它可能比你的更轻,因为它只进行 1 个 Cos 调用和 1 个 Sqrt 调用。

              public static double GetDistanceBetweenTwoPos(double lat1, double long1, double lat2, double long2)
              {
                double distance = 0;
                double x = 0;
                double y = 0;
              
                x = 69.1 * (lat1 - lat2);
                y = 69.1 * (long1 - long2) * System.Math.Cos(lat2 / 57.3);
              
                //calculation base : Miles
                distance = System.Math.Sqrt(x * x + y * y);
              
                //Distance calculated in Kilometres
                return distance * 1.609;
              }
              

              【讨论】:

              • 如果你能穿越地球就好了:)
              【解决方案12】:

              有人已经提到过记忆,这有点相似。如果您将同一点与许多其他点进行比较,那么最好预先计算该等式的各个部分。

              而不是

              双倍结果 = Math.Acos(Math.Sin(lat2rad) * Math.Sin(lat1rad) + Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad));

              有:

              双倍结果 = Math.Acos(lat2rad.sin * lat1rad.sin + lat2rad.cos * lat1rad.cos * (lon2rad.cos * lon1rad.cos + lon1rad.sin * lon2rad.sin));

              我认为这与其他人发布的公式相同,因为当您展开括号时,部分等式将消失:)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-12-26
                • 1970-01-01
                • 2020-10-21
                • 2020-09-15
                • 1970-01-01
                • 2014-04-29
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多