7、位置质量:Accuracy精度概念、速度与方位计算、海拔与地磁场数据获取

各位同学,今天我们来聊聊位置质量这个话题。说实话,很多人拿到经纬度就觉得万事大吉了,但实际开发中,位置数据的质量才是真正决定应用体验的关键。我见过太多App因为忽略了精度问题,导致导航路线飘到河里去,或者运动轨迹画成了心电图。

7.1 精度(Accuracy)—— 别被数字骗了

先问大家一个问题:你手机显示“精度10米”,真的意味着你就在那个10米圆圈里吗?

嗯,不一定。Android的精度值是一个概率估计,通常表示有68%的概率你的真实位置在以该点为圆心、精度值为半径的圆内。说白了,这是一个统计学概念,不是绝对边界。

核心要点:
  • getAccuracy() 返回水平精度(米),越小越好
  • 精度值 ≤ 20米:适合导航、运动追踪
  • 精度值 20~100米:适合签到、天气
  • 精度值 > 100米:只适合粗略定位(城市级别)

我在项目中遇到过一个问题:某款户外App在山区经常出现精度值突然跳到500米的情况。排查后发现,是因为GPS信号被山谷遮挡,系统自动切换到了基站定位。所以,不要盲目信任精度值,要结合场景做过滤。

我的习惯:在获取位置后,先判断 accuracy 是否满足业务需求。比如导航场景,我一般要求 accuracy ≤ 30米,否则提示用户等待或切换到手动输入。

7.2 速度与方位计算 —— 别自己算,有现成的

很多新手喜欢自己算速度:拿两个位置的距离除以时间差。你想想看,这有多不靠谱?GPS信号抖动一下,算出来的速度能飙到200km/h。

Android Location 对象已经提供了两个现成的方法:

// 获取速度(米/秒)
float speed = location.getSpeed();

// 获取方位角(度,0=北,90=东,180=南,270=西)
float bearing = location.getBearing();

这两个值是由GPS芯片直接计算出来的,内部做了平滑滤波,比你自己算靠谱得多。我曾经在骑行App里对比过,自己算的速度波动极大,而 getSpeed() 返回的值非常稳定。

注意:
  • getSpeed() 只在 GPS 定位时有效,网络定位通常返回 0
  • getBearing() 需要设备在运动中才有意义,静止时返回 0
  • 如果设备没有移动,bearing 值会乱跳,建议结合 speed 判断

那如果我想自己算方位呢?Android 也提供了工具类:

float[] results = new float[3];
Location.distanceBetween(
    startLat, startLng,   // 起点
    endLat, endLng,       // 终点
    results
);
// results[0] = 距离(米)
// results[1] = 初始方位角(度)
// results[2] = 最终方位角(度)

这个方法用的是WGS84椭球模型,精度很高。我在做轨迹回放功能时就用它来计算每两个点之间的方向变化。

7.3 海拔高度 —— 别太当真

海拔数据是个坑。真的,我踩过。

Android 提供了 getAltitude() 方法,但它的精度……嗯,怎么说呢,看运气。

定位方式 海拔精度 说明
GPS ±10~50米 受卫星几何分布影响大
GPS + 气压计 ±1~3米 需要设备硬件支持
网络定位 不可用 通常返回0或海平面高度

我曾经做一个登山App,用户反馈海拔数据忽高忽低,明明在爬坡,海拔却往下掉。后来发现是GPS信号反射导致的误差。解决方案是:对海拔数据做滑动平均滤波,取最近5~10个点的平均值。

避坑指南:如果你需要精确海拔(比如航空、测绘),建议使用气压计。Android 提供了 SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) 方法,配合校准后的海平面气压,精度可以做到1米以内。

7.4 地磁场数据 —— 指南针的真相

地磁场数据主要用于电子罗盘方位校正。Android 通过 Sensor.TYPE_MAGNETIC_FIELD 提供原始磁场数据,单位是微特斯拉(μT)。

但这里有个问题:地磁场很弱,很容易被干扰。手机扬声器、马达、甚至你手上的金属手表都会影响读数。

// 获取地磁场传感器
SensorManager sensorManager = getSystemService(SensorManager.class);
Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

// 在 onSensorChanged 中获取数据
@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
        float x = event.values[0]; // 地磁场X分量
        float y = event.values[1]; // 地磁场Y分量
        float z = event.values[2]; // 地磁场Z分量
        float total = (float) Math.sqrt(x*x + y*y + z*z);
        // total 正常范围:25~65 μT(视纬度而定)
    }
}

要得到真正的方位角,需要结合加速度传感器做融合计算。Android 提供了 SensorManager.getRotationMatrix()SensorManager.getOrientation() 方法,帮你完成这个复杂计算。

我的经验:地磁场数据需要做低通滤波。原始数据噪声很大,直接使用会导致指南针疯狂抖动。我一般用指数加权移动平均(EWMA)来处理:
filteredValue = alpha * rawValue + (1 - alpha) * filteredValue;
// alpha 通常取 0.1~0.3

7.5 知识体系总览

下面这张图总结了本章的核心知识点,帮你理清思路:

位置质量 精度 (Accuracy) getAccuracy() 概率估计,68%置信区间 GPS优于网络定位 速度 & 方位 getSpeed() / getBearing() GPS芯片内部滤波 distanceBetween() 工具 海拔 (Altitude) getAltitude() GPS精度±10~50米 气压计精度±1~3米 地磁场 TYPE_MAGNETIC_FIELD 需融合加速度传感器 低通滤波去噪声 核心原则 不要盲目信任原始数据 → 结合场景做过滤和滤波 → 必要时融合多传感器

7.6 实战建议

最后,给大家几个我在实际项目中总结的经验:

  1. 精度过滤是第一步:拿到位置后,先判断 accuracy 是否满足需求,不满足就丢弃或提示用户
  2. 速度数据用现成的:别自己算,getSpeed() 已经做了平滑处理
  3. 海拔数据要滤波:滑动平均或卡尔曼滤波都能有效减少抖动
  4. 地磁场要融合:单独用磁场传感器做指南针,结果会让你崩溃
  5. 测试要覆盖不同场景:室内、室外、高楼间、山谷里,精度表现天差地别
一个小技巧:在做位置相关的App时,我习惯在调试界面显示所有原始数据(精度、速度、方位、海拔、卫星数)。这样在测试时能快速定位问题,到底是GPS信号不好,还是代码逻辑有bug。

好了,关于位置质量的内容就讲到这里。记住一句话:拿到位置只是开始,理解质量才是关键。下一章我们会聊如何在不同场景下选择合适的定位策略,到时候见。


公众号:蓝海资料掘金营,微信deep3321