地图类程序前端——使用阈值和噪声过滤算法处理平滑 UI

地图类程序前端——使用阈值和噪声过滤算法处理平滑 UI

三葉Leaves Author

本文简要记录了我在解决某一款企业级地理信息类微信小程序时候,处理定位点随机抖动的过程。由于 GPS 精度、信号强度等各种不可控因素,采集到的经纬度信息往往会产生大量偏差造成抖动,我用阈值和 OEF 算法有效解决了这个问题。

Discard 逻辑:异常漂移点过滤

如果要考虑竞赛性质的反作弊等,该逻辑不能只做在前端

该过滤逻辑仅是在前端提前粗过滤,为了减轻传输量、优化实时 UI 体验,但是如果要兼顾反作弊、异常监测等需求,后端也应该做出类似的逻辑来决定坐标点是否录入。

我设计了三层阈值,简要概括为:

  • 速度不能超过人类越野跑速度阈值 6 m/s
  • 加速度不能超过人类的瞬时加速度阈值 4 m/s²
  • 最低 10 m 阈值确保不会因为 GPS 抖动而大量上传。

设定阈值:

1
2
3
4
5
6
7
8
9
10
11
12
uploadGeometry: function(currentLocation,allCheckedIn) {

...

const MAX_SPEED = 6; // m/s,人类越野跑速度阈值

    const MAX_ACCEL = 4; // m/s²,瞬时加速度阈值

    const DIST_THRESHOLD = 10; // m,位移阈值

...
}

计算速度和加速度的核心算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 计算时间差、速度、加速度

      const timeStepMs = currentTime - lastBasicPoint.time;

      const timeStepSeconds = timeStepMs / 1000;

      // 避免除零

      if(timeStepSeconds <= 0){

        return; // 时间未前进,丢弃

      }

      const speedStep = twoPointsDistance / timeStepSeconds; // m/s

      const acceleration = Math.abs(speedStep - lastBasicPoint.speed) / timeStepSeconds; // m/s²

      // 判定该点是否有效

      const isValidPoint = (twoPointsDistance >= DIST_THRESHOLD) &&

                           (speedStep <= MAX_SPEED) &&

                           (acceleration <= MAX_ACCEL);

      // 若满足条件或已全部打卡,记录并上传

      if(isValidPoint || allCheckedIn){

        console.log("上传有效坐标", {twoPointsDistance, speedStep, acceleration});

OEF 算法:消锯齿平滑处理

刚才的速度/加速度阈值只能“剔除离谱点”,但剩下的坐标虽然在合理阈值内, GPS 的随机抖动仍然会让线路锯齿、距离累计偏大。

我在网上找到了两种算法,最终选择了 OEF 算法处理。

OEF 算法核心实现可参考:Noise Filtering Using 1€ Filter | Jaan Tollander de Balsch

OEF 算法官网给的示意图
OEF 算法官网给的示意图

使用卡尔曼滤波 / OEF(One Euro Filter) 在前端平滑 UI 曲线

  • 卡尔曼滤波算法如果想在前端纯手写,动辄需要几十行矩阵式推导,虽然可以引用第三方库,但是对于微信小程序来说整合也麻烦。不过值得一提的是,后端的再次校验时可以采用这种算法,来配合前端达到更好效果。
  • OEF 算法更轻量,计算速度更具实时性, 适合移动端小程序环境。所以我在前端写了一个工具类实现这个算法。

我创建的工具类,放在了 utils\one-euro-filter.js,算法核心如下:

1
2
3
4
5
6
7
8
9
10
11
function alphaCalc(cutoff,freq){ ... }          // 根据截止频率求 α
...
filter(x,timestamp){
dt = (t−lastTime) , freq=1/dt // 实时采样频率
dx = (x−lastValue)*freq // 原始速度
αd = alphaCalc(dCutoff,freq) // 导数滤波系数
deriv = αd*dx + (1−αd)*deriv // 平滑后的速度
cutoff = minCutoff + β*|deriv| // 自适应截止频率
α = alphaCalc(cutoff,freq) // 主滤波系数
filtered = α*x + (1−α)*lastValue // 输出新位置更新状态并返回
}

与上文的 discard 逻辑的协同

OEF 在 10 m 内随机抖动 场景下生效:

• 抖动 → 平滑后基本被“压扁”,距离累积不再反复 +1 m、-1 m

• 真正的大位移 (>10 m) 仍会输出明显变化,被后面 uploadGeometry 捕获并进入速度/加速度阈值判定(仍能过滤“飞跃点”)。

最终效果

每只滤波器内部会保存 lastTime / lastValue / deriv(速度的一阶导),随后对每次输入做自适应计算。

  • UI 轨迹线更顺滑、不锯齿

  • 距离累积趋于真实(不被高频噪声放大)

  • 不引入明显延迟(每次计算只需常数级乘加运算)

参数调节

alphaCalc(cutoff, freq)

  • minCutoff 控制静态/慢速时的平滑强度;
  • freq 会影响beta ,beta 越大,响应越灵敏(平滑程度越低,延迟越小)。

现在用 1.0 / 0.0 做保守平滑,可根据体验提高 beta0.005-0.02 使转弯曲线更“跟手”。

onLoad 函数里:

1
2
3
4
5
    // 初始化定位平滑滤波器(freq 无需指定,会根据每次采样动态计算)

    this.latFilter = new OneEuroFilter(1.0, 0.0); // minCutoff=1, beta=0 ->可按需要调参

    this.lonFilter = new OneEuroFilter(1.0, 0.0);

实测验证

下面两张图分别是优化前和优化后的相同路线下的测试。

优化前
优化前

算法优化后
算法优化后

  • 标题: 地图类程序前端——使用阈值和噪声过滤算法处理平滑 UI
  • 作者: 三葉Leaves
  • 创建于 : 2025-07-10 00:00:00
  • 更新于 : 2025-07-10 13:51:54
  • 链接: https://blog.oksanye.com/c147aa44710c/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论