【问题标题】:Simplification / optimization of GPS trackGPS轨迹的简化/优化
【发布时间】:2011-05-27 16:43:41
【问题描述】:

我有一个由gpxlogger(1) 制作的 GPS 轨迹(作为gpsd 的客户端提供)。 GPS 接收器每 1 秒更新一次坐标,gpxlogger 的逻辑非常简单,它会记下位置 (lat, lon, ele) 和一个时间戳 (time),每隔 n em> 秒(在我的例子中 n = 3)。

在写下几个小时的轨迹后,gpxlogger 保存了几兆字节长的 GPX 文件,其中包含数千个点。之后,我尝试在地图上绘制这条轨迹并将其与OpenLayers 一起使用。它有效,但数千个点使使用地图变得草率而缓慢。

我知道有几千个次优点。有无数的点可以删除而几乎不会丢失任何东西:当有几个点大致组成直线并且我们在它们之间以相同的恒定速度移动时,我们可以离开第一点和最后一点,其他的都扔掉。

我曾想过使用gpsbabel 进行此类轨道简化/优化工作,但是,唉,它的simplification filter 仅适用于路线,即仅分析路径的几何形状,没有时间戳(即不检查速度是否大致常数)。

是否有一些现成的实用程序/库/算法可用于优化曲目?或者我可能错过了 gpsbabel 的一些聪明选择?

