【问题标题】:Fastest way to reduce number of latitude and longitude points减少经纬度点数量的最快方法
【发布时间】:2011-11-30 09:37:50
【问题描述】:

我正在尝试将多个点减少并合并到这些位置的中心点。现在我通过找到最接近的对来强制它,将它们组合并重复直到我将它减少到我的目标(旁注:实际上我通过(lat*lat+long*long)排序然后在任一侧搜索10%来减少问题每个点的距离,我的测试总是找到该范围内的最短距离)。

例如,我想将 4000 个点减少到 1000 个,理想情况下将最近的点组合到这些最近点的中心。基本上是为了建立反映该区域地址数量的标记点。

有没有更好的算法可以给我尽可能准确的结果?还是更快的距离算法?我想它只需要在短距离内准确


现在我正在寻找距离(维基百科在“球形地球投影到平面”下有它):

double dLat = pos2.LatitudeR - pos1.LatitudeR;
double dLon = pos2.LongitudeR - pos1.LongitudeR;

double cosLatM = Math.Cos((pos2.LatitudeR + pos1.LatitudeR)/2) * dLon;
double a = dLat*dLat + cosLatM*cosLatM;

我考虑过将彼此距离为 x 的所有点分组,然后扩展 x 直到达到我的目标最终点数,但我不知道如何让它像我的完美主义想要的那样准确它。这就是我能想到的所有方式,具体取决于输入点列表的顺序。


编辑以描述我当前的算法如何处理(这是找到我想要的结果的理想方式,但更快的近似值是值得的):

线性描述它,如果你有x=1,4,5,6,10,20,22

  1. 它将结合 4+5=4.5 [找到的第一个 1.0 距离]
  2. (4.5*2+6)/3=5 -- x=1,5,10,20,22 [1.5 距离]
  3. 20+22=21 -- x=1,5,10,21 [2.0 距离]
  4. (5*3+1)/4=4 -- x=4,10,21 [4.0 距离]
  5. (4*4+10)/5.2 -- 所以你最终会得到x=5.2,21。 (它会跟踪 CombineCount,因此它可以通过这种方式找到正确的平均中心)

结果: 这是我当前的距离函数,为 cos^2 生成查找表。还没来得及检查我的点有多接近,所以还没有实施乔伊关于近似 cos^2 的建议,但这可以提高此处查找表的速度。

我尝试的 K-Cluster 算法(请参阅我对该答案的评论)并没有按照我的意愿将它们组合在一起,它最终在地图中心附近有大量点,而在边缘则有少量点。因此,除非我能纠正我使用的算法较慢。

public static double Distance(AddressCoords pos1, AddressCoords pos2, DistanceType type)
{
    if (LookupTable == null) LookupTable = BuildLookup();

    double R = (type == DistanceType.Miles) ? 3960 : 6371;

    double dLat = pos2.LatitudeR - pos1.LatitudeR;
    double dLon = pos2.LongitudeR - pos1.LongitudeR;

    double LatM = ((pos2.LatitudeR + pos1.LatitudeR)/2);
    if (LatM < 0) LatM = -LatM; //Don't allow any negative radian values
    double cosLatM2 = LookupTable[(int)(LatM * _cacheStepInverse)];
    double a = dLat*dLat + cosLatM2 * dLon*dLon;

    //a = Math.Sqrt(a);

    double d = a * R;

    return d;
}

private const double _cacheStep = 0.00002;
private const double _cacheStepInverse = 50000;

private static double[] LookupTable = null;

public static double[] BuildLookup()
{
    // set up array
    double maxRadian = Math.PI*2;
    int elements = (int)(maxRadian * _cacheStepInverse) + 1;

    double[] _arrayedCos2 = new double[elements];
    int i = 0;
    for (double angleRadians = 0; angleRadians <= maxRadian;
        angleRadians += _cacheStep)
    {
        double cos = Math.Cos(angleRadians);
        _arrayedCos2[i] = cos*cos;
        i++;
    }
    return _arrayedCos2;
}

【问题讨论】:

  • 只是为了更好地理解您的要求,如果您的 4000 个点完全均匀地分布在网格中会怎样?
  • 如果是这种情况,我的要求不会关心它选择组合哪些对...如果它们都是正方形,我认为我当前的算法会将前两个组合在一起找到一个中心点。中途会有矩形,然后结合那些最接近的对以获得 4 个点的中心点。如果不是以 2 的幂次方减少,则取决于点的顺序
  • 如果你有 3 个点与其他点相距很远,你希望发生什么?把两个结合起来,让另一个不理会?结合两个,然后将另一个与一个很远的地方结合起来?还有什么?
  • 是的,我想你已经理解了,但为了确保我添加了当前代码每次迭代的示例

标签: c# performance algorithm


【解决方案1】:

至于一种有效的方法,您是否考虑过在地图上放置一个网格,然后将每个点分配给网格中对应的单元格?这应该有很好的性能。

更好(但更慢)的方法是使用动态单元格而不是像上面建议的固定单元格。你从没有细胞开始。然后放下地图中的第一个点并定义一个周围有一些预定尺寸的单元格。然后在地图上放下下一个点。如果它落在前一个单元格内,则将其添加到其中,并可能将单元格重新定位在两个点周围。如果该点位于单元格之外,则为它创建第二个单元格。现在您将第三个点添加到地图并对照两个单元格检查它。此过程将一直持续到您将所有点添加到地图中为止。我希望你能明白。我认为您可以通过更改单元格的大小来大致限制减少点的数量。

编辑(基于 rrenaud 的评论):您可以开始使用较大的单元格大小并应用上述算法之一。如果您最终得到的单元格数量太少,那么您可以对每个单元格重复该算法并进一步细分它们。虽然这不会让您精确地减少到固定数量的点,但您可以非常接近。

【讨论】:

  • 这将是一个很好的算法,但我认为它不适合 4000->1000 并获得中心点条件吗?
  • 我认为您可以通过更改单元格大小来限制所需的减少点数。我的算法没有做的是自适应地改变像元大小,这样当这些点都彼此靠近时你会得到更小的像元,而当它们分散开时你会得到更大的像元。我正在考虑这个问题,但还没有提出解决方案。
  • 递归地细分单元格,直到它们只包含少量的点。
【解决方案2】:

您是否考虑过查看K-Cluster 算法?

这类算法用于根据最接近的均值将接近/相关的对象(在您的情况下为点)“分组”到集群中。这些算法通常非常优化,并且是为处理大量数据而构建的。在 4000 点 -> 1000 点的情况下,您将对数据运行 1000-Cluster 运行,并返回 1000 组点,每组可以合并为一个点。

【讨论】:

  • 我发现了这个tutorial,这是一个易于修改的 K-Means 实现,这比我原来的算法快得多,但我还没有比较结果。
【解决方案3】:

加快计算点之间的距离:

如果你做一些初等代数,你会得到:

D = R*Sqrt(Lat2^2 + Lat1^2 - 2*Lat1*Lat2 + cos^2((Lat2 + Lat1) /2)(Lon2^2 + Lon1^2 - 2*Lon1*Lon2))

您可以做的第一件事是对地球半径 (R) 进行归一化处理,并比较距离的平方而不是距离,从而避免平方根和 R 项,每次比较可以节省 2 次计算。离开:

valToCompare = Lat2^2 + Lat1^2 - 2*Lat1*Lat2 + cos^2((Lat2 + Lat1) /2)(Lon2^2 + Lon1^2 - 2*Lon1*Lon2)

您可以做的另一件事是预先计算每个坐标的 Lat^2 和 Lon^2 - 将每次比较的计算次数减少 4。

此外,如果这些点在纬度上都相对靠近,您可以通过使用随机点的纬度或所有点的平均纬度而不是平均纬度预先计算它来获得 cos^2 项的近似值比较的两点。这将每次比较的计算次数再减少 4 次。

最后,您可以为每个点预先计算 2*Lat 和 2*Lon,为每次比较减少 2 次计算。

这些都不能改善您的算法本身,但它应该使它运行得更快,并且可以应用于任何需要比较点之间距离的算法。

【讨论】:

  • 不要忘记纬度和经度必须以弧度为单位才能正常工作。
  • 在 C# 中执行此操作并没有提高我的速度。但它确实帮助我确定 Math.Cos 绝对是寻找距离的缓慢部分。所以我想我可能会构建一个使用查找数组的新 Cos 函数。 -- 使用我的测试数据,我的算法使用我的距离函数或你的算法需要 140 秒,但删除对 Math.Cos 的调用需要 30 秒,因此查找表应该至少在 60 秒以下(这是一个简单的修改,我从长远来看会尝试不同的算法)
  • 你能做出我建议的 cos^2 近似吗?那应该把它减少到30s。可能值得打印出您计算的所有 cos^2 值,以查看它们之间的差异。另一种选择是查看 cos^2(x + dx) - cos^2(x) 的泰勒展开式,它会告诉您错误项。
  • 我没有完全理解它,但是预先计算它基本上不会将它减少到笛卡尔距离吗?我已经有一段时间没有做任何几何了,你能解释一下泰勒展开吗? -- 顺便说一句,我为 cos^2 的所有值预先计算了一个查找数组,完整的算法需要 45 秒,算法本身大约需要 15 秒,距离计算大约需要 30 秒
  • 如果经度都相对接近(意思是 (Lat2+Lat1)/2 永远不会有太大变化),我建议对 cos^2 项取一个近似值。对于您计算的每个距离,可能值得打印精确距离、近似距离和 ((exactDistance - approxDistance)/exactDistance)*100,这将告诉您 cos^2 近似值有多“糟糕”。还值得注意的是,地球不是一个完美的球体,所以无论如何这都是近似的。我认为泰勒展开式可能有点矫枉过正,但它是一种将任何函数表示为多项式的方法(参见 Wikipedia)...
猜你喜欢
  • 2010-11-03
  • 2021-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多