我的数据流来自连接到GPS设备和测斜仪的程序(它们实际上是独立设备,而不是手机),并且当用户在车内行驶时记录数据。我收到的基本数据是:
此数据存储在数据库中,并从数据库读回到数组中。从开始到结束,记录顺序得到适当维护,因此即使从GPS设备记录的时间戳仅为1秒精度,我们在5hz采样,时间的绝对值也没有兴趣和插入顺序就足够了。
为了帮助分析数据,用户执行一个非常基本的数据输入任务,即选择" start"和"结束"来自收集的路径数据的道路上的曲线。我从Google获得了地图图像,并在其上绘制了曲线数据。用户根据他们自己对该区域的了解放大感兴趣的曲线,并点击地图上的两个点。谷歌实际上非常好,并报告用户点击纬度/经度而不是我必须尝试从像素值回溯它的位置,因此用户点击了与数据相关的位置的问题。
放大曲线会剪切数据:我只检索落在缩放级别定义的Lat / Lng窗口中的数据。大多数情况下,当一次驾驶会话可能导致超过10万个数据点时,我处理的数据点少于300个。
我需要找到落在点击点之间的曲线数据的子段。
最初,我采用了最接近每个点击点的两个点,曲线是它们之间的任何东西。这一直有效,直到我们开始让司机在路上多次通过。通常情况下,驾驶员将在一条有趣的道路上进行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;
}
这很有效,直到我们开始建议驾驶员通过转弯行驶15MPH左右(据说,它有助于减少传感器误差。我个人并不完全相信我们在更高的速度下看到的是错误,但是我可能不会赢得那个论点)。以15MPH行驶的汽车以22fps的速度行驶。以5hz采样这些数据意味着每个数据点相距约4.5英尺。但是,我们的GPS装置的精度仅为5英尺左右。因此,只有GPS数据本身的抖动才能在如此低的速度和高采样率下导致数据的拐点(从技术上讲,在此采样率下,您必须至少达到35MPH以避免此问题,但是在实践中它似乎在25MPH下工作正常。)
此外,我们很快就会将采样率提高到10 - 15 Hz。您需要以大约45MPH的速度行驶以避免我的拐点问题,这对大多数感兴趣的曲线都不安全。我目前的程序最终将数据分成几十个子分段,在我知道只有4遍的路段上。只有 300个数据点的一个部分出现在35个子分段中。每次传球的开始和结束的指示(一个小图标)的渲染非常清楚地表明每个真正的传球被切成几块。
但我曾经尝试过这次尝试过,但效果并不好。如果用户没有特别靠近他们想要的位置点击,则步骤#2可以返回不合理的大量点数。如果用户非常点击,特别是接近他们想要的位置,它可以返回太少的点。我不确定步骤#3的计算密集程度。如果驾驶员驾驶特别长的弯道并且在开始和结束之后立即转弯以执行随后的通过,则步骤#5将失败。我们或许可以训练司机不要这样做,但我不想在这些事情上冒险。因此,我可以使用一些帮助来弄清楚如何剪切和分组这条路径,将其自身翻倍,进入子段以进行曲线传递。
答案 0 :(得分: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;
}