【问题讨论】:

    标签: optimization gps simplify gpx


    【解决方案1】:

    是的,如前所述,Douglas-Peucker 算法是简化 2D 连接路径的直接方法。但正如您所指出的,您需要将其扩展到 3D 案例,以正确简化 GPS 轨迹,其中包含与每个点相关的固有时间维度。我已经使用 Douglas-Peucker 的 PHP 实现为我自己的 Web 应用程序这样做了。

    只要稍微了解一下算法的工作原理,就很容易将算法扩展到 3D 案例。假设您的输入路径由标记为 A 到 Z 的 26 个点组成。这条路径的最简单版本有两个点,A 和 Z,所以我们从那里开始。想象一下 A 和 Z 之间的线段。现在扫描所有剩余的点 B 到 Y 以找到离线段 AZ 最远的点。假设最远的点是 J。然后,您扫描 B 和 I 之间的点以找到离线段 AJ 最远的点,并扫描点 K 到 Y 以找到离线段 JZ 最远的点,依此类推,直到剩下的点都位于某个所需的距离阈值内。

    这将需要一些简单的向量操作。从逻辑上讲,3D 中的过程与 2D 中的过程相同。如果您发现用您的语言实现的 Douglas-Peucker 算法,它可能已经实现了一些 2D 矢量数学,您需要扩展它们以使用 3 维。

    您可以在此处找到 3D C++ 实现:3D Douglas-Peucker in C++

    您的 x 和 y 坐标可能以纬度/经度为单位,而 z(时间)坐标可能以 unix 纪元以来的秒数为单位。您可以通过确定适当的时空关系来解决这种差异;假设您想在 1 平方英里的地图区域内查看一天的活动。将此关系想象为 1 英里 x 1 英里 x 1 天的立方体,您必须预先调整时间变量。从度数到表面距离的转换并非易事,但对于这种情况,我们简化并说 1 度是 60 英里;那么一英里是0.0167度。一天是86400秒;那么为了使单位相等,我们为您的时间戳提供的预分频因子是 0.0167/86400,或大约 1/5,000,000。

    例如,如果您想在 2 天内查看同一 1 平方英里地图区域内的 GPS 活动,则时间分辨率的重要性将降低一半,因此将其进一步缩小两倍,至 1/10,000,000。玩得开心。

    【讨论】:

    【解决方案2】:

    看看Ramer-Douglas-Peucker 平滑复杂多边形的算法,还有Douglas-Peucker 线简化算法可以帮助您减少点数。

    【讨论】:

    • 谢谢,我想应该已经有一些可用的算法了,只是错过了!我在这里唯一需要的是在 3D 空间(即 ''(x, y, t)'' 坐标)中调整该算法并以某种方式在这样的空间中定义“正交距离”,这是对的吗?
    • 是的,您需要以某种方式映射time 参数:)
    【解决方案3】:

    开源 GeoKarambola java 库(无 Android 依赖项,但可在 Android 中使用),其中包含一个 GpxPathManipulator 类,该类可进行路线和轨道简化/缩减(3D/海拔感知)。 如果这些点有不会被丢弃的时间戳信息。 https://sourceforge.net/projects/geokarambola/

    这是交互的算法 https://lh3.googleusercontent.com/-hvHFyZfcY58/Vsye7nVrmiI/AAAAAAAAHdg/2-NFVfofbd4ShZcvtyCDpi2vXoYkZVFlQ/w360-h640-no/movie360x640_05_82_05.gif

    该算法基于通过消除具有最大 XTD(跨轨道距离)误差的点来减少点数,直到满足容许误差或达到最大点数(函数的两个参数),无论先到先得。

    另一种算法,对于像轨道简化(我称之为“streamplification”)这样的实时流是: 您保留 GPS 传感器给您的点的小缓冲区,每次将 GPS 点添加到缓冲区(包括高度)时,您计算缓冲区中所有点到线段的最大 XTD(交叉轨道距离)将第一个点与缓冲区的(新添加的)最后一个点结合起来。如果具有最大 XTD 的点违反了您的最大容忍 XTD 误差(25m 给了我很好的结果),那么您在该点切割缓冲区,将其注册为要附加到流放大轨道的选定点,修剪尾随部分缓冲到那个切点,然后继续。在轨道的末端,缓冲区的最后一个点也被添加/刷新到溶液中。 该算法足够轻巧,可以在 AndroidWear 智能手表上运行,无论您是慢动作还是快动作,或者长时间闲置在同一个地方,都能提供最佳输出。唯一重要的是你的赛道的形状。您可以走很多分钟/公里,只要您沿直线移动(在 +/- 允许的 XTD 误差偏差范围内的走廊),streamplify 算法将仅输出 2 个点:出口形成最后一条曲线和入口的点在下一条曲线上。

    【讨论】:

      【解决方案4】:

      我遇到了类似的问题。 gps 单元获得分数的速率比所需的要大得多。许多点在地理上并不相距很远。我采用的方法是使用半正弦公式计算点之间的距离。如果距离不大于我的阈值(在我的情况下为 0.1 英里),我会丢弃该点。这很快将点数减少到可管理的大小。

      我不知道你在寻找什么语言。这是我正在处理的一个 C# 项目。在底部你会找到haversine代码。

      http://blog.bobcravens.com/2010/09/gps-using-the-netduino/

      希望这能让你继续前进。

      鲍勃

      【讨论】:

      • 谢谢,但你的例子比我需要的简单得多。如果我正确理解您的代码,您只需过滤掉与前一个距离与给定距离相差不大的点。我想进一步简化,通过检查我们是否在一段时间内保持相同的恒定速度。最后但并非最不重要的一点是,您使用的是 Haversine 公式,而最好使用 Vincenty's formula - OpenLayers 包括该公式的实现。无论如何感谢您的回答:)
      • @GreyCat 为什么使用 Vicinity 的公式比使用 Haversine 更好?
      • Haversine 的准确度要低得多:它假设行星是球体,而 Vincenty 假设是扁球体,这更接近地球的真实情况。
      • @GreyCat 准确度几乎与此上下文无关。当您首先已经丢弃大部分数据时,您不需要准确性:) 在我们谈论的用于简化 GPS 轨迹的微小距离(不到 1 英里)上,如果该点是在决定是否需要跟踪点时几英尺? Haversine 更快...
      • 是的,但是如果我们回到原始问题的上下文,我们最终不会使用它们中的任何一个 - 事实上,在 Douglas-Peucker 3D 算法中只使用多边形坐标 + 时间产生了很好的结果。
      【解决方案5】:

      这可能是 NP 难的。假设您有 A、B、C、D、E 点。

      让我们尝试一个简单的确定性算法。假设您计算从 B 点到 A-C 线的距离,并且它小于您的阈值(1 米)。所以你删除了 B。然后你尝试将 C 与 A-D 行相同,但它更大,而 C-E 的 D 也更大。

      但事实证明最优解是 A、B、E,因为点 C 和 D 靠近 B-E 线,但在相反的两侧。

      如果你删除 1 个点,你不能确定它应该是你应该保留的点,除非你尝试所有可能的解决方案(大小可以是 n^n,等等n=80 这比已知宇宙中的最小原子数还多)。

      下一步:尝试 brute force 或 branch and bound 算法。无法扩展,不适用于实际大小。你可以放心地跳过这一步:)

      下一步:首先执行 确定性 算法,然后使用 元启发式 算法(禁忌搜索、模拟退火、遗传算法)对其进行改进。在java中有几个开源实现,比如Drools Planner。

      总而言之,使用第一个简单的确定性算法,您可能会得到一个可行的解决方案(尽管不是最优的),因为您只有一个约束。

      这个问题的远亲可能是旅行推销员问题变体,其中推销员不能访问所有城市,而必须选择一些城市。

      【讨论】:

      • 如果我们努力找到一个真正最优的解决方案(应该留下给定的目标点数),那么是的,它可能是 NP 难的,但是“足够好”的解决方案很好为了我。基本上,它归结为在使用直线段组成的 ''(x, y, t)'' 的 3D 空间中找到一条线的足够好的近似值。
      【解决方案6】:

      你想扔掉无趣的点。所以你需要一个函数来计算一个点有多有趣,然后你可以计算所有点有多有趣,然后扔掉N个最不感兴趣的点,你选择N充分精简数据集。听起来您对有趣的定义对应于高加速度(偏离直线运动),这很容易计算。

      【讨论】:

      • 我不知道我的目标点数先验。但一般来说,是的,相当简单的解决方案是对每个点的有趣程度有一个简单的度量(最好基于它之前的''k''点和之后的''k''点的滑动窗口) .你知道这个指标可能是什么样子吗?我已经做了一些实验,但还是想不出一个?
      • 设兴趣点为x_2,上一个点为x_1,下一个点为x_3。让您在这些时间点的时间为 t_1、t_2、t_3。那么该点之前的速度是 v_12 = (x_2 - x_1) / (t_2 - t_1),之后的速度是 v_23 = (x_3 - x_2) / (t_3 - t_2)。加速度矢量为 a = (v_23 - v_13) / (t_3 - t_1)。您可能想要使用该向量的大小 |a|。
      • 这将是瞬时加速度的近似值,而不是滑动窗口中的加权平均加速度。我不确定这会奏效 - 即它会准确显示不感兴趣的点,但保证所有有趣的点都会保留。
      【解决方案7】:

      试试这个,它是免费的开源在线服务:

      https://opengeo.tech/maps/gpx-simplify-optimizer/

      【讨论】:

      • 您能解释一下我在哪里可以找到该服务的来源吗?
      • 啊,原来是 RDP 实现。谢谢!
      • 是的...如果你想尝试其他算法,请 fork github repo 代码!
      【解决方案8】:

      我想你需要在改变方向的地方保留点。如果你将你的轨迹分割成一组恒定方向的区间,你可以只留下这些区间的边界点。
      而且,正如 Raedwald 指出的那样,您需要留下加速度不为零的点。

      【讨论】:

        【解决方案9】:

        不确定这将如何运作,但如何获取点列表,计算它们之间的距离以及路线的总距离,然后确定分辨率距离,然后根据线性插值位置每步 x 米。即,对于每个修复,您都有一个“距起点的距离”度量,您只需插入整个路线的 n*x 所在的位置。 (你可以决定你想要多少点并将总距离除以得到你的分辨率距离)。最重要的是,您可以添加一个窗口函数,可能取当前点 +/- z 点并应用类似 exp(-k* dist^2/accuracy^2) 的权重来获得 dist 的一组点的加权平均值是与原始插值点的距离,accuracy 是 GPS 位置的假定精度。

        【讨论】:

          【解决方案10】:

          一种非常简单的方法是重复删除在其邻居之间创建最大角度的点(在 0° 到 180° 的范围内,其中 180° 表示它在其邻居之间的直线上),直到您的点数足够少.这将从删除所有与其邻居完全一致的点开始,并将从那里开始。

          您可以在Ο(n log(n))中做到这一点,方法是列出每个索引及其角度,按角度降序对该列表进行排序,从列表的前面保留您需要的数量,然后排序按索引降序排列的较短列表,并从点列表中删除索引。

          def simplify_points(points, how_many_points_to_remove)
            angle_map = Array.new
            (2..points.length - 1).each { |next_index|
              removal_list.add([next_index - 1, angle_between(points[next_index - 2], points[next_index - 1], points[next_index])])
            }
            removal_list = removal_list.sort_by { |index, angle| angle }.reverse
            removal_list = removal_list.first(how_many_points_to_remove)
            removal_list = removal_list.sort_by { |index, angle| index }.reverse
            removal_list.each { |index| points.delete_at(index) }
            return points
          end
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-08-04
            • 2015-02-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多