坐标系与空间参考:WGS84、ENU、高度系统与坐标转换

做航点规划,第一个绕不开的问题就是:这个点到底在哪儿?

你想想看,无人机在天上飞,地面站屏幕上显示一个经纬度,飞控内部算的是米制偏移,高度还有两种说法。如果这些坐标系没理清楚,轻则航线偏个几十米,重则直接撞山。我早年做测绘无人机项目时就吃过这个亏——WGS84 和局部坐标搞混了,飞机差点飞到禁飞区里去。

这一章,咱们就把坐标系这件事彻底讲透。

1. WGS84 坐标系:全球统一的“经纬度语言”

WGS84 全称 World Geodetic System 1984。说白了,它是一个地心地图坐标系——地球被抽象成一个椭球体,原点在地球质心。

我们平时说的经纬度(Latitude, Longitude)就是 WGS84 的表达方式。纬度范围 -90° 到 +90°,经度范围 -180° 到 +180°。高度部分用的是椭球高(Ellipsoidal Height),这个后面会细说。

核心要点:WGS84 是 GPS 卫星使用的原生坐标系。你从 GPS 模块读到的原始数据,就是 WGS84 经纬度 + 椭球高。

举个例子,我常用的测试点坐标:

纬度: 39.9042° N
经度: 116.4074° E
椭球高: 43.5 m

这个坐标在北京天安门附近。但注意,这个 43.5 米是椭球高,不是海拔高。两者差多少?后面会讲。

2. 局部坐标系(ENU):飞控真正“认”的坐标系

WGS84 虽然全球统一,但做航点规划时用起来很别扭。你想啊,两个航点之间距离 100 米,你用经纬度差值去算,还得考虑地球曲率,太麻烦了。

飞控内部实际用的是局部笛卡尔坐标系,最常见的就是 ENU(East-North-Up)。

  • E(East):东向,单位米
  • N(North):北向,单位米
  • U(Up):天向,单位米

ENU 的原点通常选在起飞点或地面站位置。这样一来,所有航点都变成了相对于原点的三维偏移量。飞控算起来就是简单的加减法,效率极高。

我的习惯:做航点规划时,我会先在 WGS84 里定义好关键航点(比如起飞点、降落点),然后所有中间航点都用 ENU 偏移量来表达。这样既保留了全局坐标的绝对性,又享受了局部坐标的计算便利。

3. 高度系统:AGL 与 MSL 的“生死之差”

高度问题,是航点规划里最容易出 bug 的地方。我见过不止一次,新手把 MSL 当成 AGL 用,结果飞机在山区直接撞树。

高度类型 全称 基准面 典型用途
MSL Mean Sea Level 平均海平面 航线规划、空域管理
AGL Above Ground Level 正下方地面 避障、仿地飞行
Ellipsoidal Height 椭球高 WGS84 椭球面 GPS 原始输出

三者的关系可以用一个简单公式表达:

椭球高 ≈ MSL + 大地水准面差距(Geoid Undulation)
AGL = MSL - 地面高程(DEM 或实时测距)

我曾经踩过的坑:在山区做电力巡检航线规划时,我直接用 MSL 高度设定了航点。结果飞机飞到山脊上方时,实际 AGL 只有不到 10 米,差点撞上铁塔。后来我强制要求:所有仿地航线必须用 AGL 模式,并且叠加 DEM 数据做校验。

4. 坐标转换原理:从 WGS84 到 ENU 的“数学桥梁”

实际工作中,我们经常需要在 WGS84 和 ENU 之间来回转换。转换过程分两步:

  1. WGS84 → ECEF(地心地固坐标系):把经纬度转成以地球质心为原点的三维直角坐标(X, Y, Z)。
  2. ECEF → ENU:以某个参考点(比如起飞点)为原点,做旋转和平移,得到东、北、天三个方向的偏移量。

下面是一个 Python 实现的核心代码片段:

import math

def wgs84_to_enu(lat, lon, alt, ref_lat, ref_lon, ref_alt):
    # 1. 将 WGS84 转 ECEF
    a = 6378137.0  # 地球长半轴
    e2 = 0.00669437999014  # 第一偏心率平方
    
    lat_rad = math.radians(lat)
    lon_rad = math.radians(lon)
    ref_lat_rad = math.radians(ref_lat)
    ref_lon_rad = math.radians(ref_lon)
    
    N = a / math.sqrt(1 - e2 * math.sin(lat_rad)**2)
    x = (N + alt) * math.cos(lat_rad) * math.cos(lon_rad)
    y = (N + alt) * math.cos(lat_rad) * math.sin(lon_rad)
    z = (N * (1 - e2) + alt) * math.sin(lat_rad)
    
    # 2. 计算参考点的 ECEF 坐标
    N_ref = a / math.sqrt(1 - e2 * math.sin(ref_lat_rad)**2)
    x_ref = (N_ref + ref_alt) * math.cos(ref_lat_rad) * math.cos(ref_lon_rad)
    y_ref = (N_ref + ref_alt) * math.cos(ref_lat_rad) * math.sin(ref_lon_rad)
    z_ref = (N_ref * (1 - e2) + ref_alt) * math.sin(ref_lat_rad)
    
    # 3. ECEF 差值转 ENU
    dx = x - x_ref
    dy = y - y_ref
    dz = z - z_ref
    
    e = -math.sin(ref_lon_rad) * dx + math.cos(ref_lon_rad) * dy
    n = (-math.sin(ref_lat_rad) * math.cos(ref_lon_rad) * dx 
         - math.sin(ref_lat_rad) * math.sin(ref_lon_rad) * dy 
         + math.cos(ref_lat_rad) * dz)
    u = (math.cos(ref_lat_rad) * math.cos(ref_lon_rad) * dx 
         + math.cos(ref_lat_rad) * math.sin(ref_lon_rad) * dy 
         + math.sin(ref_lat_rad) * dz)
    
    return e, n, u

实际工程建议:不要自己手写转换逻辑,除非你在做底层飞控。直接用成熟的库,比如 pyproj 或 geographiclib。我团队内部统一用 pyproj,一行代码搞定转换,还自动处理了大地水准面模型。

5. 知识体系总览

下面这张图把本章的核心概念串起来了。你可以看到:WGS84 是“全球坐标”,ENU 是“局部坐标”,高度系统是“垂直基准”,转换公式是“连接桥梁”。

坐标系与空间参考知识体系 WGS84 坐标系 经纬度 + 椭球高(GPS 原生) 坐标转换:WGS84 → ECEF → ENU ENU 局部坐标系 东-北-天,原点在起飞点 高度系统 MSL / AGL / 椭球高 💡 工程要点 规划用 WGS84 定义关键点,飞控用 ENU 执行轨迹 山区航线必须用 AGL + DEM 校验,避免撞地

6. 避坑指南:坐标系相关的常见错误

  • 混淆 MSL 和椭球高:在中国大陆大部分地区,大地水准面差距在 -10m 到 +30m 之间。直接用 GPS 椭球高当海拔用,误差可能超过 30 米。
  • ENU 原点不一致:地面站和飞控的 ENU 原点必须严格一致。我见过一个案例,地面站用起飞点做原点,飞控用当前位置做原点,结果航线偏了 200 米。
  • 忽略地球曲率:航点间距超过 10 公里时,直接用平面几何算 ENU 会有明显误差。这时候必须用大圆航线或测地线算法。

最后提醒一句:坐标系转换不是“写对公式就行”的事。不同飞控固件(PX4、ArduPilot)对高度和坐标的处理细节有差异。做跨平台移植时,一定要在仿真环境里用实际数据验证一遍。