【Flink 轨迹实战】看懂距离、速度过滤代码:为什么要用 Haversine?抖动点和异常点怎么判断?
本文介绍了轨迹数据处理中的关键过滤方法,主要包括:1)通过Haversine公式计算两点间距离;2)基于时间差计算移动速度;3)设置最小移动距离阈值过滤GPS抖动点;4)设置最大速度阈值过滤异常点。文章详细解析了时间差计算、球面距离算法、抖动点识别和异常速度判断的实现逻辑,并提供了典型场景的速度阈值建议。这些过滤方法能有效消除GPS漂移和设备异常导致的轨迹失真,确保数据符合物理常识,为下游分析提供
·
文章目录
在做轨迹处理(GPS / 车辆 / 无人机 / 人员定位)时,
- 经纬度算距离
- 用时间算速度
- 小距离丢弃
- 大速度丢弃
一、核心目的
判断一个定位点是否“符合物理常识”:
如果几乎没动 → 抖动点
如果快得不可能 → 异常点
都直接丢掉
二、完整代码片段
// 1) 时间阈值:太密集就不写 buffer(减少下游点数)
Long le = lastEmitTs.value();
if (le != null && p.ts - le < cfg.minEmitIntervalMs) {
return;
}
// 2) 距离/速度阈值:基于 lastPoint 计算两点间距离与速度
Double derivedSpeed = null;
if (lp != null) {
long dtMs = max(1, p.ts - lp.ts); // 避免除 0
double distM = haversineMeters(lp.lat, lp.lon, p.lat, p.lon);
// 抖动点:位移过小(常见于 GPS 漂移/静止抖动)
if (distM < cfg.minMoveMeters) {
incJitterDrop();
return;
}
// 推导速度:米/秒
derivedSpeed = distM / (dtMs / 1000.0);
// 异常速度:大概率是定位跳点或异常数据
// 当前策略:直接丢弃(也可以改成“切段”或“保留但打标”)
if (derivedSpeed > cfg.maxSpeedMps) {
incSpeedDrop();
return;
}
}
/**
* Haversine 公式计算球面两点距离(单位:米)
* - R=6371000m:地球平均半径
* - 适合轨迹点距离计算(GPS)
*/
private static double haversineMeters(double lat1, double lon1, double lat2, double lon2) {
double R = 6371000.0;
double dLat = toRadians(lat2 - lat1);
double dLon = toRadians(lon2 - lon1);
double a = sin(dLat/2)*sin(dLat/2) +
cos(toRadians(lat1))*cos(toRadians(lat2)) *
sin(dLon/2)*sin(dLon/2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
return R * c;
}
三、先搞清楚:这两个点是谁?
LastPoint lp = lastPoint.value(); // 上一个“已接受”的点
RawPoint p = 当前新来的点;
你可以把它理解成:
lp(上一个点) ─────────▶ p(当前点)
所有计算,都是围绕 “这两个点之间发生了什么”。
四、第一步:算时间差(dtMs)
long dtMs = max(1, p.ts - lp.ts);
这一步在干什么?
p.ts - lp.ts:两个点之间隔了多久(毫秒)max(1, …):防止除 0(非常重要)
举例
lp.ts = 10000 ms
p.ts = 10050 ms
dtMs = 50 ms
如果两个点时间戳相同:
dtMs = 1 ms(强行兜底)
五、第二步:算距离(distM)——为什么要用 Haversine?
double distM = haversineMeters(lp.lat, lp.lon, p.lat, p.lon);
这一步的本质
根据两个经纬度,计算它们在“地球表面”的真实距离(米)
Haversine 是什么?(不用怕)
private static double haversineMeters(...)
这是 GPS / 地图 / 轨迹领域的标准算法,特点是:
- ✅ 考虑地球是球体
- ✅ 精度足够(米级)
- ✅ 被广泛使用
你可以把它当成一个“官方认证的算距离工具”,
不用关心数学推导,只管用。
举个能理解的例子
lp: (39.900000, 116.400000)
p : (39.900010, 116.400010)
大约相当于:
→ 移动了 1~2 米
那么:
distM ≈ 1.5
六、第三步:抖动点判断(为什么距离太小要丢?)
if (distM < cfg.minMoveMeters) {
incJitterDrop();
return;
}
什么是抖动点?
现实中的 GPS:
- 人 / 车 / 无人机 静止
- 经纬度却会在 ±1~3 米 范围内乱跳
如果不处理,轨迹会变成:
原地抖成一团毛线
举例说明
minMoveMeters = 2 米
distM = 0.6 米
👉 判断为 GPS 漂移,不是真实移动 → 丢弃
七、第四步:算推导速度(derivedSpeed)
derivedSpeed = distM / (dtMs / 1000.0);
拆开看:
dtMs / 1000.0 → 秒
distM / 秒 → 米/秒
举例(非常直观)
distM = 10 米
dtMs = 1000 ms = 1 秒
derivedSpeed = 10 m/s
八、第五步:异常速度判断(为什么速度太大要丢?)
if (derivedSpeed > cfg.maxSpeedMps) {
incSpeedDrop();
return;
}
这一步在防什么?
防这种情况:
上一点:北京
下一点:上海
时间差:1 秒
算出来的速度是:
1000+ 公里 / 秒 ❌
这种情况通常是:
- GPS 跳点
- 数据错位
- 经纬度异常
- 设备故障
maxSpeedMps 一般怎么设?
| 场景 | 合理范围(m/s) |
|---|---|
| 行人 | 3 ~ 5 |
| 自行车 | 8 ~ 12 |
| 汽车 | 30 ~ 60 |
| 无人机 | 50 ~ 100 |
九、总结
如果有上一个点:
1. 算这两个点隔了多久
2. 算这两个点隔了多远
3. 如果几乎没动 → GPS 抖动 → 丢
4. 算实际速度
5. 如果快得不可能 → 异常 → 丢
否则:
这个点是正常的
如果你 不做这层过滤,结果通常是:
- 轨迹乱抖
- 偶尔飞线几百公里
- 地图前端缩放直接炸
- 下游分析严重失真
本质原因是:
真实世界 ≠ 传感器世界
传感器数据必须经过“物理常识过滤”
更多推荐
所有评论(0)