简介:在地理信息系统与航空航天领域,坐标转换是数据定位与分析的基础环节。这套MATLAB工具包面向需要处理地球坐标的工程师与研究人员,重点解决地心地固坐标系(ECEF)与经纬度高度(LLH)之间,以及局部东-北-上(ENU)坐标系与ECEF之间的换算问题。压缩包内共2个m文件,大小约2KB,代码简洁紧凑,适合直接调用或二次移植。一个脚本基于WGS84椭球模型实现ECEF转经纬高,另一个完成局部ENU到ECEF的转换,两者结合可覆盖从空间绝对位置到局部参考系的完整转换链路,常用于GPS数据处理、遥感影像几何校正和飞行路径规划。目前已有480人学习下载。对希望深入掌握坐标转换原理的开发者而言,这份代码提供了可运行示例和基于地球椭球参数的详细解算过程,既能帮助理解背后的数学推导,也能快速集成到实际项目中。
1. 坐标系转换在 MATLAB 里最常见的起点,是地固坐标系和经纬高之间的地球坐标转换
同样一个观测点,在 GPS 接收机里读到的是北纬 40.06°、东经 116.32°、椭球高 43 米,后端仿真或雷达数据处理却要求给出地固坐标系(ECEF)下的 x、y、z。这不是换单位那么简单:经纬高定义在 WGS-84 椭球面上,地固坐标定义在随地球旋转的直角参考架里,二者靠三个椭球常数和一组非线性公式衔接。标题里的 plateza9 这类脚本,本质上就是在 MATLAB 里把“地球坐标转换”封装成可调函数。下面从公式出发,给出手写转换函数、CSV 批量导入和结果验证的完整路径,适合在地理信息、卫星导航和仿真项目里处理坐标数据的工程师。
2. 地固坐标系与地球坐标转换:从经纬高到 ECEF 的三条公式
2.1 地固坐标系不是“地心坐标系”的另一个名字
地固坐标系的全称是 Earth-Centered, Earth-Fixed,直译就是“地心、地球固定”,MATLAB 文档里常缩写成 ECEF。坐标系原点在地球质心,Z 轴指向国际协议北极,X 轴指向本初子午线与赤道交点,Y 轴按右手系补齐。关键在“Fixed”:坐标系与地球壳层固连,地球自转时地面站坐标不会漂移。
与之相对的经纬高坐标系属于大地坐标系,参考面是旋转椭球而不是地面。北斗、GPS 原始输出大多是大地坐标,而卫星轨道计算、双天线测姿、地基增强系统里,ECEF 几乎被当成默认语言。坐标系转换做的一件事就是把这两套读数相互翻译;地固坐标系的地位,决定了它通常是整个转换流程的中间锚点。
2.2 正向转换公式:WGS-84 椭球参数怎么进来
正向转换指从经纬高(lat, lon, h)到 ECEF(x, y, z),核心就是 WGS-84 椭球的四个量。a 是长半轴,f 是扁率,e² 是第一偏心率平方,N 是卯酉圈曲率半径。参数一旦用错,结果不是差几米,而是差出几个数量级。
| 符号 | WGS-84 数值 | 说明 |
|---|---|---|
| a | 6378137.0 m | 赤道半轴 |
| f | 1 / 298.257223563 | 椭球扁率 |
| e² | f × (2 − f) | 第一偏心率平方 |
| N | a / sqrt(1 − e²·sin²φ) | 卯酉圈曲率半径 |
正向计算的 MATLAB 写法非常短:
a = 6378137.0; f = 1 / 298.257223563; e2 = f * (2 - f); N = a ./ sqrt(1 - e2 .* sind(lat).^2); x = (N + h) .* cosd(lat) .* cosd(lon); y = (N + h) .* cosd(lat) .* sind(lon); z = (N .* (1 - e2) + h) .* sind(lat);这里必须用sind和cosd,MATLAB 默认的sin、cos接收弧度,直接套会得到完全错乱的结果。变量 lat、lon、h 支持向量输入,.*和.^是保证逐元素运算;如果写成*和^,三列向量会触发矩阵维度错误。输出 x、y、z 单位是米,与 h 保持一致。
有个容易忽略的点:h 是椭球高,不是海拔。GPS 接收机解算出的高程默认是 WGS-84 椭球高,而测绘成果常用正常高,两者相差一个高程异常,在同一城市可能差几十米。做转换前先确认数据来自哪个高程基准。
2.3 反算时要迭代,不能直接开方
ECEF 转经纬高的反解,经度可以一步拿到:lon = atan2d(y, x)。但纬度和椭球高互相耦合,因为 N 本身是纬度的函数,而 h 又出现在平面分量的表达式里,无法得到闭式解。常用的做法是给定一个初值,然后迭代修正。
p = hypot(x, y); lat = atan2d(z, p .* (1 - e2)); for k = 1:8 N = a ./ sqrt(1 - e2 .* sind(lat).^2); h = p ./ cosd(lat) - N; lat = atan2d(z, p .* (1 - e2 .* N ./ (N + h))); end初值的选取很关键:atan2d(z, p*(1-e2))给出的纬度比真实值略小,但它能保证迭代单调收敛。8 次迭代对绝大多数地球表面点已经足够,高程精度能到毫米级。若把迭代次数改成 3,经纬度变化很小,但 h 可能差出厘米级,这是后续做高程拟合时注意的精度拐点。
2.4 框架转换之外,还有基准转换
经常被混用的还有另一类转换:WGS-84、CGCS2000、北京 54、西安 80 之间的坐标基准转换。椭球参数、原点定向和尺度都不同,光靠经纬高和 ECEF 的三条公式解决不了,标准做法是七参数布尔莎模型:
Xt = Tx + (1 + k) * X + wz * Y - wy * Z; Yt = Ty - wz * X + (1 + k) * Y + wx * Z; Zt = Tz + wy * X - wx * Y + (1 + k) * Z;其中 Tx、Ty、Tz 是三个平移,wx、wy、wz 是三个旋转,k 是尺度变化。这部分在工程中往往通过布尔莎或者三维四参数完成。标题里的“plateza9”如果设计成通用入口,通常会把框架转换和基准转换分层,先转 ECEF,再套七参数,最后转目标椭球的经纬高。
3. 在 MATLAB 里跑通地球坐标转换:工具箱函数与手写函数
3.1 有 Mapping Toolbox 时直接用 lla2ecef 和 ecef2lla
MATLAB 的 Mapping Toolbox 提供了lla2ecef和ecef2lla,这是最快的一条路,不需要自己维护椭球参数。函数默认使用 WGS-84 参考椭球,输入矩阵规格是 N×3,列顺序固定为纬度、经度、椭球高,单位分别是度、度、米。
lla = [40.0, 116.0, 50]; % 纬度、经度、椭球高 ecef = lla2ecef(lla); % 1×3 的 ECEF lla_back = ecef2lla(ecef); % 转回去输出 ecef 的三个分量是 x、y、z,单位米。ecef2lla内部使用迭代算法,所以不必关心反解收敛问题。需要注意,这个函数对第三列的解释永远是椭球高;把海拔直接填进去,在起伏较大的地区会引入不可接受的误差。
3.2 最小可运行代码:单点转换和十万点批量
实际项目里很少只转一个点,更多是从日志文件里拉出几十万行轨迹。批量处理时,只要把经纬高组织成 N×3 矩阵即可:
n = 1e5; lat = -90 + 180 * rand(n, 1); lon = -180 + 360 * rand(n, 1); h = zeros(n, 1); lla = [lat, lon, h]; ecef = lla2ecef(lla); fprintf('ECEF 坐标范围:\n'); disp([min(ecef); max(ecef)]);这段代码生成 10 万个全球随机点,对每个点做 WGS-84 正向转换。rand生成的纬度覆盖 −90° 到 90°,经度覆盖 −180° 到 180°,高程全部按椭球高 0 处理。人脸图上创建 N×3 矩阵,比循环调lla2ecef快一个量级以上,因为函数内部对矩阵做了向量化运算。
3.3 没有工具箱:手写 geodetic2ecef 和 ecef2geodetic
如果目标机器没有 Mapping Toolbox,或者需要在算法流程里嵌入可读性较高的源码,手写版本最稳妥。把第 2 章的公式整理成完整函数:
function [x, y, z] = geodetic2ecef(lat, lon, h) % geodetic2ecef 经纬高转地固坐标系 % 输入 lat, lon 单位为度,h 单位为米 a = 6378137.0; f = 1 / 298.257223563; e2 = f * (2 - f); N = a ./ sqrt(1 - e2 .* sind(lat).^2); x = (N + h) .* cosd(lat) .* cosd(lon); y = (N + h) .* cosd(lat) .* sind(lon); z = (N .* (1 - e2) + h) .* sind(lat); end function [lat, lon, h] = ecef2geodetic(x, y, z) % ecef2geodetic 地固坐标转经纬高 a = 6378137.0; f = 1 / 298.257223563; e2 = f * (2 - f); lon = atan2d(y, x); p = hypot(x, y); lat = atan2d(z, p .* (1 - e2)); for k = 1:8 N = a ./ sqrt(1 - e2 .* sind(lat).^2); h = p ./ cosd(lat) - N; lat = atan2d(z, p .* (1 - e2 .* N ./ (N + h))); end end正向函数里的N与h都是同尺寸数组,所以三条输出语句全部用点乘。反向函数里p = hypot(x, y)返回逐元素模长,等价于sqrt(x.^2 + y.^2),但数值稳定性更好。迭代初值故意用了(1-e2)修正项,避免在低纬度处出现台阶式跳变。
这组函数与lla2ecef的差异控制在微米级,差别来自ecef2lla内部使用的参考椭球细节和迭代停止条件。手写版还有一个额外优势:可以随时替换a和f,兼容克氏椭球或自定义参考椭球,这在做地方坐标系转换时非常实用。
4. 把 plateza9 变成批量工具:从 CSV / TXT 导入到统一输出
4.1 读取坐标文件:readmatrix 和 readtable 两种姿势
很多人在“coord 在主界面的什么地方导入 csv 或者 txt 文件”这个问题上绕路,以为 MATLAB 有个隐藏的坐标导入按钮。实际处理坐标数据时,命令行脚本比图形界面靠谱得多。无表头的纯数据文件直接用readmatrix:
data = readmatrix('stations.csv'); lla = data(:, 1:3);readmatrix会自动判定分隔符,空格、逗号、Tab 都能处理。如果 CSV 带表头,需要用readtable保住列名:
tbl = readtable('stations.csv'); lat = tbl{:, 'lat'}; lon = tbl{:, 'lon'}; h = tbl{:, 'height'}; lla = [lat, lon, h];readtable返回的是 table 对象,用花括号取值得到的是数值矩阵。表头里有中文列名时,tbl.lat这类点索引可能失效,最稳的方式是tbl{:,'列名'}。读取后立刻检查isnan(lla(:)),因为文件里的空行和注释行会被读成 NaN,直接进入转换函数会把整行结果污染成 NaN。
4.2 按 plateza9 的命名封装转换入口
压缩包里的文件名未必规范,但封装思路是固定的:一个总入口、两个方向、一个固定输出格式。以下代码把前面的手写函数包装成 plateza9:
function out = plateza9(coords, mode) % plateza9 地球坐标转换便捷入口 % coords 为 m×3 或 m×3 以上矩阵,取前三列 % mode 可选 "lla2ecef" 或 "ecef2lla" arguments coords (:,3) double mode (1,1) string = "lla2ecef" end switch mode case "lla2ecef" [x, y, z] = geodetic2ecef(coords(:,1), coords(:,2), coords(:,3)); case "ecef2lla" [x, y, z] = ecef2geodetic(coords(:,1), coords(:,2), coords(:,3)); otherwise error('plateza9:unknownMode', '不支持的模式: %s', mode); end out = [x, y, z]; endarguments块里的(:,3) double强制输入至少三列,并且必须是双精度数组。mode默认指向正向转换,只给一个参数时也能工作。反向转换输出仍然是三列,前两列是经纬度、第三列是椭球高,这样上游代码不必区分到底是哪一类坐标。
4.3 向量化、循环与异常定位
早期版本常写成逐行循环:
for i = 1:size(lla, 1) [x, y, z] = geodetic2ecef(lla(i,1), lla(i,2), lla(i,3)); out(i, :) = [x, y, z]; end这个写法在 1 万点以下没问题,到 100 万点就会明显拖慢,原因不是 MATLAB 循环慢,而是每次迭代都要做函数调用和矩阵拼接。改用列向量直接调用一次,性能差异能到两个量级:
[x, y, z] = geodetic2ecef(lla(:,1), lla(:,2), lla(:,3)); out = [x, y, z];批处理异常时,不要直接看 100 万行的原始结果,先做范围检查。下面这张表是实际项目里最常见的四类问题:
| 现象 | 大概率原因 | 排查方式 |
|---|---|---|
| 输出出现 NaN | 源文件含表头或空行 | 检查isnan(lla)的行索引 |
| 坐标差几十公里 | 用了平均半径 6371 km | 确认代码里写的是a=6378137.0 |
| 纬度与经度明显颠倒 | CSV 列顺序不是 lat, lon, h | 查看第一列数值是否在 −90 到 90 之间 |
| 全部高程为 0 | 第三列被读成空值 | 用readtable看列名和缺失值 |
5. 转完不验收等于白转:往返误差验证和三个易错点
5.1 把输出再转回原坐标系,误差看两个量
坐标系转换写完,验证方法只有一个标准答案:将 ECEF 结果反向转回经纬高,然后与原始输入比对。往返误差能同时验证正向公式、反向迭代次数和椭球参数是否一致。
lat0 = 40 + rand(100, 1) * 0.1; lon0 = 116 + rand(100, 1) * 0.1; h0 = 50 + randn(100, 1) * 10; lla_in = [lat0, lon0, h0]; ecef_out = plateza9(lla_in, "lla2ecef"); lla_back = plateza9(ecef_out, "ecef2lla"); fprintf('纬度最大误差: %.3e 度\n', max(abs(lla_in(:,1) - lla_back(:,1)))); fprintf('经度最大误差: %.3e 度\n', max(abs(lla_in(:,2) - lla_back(:,2)))); fprintf('高度最大误差: %.3e 米\n', max(abs(lla_in(:,3) - lla_back(:,3))));运行后,经纬度误差应在 1e-10 度数量级,对应毫米级;高程误差在 1e-6 米数量级。这个结果说明代码本体没有问题,后续如果出现更大偏差,问题几乎都在数据侧,而不是转换侧。
5.2 三个在地固坐标系转换中被反复问到的边界细节
第一点,经度方向别被 360° 包络骗了。atan2d返回的经度范围是 −180° 到 180°,源数据如果是 0° 到 360° 的格式,反向输出会突然跳到负值,这时用mod(lla_back(:,2), 360)统一到 0–360° 再比较,否则会被误判成上千公里的偏差。
第二点,极点附近经度不稳定。纬度接近 ±90° 时,所有经度对应的空间位置几乎重叠,反解出的经度会受迭代初值影响而跳动。这是地固坐标系的固有奇点,不是算法缺陷;项目如果涉及极区数据,验证时要单独对纬度和经度分别设容差。
第三点,如果系统下一步需要ecef2eci,也就是从地固坐标系转地心惯性系,必须引入地球自转矩阵和格林尼治恒星时角,不能把 ECEF 坐标直接当成惯性系坐标使用。时间戳不同,同一组 ECEF 坐标对应的惯性系坐标相差很大;这也是“地球坐标转换”里最容易和框架转换混淆的一层。把 plateza9 的输入侧加上时间参数,或者让调用方在外部完成时间对齐,再把结果交给后续矩阵旋转。
本文还有配套的精品资源,点击获取