分割 GPS 路径数据
Posted
技术标签:
【中文标题】分割 GPS 路径数据【英文标题】:Segmenting GPS path data 【发布时间】:2013-09-05 20:37:04 【问题描述】:我的问题
我有一个来自程序的数据流,该程序连接到 GPS 设备和倾角仪(它们实际上都是独立设备,而不是手机),并在用户开车时记录数据。我收到的基本数据是:
纬度/经度 - 来自 GPS,分辨率约为 +-5 英尺, 车辆陆地速度 - 来自 GPS,以节为单位,我将其转换为 MPH 顺序记录索引 - 来自数据库,它是一个自动递增的整数,不会删除任何内容, 其他一些与我当前的问题无关的内容。此数据存储在数据库中,然后从数据库读回数组。从头到尾,记录的顺序是正确维护的,所以即使从 GPS 设备记录的时间戳只有 1 秒的精度,我们以 5hz 采样,时间的绝对值和插入顺序无关紧要够了。
为了帮助分析数据,用户执行一个非常基本的数据输入任务,即从收集的路径数据中选择道路上弯道的“起点”和“终点”。我从 Google 获得了一张地图图像,并在其上绘制了曲线数据。用户根据自己对该区域的了解放大感兴趣的曲线,然后单击地图上的两个点。 Google 实际上非常好,它会报告用户在纬度/经度中单击的位置,而不是我不得不尝试从像素值中回溯它,因此涵盖了与数据相关的用户单击位置的问题。
曲线上的放大会裁剪数据:我只检索落在由缩放级别定义的 Lat/Lng 窗口中的数据。大多数时候,我处理的数据点少于 300 个,而单次驾驶会话可能会产生超过 100k 的数据点。
我需要找到位于点击点之间的曲线数据的子段。
我的尝试
最初,我取离每个点击点最近的两个点,曲线是落在它们之间的任何东西。这一直有效,直到我们开始让司机多次通过道路。通常,一个司机会在一条有趣的道路上来回跑 2 次,总共给我们 4 次通行证。如果您将两个最接近的点与两个点击点相比较,那么您最终可能会得到第一个点对应于一个通道上的一个基准,而第二个点对应于一个完全不同的通道上的一个基准。这两个点之间的序列中的点将延伸到曲线之外。而且,即使你很幸运并且找到的所有数据点都在同一个通道上,那也只会给你一个通道,我们需要收集所有通道。
有一段时间,我有一个效果更好的解决方案。我计算了两个新序列,表示从每个数据点到每个点击点的距离,然后是该距离的近似二阶导数,寻找从点击点到数据点的距离的拐点。我推断拐点意味着拐点之前的点越来越接近点击点,拐点之后的点离点击点越来越远。在数据点上迭代地执行此操作,我可以在找到曲线时对它们进行分组。
也许一些代码是有序的(这是 C#,但不要担心实物回复,我能够阅读大多数语言):
static List<List<LatLngPoint>> GroupCurveSegments(List<LatLngPoint> dataPoints, LatLngPoint start, LatLngPoint end)
var withDistances = dataPoints.Select(p => new
ToStart = p.Distance(start),
ToEnd = p.Distance(end),
DataPoint = p
).ToArray();
var set = new List<List<LatLngPoint>>();
var currentSegment = new List<LatLngPoint>();
for (int i = 0; i < withDistances.Length - 2; ++i)
var a = withDistances[i];
var b = withDistances[i + 1];
var c = withDistances[i + 2];
// the edge of the map can clip the data, so the continuity of
// the data is not exactly mapped to the continuity of the array.
var ab = b.DataPoint.RecordID - a.DataPoint.RecordID;
var bc = c.DataPoint.RecordID - b.DataPoint.RecordID;
var inflectStart = Math.Sign(a.ToStart - b.ToStart) * Math.Sign(b.ToStart - c.ToStart);
var inflectEnd = Math.Sign(a.ToEnd - b.ToEnd) * Math.Sign(b.ToEnd - c.ToEnd);
// if we haven't started a segment yet and we aren't obviously between segments
if ((currentSegment.Count == 0 && (inflectStart == -1 || inflectEnd == -1)
// if we have started a segment but we haven't changed directions away from it
|| currentSegment.Count > 0 && (inflectStart == 1 && inflectEnd == 1))
// and we're continuous on the data collection path
&& ab == 1
&& bc == 1)
// extend the segment
currentSegment.Add(b.DataPoint);
else if (
// if we have a segment collected
currentSegment.Count > 0
// and we changed directions away from one of the points
&& (inflectStart == -1
|| inflectEnd == -1
// or we lost data continuity
|| ab > 1
|| bc > 1))
// clip the segment and start a new one
set.Add(currentSegment);
currentSegment = new List<LatLngPoint>();
return set;
在我们开始建议驾驶员以 15 英里/小时左右的速度转弯之前,这种方法效果很好(据说,这有助于减少传感器错误。我个人并不完全相信我们在更高速度下看到的是错误,但我可能不会赢得那个论点)。以 15MPH 行驶的汽车以 22fps 行驶。以 5hz 采样此数据意味着每个数据点相距大约四英尺半。然而,我们的 GPS 装置的精度只有 5 英尺左右。因此,在如此低的速度和高采样率下,仅 GPS 数据本身的抖动可能会导致数据出现拐点(从技术上讲,在这个采样率下,您必须至少达到 35MPH 才能避免这个问题,但它在实践中似乎以 25MPH 的速度运行良好)。
此外,我们可能很快就会将采样率提高到 10 - 15 Hz。你需要以大约 45MPH 的速度行驶以避免我的拐点问题,这在大多数感兴趣的曲线上是不安全的。我当前的程序最终将数据分成几十个子段,在我知道只有 4 个通道的路段上。只有 300 个数据点的部分出现在 35 个子段中。每个通道的开始和结束指示的渲染(一个小图标)非常清楚地表明每个真正的通道都被分割成几块。
我想去的地方
-
找出所有点到点击起点和终点的最小距离
查找距离该距离 +10 英尺范围内的所有点。
按数据连续性对每组点进行分组,即每组在数据库中应该是连续的,因为特定路径上的多个点可能落在距离半径内。
将每个点击点的每个组的数据中点作为每次传递的代表起点和终点。
将每个点击点的两个集合中的点与将每个“开始”和“结束”之间的记录索引距离最小化的点配对。
停下来?!
但我之前尝试过一次,但效果不佳。如果用户没有在他们想要的位置附近单击,则第 2 步可能会返回不合理的大量点。如果用户点击非常接近他们想要的位置,它可能会返回太少的点。我不确定第 3 步的计算密集程度如何。如果驾驶员要驶过特别长的弯道并在开始和结束后立即掉头以执行后续传球,则第 5 步将失败。我们也许可以训练车手不要这样做,但我不喜欢在这些事情上冒险。因此,我可以使用一些帮助来确定如何剪辑和分组这条路径,该路径将自身翻倍成子段,以便通过曲线。
【问题讨论】:
【参考方案1】:好的,这就是我最终做的事情,而且现在看起来效果很好。我喜欢它比以前更容易遵循。我认为我的问题中的第 4 步是不必要的。用作起点和终点的确切点并不重要,所以我只取第一个点击点所需半径内的第一个点和第二个点所需半径内的最后一个点,然后取中间的所有内容.
protected static List<List<T>> GroupCurveSegments<T>(List<T> dbpoints, LatLngPoint start, LatLngPoint end) where T : BBIDataPoint
var withDistances = dbpoints.Select(p => new
ToStart = p.Distance(start),
ToEnd = p.Distance(end),
DataPoint = p
).ToArray();
var minToStart = withDistances.Min(p => p.ToStart) + 10;
var minToEnd = withDistances.Min(p => p.ToEnd) + 10;
bool startFound = false,
endFound = false,
oldStartFound = false,
oldEndFound = false;
var set = new List<List<T>>();
var cur = new List<T>();
foreach(var a in withDistances)
// save the previous values, because they
// impact the future values.
oldStartFound = startFound;
oldEndFound = endFound;
startFound =
!oldStartFound && a.ToStart <= minToStart
|| oldStartFound && !oldEndFound
|| oldStartFound && oldEndFound
&& (a.ToStart <= minToStart || a.ToEnd <= minToEnd);
endFound =
!oldEndFound && a.ToEnd <= minToEnd
|| !oldStartFound && oldEndFound
|| oldStartFound && oldEndFound
&& (a.ToStart <= minToStart || a.ToEnd <= minToEnd);
if (startFound || endFound)
cur.Add(a.DataPoint);
else if (cur.Count > 0)
set.Add(cur);
cur = new List<T>();
// if a data stream ended near the end of the curve,
// then the loop will not have saved it the pass.
if (cur.Count > 0)
cur = new List<T>();
return set;
【讨论】:
是按顺序传递的dbpoints列表吗?就像即使司机多次通过同一路段一样,不同的通行证会在序列的后面吗?如果是这样,在一定距离内获得靠近起点的所有点的集合可能会更容易。然后删除一个点并迭代直到您的终点,并继续直到开始集为空。您是否也为 Navteq 工作? 重新阅读您的问题,它似乎按顺序存储在数据库中...既然是这种情况,我建议使用起点 5 英尺内的点。还有必要返回 List> 包含边界内传入的所有点吗?如果不是,我还建议丢弃不必要的点,即选择一个起点并迭代直到至少 5 英尺(或更多,取决于您的采样率)英尺远的下一个点,然后选择那个点,重复直到你有你的段。 嘿,不,我不为 Navteq 工作。是的,我确实需要边界点之间的所有点,因为它们还有与之相关的其他数据,我需要对其进行分析。 我确实发现这个方法有问题。在一般情况下,固定距离半径不起作用。测试车辆的行驶速度在 15 到 60 MPH 之间,这意味着每个样品的距离在 4.4 到 17.6 英尺之间。下限为较快的速度错过了点,而上限为较慢的速度占用了太多的点。相反,我计算出测试运行的最大速度,转换为每个样本的英尺,加上 20%,这就是我找到点的半径。这样一来,我也考虑了由于在不同车道或道路另一侧行驶而造成的差异。 在返回集合之前最后一次添加子段的检查实际上更可能返回垃圾,而不是遇到恰好在曲线上的测试运行结束。其他分析系统会阻止它被使用,但最好还是剪掉它。以上是关于分割 GPS 路径数据的主要内容,如果未能解决你的问题,请参考以下文章