【发布时间】: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 步将失败。我们也许可以训练车手不要这样做,但我不喜欢在这些事情上冒险。因此,我可以使用一些帮助来确定如何剪辑和分组这条路径,该路径将自身翻倍成子段,以便通过曲线。
【问题讨论】: