坐标系基础:地理坐标系(WGS84)、局部坐标系(ENU)、机体坐标系、坐标系转换原理

做无人机航点规划,第一个绕不开的坎就是坐标系。我见过不少新手,代码写得挺漂亮,结果飞机一上天就乱飞——十有八九是坐标系搞混了。说白了,坐标系就是给飞机一个「我在哪、要去哪」的数学框架。今天咱们把这几个坐标系彻底捋清楚。

1. 地理坐标系(WGS84)——地球的“全球地址”

WGS84 是目前全球卫星导航系统(GPS、北斗、GLONASS)通用的坐标基准。它用经度、纬度和海拔高度来描述地球上任意一点的位置。你手机里那个定位图标,背后就是 WGS84。

我记得刚入行时,总觉得 WGS84 就是经纬度,很简单。直到有一次在极地附近做测试,发现经度线收敛导致定位跳变,才意识到这东西没那么简单。

参数 说明 典型值
经度(Longitude) 本初子午线以东/以西的角度 116.397°E(北京)
纬度(Latitude) 赤道以北/以南的角度 39.908°N(北京)
海拔(Altitude) 相对于 WGS84 椭球面的高度 43.5 m
⚠️ 注意:WGS84 的海拔是椭球高,不是真实海拔。真实海拔需要结合大地水准面模型(如 EGM96)修正。我曾经在山区做测绘时,椭球高和真实海拔差了 30 多米,差点把飞机撞山。

2. 局部坐标系(ENU)——以你为中心的“东北天”

ENU 坐标系,全称 East-North-Up,就是以东为 X 轴、北为 Y 轴、天为 Z 轴的右手坐标系。它把地球表面的一小块区域近似为平面,非常适合做局部路径规划。

为什么需要 ENU?因为 WGS84 是球面坐标,计算距离和角度非常麻烦。而 ENU 是直角坐标,加减乘除直接算,简单粗暴。你想想看,让飞机飞 100 米向东,用 ENU 就是 (100, 0, 0),多清爽。

💡 核心要点:ENU 的原点可以任意指定,通常选在起飞点或地面站位置。原点确定后,整个坐标系就固定了。

我在项目中习惯把起飞点设为 ENU 原点。这样所有航点都相对于起飞点描述,调试时一眼就能看出飞机偏了多少。

3. 机体坐标系(Body Frame)——飞机自己的“前后左右”

机体坐标系是固定在飞机上的。通常 X 轴指向机头(前),Y 轴指向右翼(右),Z 轴指向下方(下)。注意,Z 轴朝下是航空界的惯例,和 ENU 的 Z 轴朝上正好相反。

这个坐标系用来描述飞机的姿态(俯仰、横滚、偏航)和机体速度。比如「飞机向右滚转 30 度」,就是在机体坐标系下说的。

轴 方向 对应姿态角
X 机头方向(前) 俯仰(Pitch)
Y 右翼方向(右) 横滚(Roll)
Z 机身下方(下) 偏航(Yaw)
💡 小技巧:机体坐标系和 ENU 的 Z 轴方向相反,转换时记得取负。我刚开始写代码时忘了这茬,结果飞机在天上画了个完美的倒抛物线。

4. 坐标系转换原理——把“全球地址”变成“本地坐标”

坐标系转换,说白了就是数学上的旋转和平移。咱们从最常用的 WGS84 → ENU 转换说起。

4.1 WGS84 → ENU 转换

这个转换分两步:先把 WGS84 转成地心地固坐标系(ECEF),再把 ECEF 转成 ENU。

// 伪代码示例:WGS84 转 ENU
// 输入:纬度 lat, 经度 lon, 高度 alt(WGS84)
// 输出:东向 e, 北向 n, 天向 u(ENU)

// 第一步:WGS84 转 ECEF
x_ECEF = (N + alt) * cos(lat) * cos(lon)
y_ECEF = (N + alt) * cos(lat) * sin(lon)
z_ECEF = (N * (1 - e²) + alt) * sin(lat)

// 第二步:ECEF 转 ENU(以参考点 lat0, lon0, alt0 为原点)
dx = x_ECEF - x_ECEF_ref
dy = y_ECEF - y_ECEF_ref
dz = z_ECEF - z_ECEF_ref

e = -sin(lon0)*dx + cos(lon0)*dy
n = -sin(lat0)*cos(lon0)*dx - sin(lat0)*sin(lon0)*dy + cos(lat0)*dz
u = cos(lat0)*cos(lon0)*dx + cos(lat0)*sin(lon0)*dy + sin(lat0)*dz

嗯,这里要注意:N 是卯酉圈曲率半径,e 是地球偏心率。这些值在 WGS84 标准里是固定的,直接查表用就行。

4.2 ENU ↔ 机体坐标系转换

这个转换靠的是姿态角(偏航、俯仰、横滚)。说白了就是把 ENU 下的向量,绕 Z 轴转偏航角,再绕 Y 轴转俯仰角,最后绕 X 轴转横滚角。

我建议你直接用旋转矩阵,别自己手算欧拉角,容易出万向锁问题。我在一个项目中手写了欧拉角转换,结果在俯仰 90 度时炸了,后来老老实实换了四元数。

💡 推荐做法:内部统一用四元数做姿态表示和旋转计算,只在输入输出时转成欧拉角。这样既避免了万向锁,又方便调试。

5. 知识体系总览

下面这张图把本章的核心逻辑串起来了。你一看就明白:WGS84 是全局定位,ENU 是局部规划,机体坐标系是姿态控制,三者通过转换公式连接。

坐标系知识体系总览 坐标系知识体系总览 WGS84 地理坐标系 ENU 局部坐标系 机体坐标系 Body Frame 转换路径 WGS84 → ECEF → ENU → 机体坐标系(通过旋转矩阵/四元数) 全球定位 GPS/北斗原始数据 局部规划 航点生成、避障 姿态控制 飞控内环、角速度 核心原则:WGS84 用于全局定位,ENU 用于局部计算,机体坐标系用于姿态控制 三者通过坐标转换公式(旋转 + 平移)相互关联

6. 避坑指南

最后分享几个我踩过的坑,希望能帮你省点时间。

  • 海拔问题:WGS84 椭球高 ≠ 真实海拔。做地形跟随任务时,一定要用 EGM96 模型修正。我曾经在青藏高原测试,椭球高和真实海拔差了 60 多米,差点撞山。
  • 原点漂移:ENU 原点选在起飞点,但飞机飞远了(比如 10 公里以上),地球曲率就不能忽略了。这时候要么分段规划,要么换回 WGS84 计算。
  • 万向锁:用欧拉角做姿态转换时,俯仰角接近 ±90° 会丢失一个自由度。我建议内部统一用四元数,只在日志和调试时转成欧拉角。
  • 精度损失:WGS84 转 ENU 时,如果参考点和目标点距离很远,浮点精度会下降。我习惯在每次航点更新时重新计算转换矩阵,而不是用固定的。
💡 我的习惯:在代码里写一个 CoordinateTransformer 类,封装所有转换逻辑。初始化时传入起飞点 WGS84 坐标,内部维护 ENU 原点和旋转矩阵。这样上层代码只跟 ENU 打交道,清爽多了。