简介:Matlab.rar压缩包是一套基于MATLAB的SGP4轨道计算源码,面向航天工程、天文学及遥感领域需要开展卫星轨道预测的研究者与工程师。SGP4模型由美国空军开发,适用于低轨与中轨卫星,tsince作为自参考时刻起算的关键时间参数贯穿计算流程,理解它能更快入门轨道预报。压缩包共193个文件,以188个m脚本为主,涵盖卫星初始化、SGP4主算法、坐标转换、数据可视化等核心模块;另有4个dat星历/章动参数文件与1个out运行结果文件,总大小仅257KB,轻量精简便于按模块对照学习。当前已有362人学习使用,反映出其在同类项目中的实用参考价值。通过研读这份代码,可以系统掌握卫星两行轨道参数读取、地球引力J2/J4摄动处理、位置速度状态矢量转换的完整实现思路,也能借助随附示例脚本快速验证轨道计算结果,为后续科研或工程开发提供一套可直接修改复用的Matlab工具模板。
1. 从源码站拖下来的 SGP4 模型,为什么改成自己的 TLE 就出错
从代码站拖下来的轨道计算压缩包,解压后通常是一组.m文件和一个能跑出漂亮弧线的 demo。换成自己的 TLE 之后,输出要么是kerr非零,要么是一串NaN,很多人的第一反应是 TLE 解析写错了,但问题往往出在tsince这个参数上——它不是绝对时间戳,而是从 TLE 历元起算的分钟数。SGP4 是 NORAD 用于 TLE 外推的解析模型,输入平均根数,输出 TEME 坐标系下的位置速度,本质上是一套带经验修正的解析展开式,而不是数值积分。这篇文章把 SGP4 模型在 Matlab 里的初始化、tsince的正确计算、坐标转换和验证手段串成一条可复现的链路,适合做卫星过境预报、星座可见性分析和碰撞预警初筛的工程师与学生。已经跑通 demo 的老手,可以直接跳到第 4、5 章看参数边界和批处理技巧。
2. 从源码读透 SGP4 模型与 tsince 参数的真实边界
2.1 先用周期 225 分钟分清 SGP4 与 SDP4
SGP4 这个名字容易让人误以为它覆盖所有地球轨道,实际上 NORAD 的解析模型家族里还有一个 SDP4,两者以轨道周期 225 分钟为界。TLE 的 mean motion 单位是 rev/day,225 分钟正好对应 6.4 rev/day:周期低于 225 分钟用 SGP4,高于等于 225 分钟必须走 SDP4 的深空分支,因为后者额外考虑了日月引力、共振带和地球扁率的长期项。你在源码解压包里看到的sgp4.m主程序通常会根据satrec里的method字段自动分流,但如果下载的是精简版、只保留近地分支,高轨 TLE 会算出一组发散的位置。
判断当前 TLE 该走哪个模型,直接在 Matlab 里看第二行的第 54~63 位就行:
line2 = '2 25544 51.6400 10.0000 0005000 90.0000 270.0000 15.50000000000000'; n_revday = str2double(line2(53:63)); if n_revday > 6.4 fprintf('周期 < 225 分钟,走 SGP4\n'); else fprintf('周期 >= 225 分钟,需要 SDP4\n'); endn_revday是平运动,单位 rev/day;6.4 这个阈值来自 1440 / 225 = 6.4,记住这一个数就能在跑仿真前快速判断模型选型是否合理。源码包里sgp4init里对待 SDP4 分支的处理通常是以isimp是否为 0 或method是否等于's'来区分的,读代码时先找这两个标志,比逐行推公式快得多。
2.2 tsince 的单位与起算点,以及容易被忽略的时间系统
tsince的单位是分钟,起算点是 TLE 第一行第 19~32 字符里的 epoch,也就是该组平均根数对应的参考时刻。SGP4 内部所有展开项都以tsince为自变量,所以它必须是一个相对值而不是datetime对象。常见错误有三种:把tsince当秒传进去、把绝对时间戳直接传进去、用本地时区算差值。TLE 的 epoch 是 UTC,观测时刻也应该换算到 UTC,两者相减再乘以 1440 才是分钟数。
% 解析 TLE 第一行里的 epoch:YYDDD.FFFFFFFF yyyy = 2000 + str2double(line1(19:20)); % 简化处理:00-56 视为 2000 年代 doy = str2double(line1(21:32)); % 年内第几天,含小数 epoch_utc = datetime(yyyy, 1, doy, 0, 0, 0, 'TimeZone', 'UTC'); epoch_jd = juliandate(epoch_utc); % 目标观测时刻,同样用 UTC obs_utc = datetime(2024, 1, 2, 3, 0, 0, 'TimeZone', 'UTC'); tsince = (juliandate(obs_utc) - epoch_jd) * 1440;datetime(yyyy, 1, doy)里的doy可以直接带小数,比如doy = 1.5表示 1 月 1 日 12 时,Matlab 会正确换算成时分秒。tsince为正表示从历元往未来推,为负也不是不能算,但 TLE 的拟合弧段通常在历元前后几小时内,外推到一天以前的结果我不建议用于工程判断。
注意:SGP4 的时间系统是 UTC 近似 UT1,两者差值在 0.9 秒以内,对 LEO 卫星的沿迹误差大约是 7 km。这不是你写错了,而是模型固有误差,做米级应用时要想清楚误差预算。
2.3 源码里必须先读的三个函数
Vallado 系的 Matlab 移植版命名相对统一,拿到压缩包先别跑 demo,打开这三个文件:twoline2rv.m、sgp4init.m、sgp4unit.m。twoline2rv负责把两行 TLE 解析成satrec结构体,这个结构体不是普通的输入参数,它的字段在每次sgp4调用后会被改写;sgp4init把平均根数转换成 SGP4 内部需要的近点根数、长周期周期项系数以及satrec里的各种速率常数;真正做解析外推的是sgp4unit,sgp4.m只是它的封装。
| 常见文件名 | 职责 | 读源码时重点看什么 |
|---|---|---|
twoline2rv.m | 解析 TLE,调用sgp4init | 输入校验、whichconst的传递路径 |
sgp4init.m | 初始化satrec,判断 SGP4/SDP4 分支 | satrec.method、satrec.isimp赋值 |
sgp4unit.m | 按tsince做解析外推 | 返回前是否改写satrec.t等字段 |
gstime.m | 计算格林尼治恒星时 | 输入是 JD 还是 MJD,单位是弧度 |
jday.m | 年月日时分秒转儒略日 | 是否处理了小数秒 |
读sgp4unit时重点确认两个常量:位置单位是 km,速度单位是 km/s;输出坐标系是 TEME,不是 J2000,更不是 ECEF。很多老手在坐标转换上翻车,根源都是没看这一个函数的注释。另外注意satrec结构体里如果存了函数句柄,Matlab 的satrec = satrec0值拷贝会共享句柄,但结构体自身的数值字段是独立复制的,所以每次传播前拷贝一份原始satrec是安全的。
3. 用 Matlab 跑通 SGP4 轨道计算的最小复现路径
3.1 数据流:从 TLE 两行到 satrec 初始化
SGP4 的调用链非常短:两行 TLE 字符串进twoline2rv,得到satrec;任意时刻的tsince进sgp4,得到r和v。最省事的做法是把 TLE 存在文本文件里,用fopen按行读入,但要注意复制粘贴时行首行尾的空格经常被吃掉,TLE 每一行固定 69 个字符,少了字符解析必然失败。
% line1, line2 来自 Space-Track 或 CelesTrak,保持原始 69 字符 line1 = '1 25544U 98067A 24001.50000000 .00016717 00000-0 30000-3 0 9990'; line2 = '2 25544 51.6400 10.0000 0005000 90.0000 270.0000 15.50000000000000'; whichconst = 72; % WGS-72 常数组,与 TLE 生成时的重力模型一致 [satrec, kerr] = twoline2rv(line1, line2, whichconst); if kerr ~= 0 error('TLE 解析失败,kerr = %d', kerr); endwhichconst传 72 是官方 TLE 体系的标准选择,WGS-84 常数组会让初始化结果有微小偏差,普通近地轨道仿真感知不到,但你要和别人的结果对比时,应该先确认两边用的是同一组常数。kerr非零时satrec不可用,常见原因是 TLE 行被截断、校验位算错、或者两个行的编号不一致。
3.2 生成 tsince 时间轴:预生成一次,反复使用
做轨道预报时通常需要按秒级或分钟级连续输出位置,先把整个时间轴换算成tsince数组,再循环调用sgp4。这里有个关键认知:sgp4内部不接收时间数组,它每次都只接受一个标量tsince,原因是satrec里有状态字段会在传播过程中被改写,向量化传参没有意义。
t_start = datetime(2024, 1, 2, 0, 0, 0, 'TimeZone', 'UTC'); t_end = datetime(2024, 1, 3, 0, 0, 0, 'TimeZone', 'UTC'); dt_min = 1; % 步长:分钟 % 生成 UTC 时间序列,再统一转成 tsince t_utc = t_start:minutes(dt_min):t_end; tsince_min = (juliandate(t_utc(:)) - epoch_jd) * 1440;juliandate对 1×N 的 datetime 数组返回 1×N 的儒略日数组,减掉epoch_jd后乘以 1440,得到的就是以历元为起点的分钟数数组。注意t_utc(:)转成列向量,后面存结果时按列填充更符合后续绘图和写 CSV 的习惯。
3.3 完整的最小循环:算 24 小时轨迹并做基本检查
n = numel(tsince_min); r_out = zeros(3, n); v_out = zeros(3, n); satrec0 = satrec; % 保留原始初始化结果 for i = 1:n satrec = satrec0; % 每次从备份拷贝,避免状态污染 [satrec, r_out(:, i), v_out(:, i)] = sgp4(satrec, tsince_min(i)); end % 自洽性检查:近圆轨道上 r 与 v 近似垂直,点积应接近 0 rdotv = dot(r_out, v_out, 1); relative_rdotv = abs(rdotv) ./ (vecnorm(r_out, 2, 1) .* vecnorm(v_out, 2, 1)); fprintf('max |r·v|/(|r||v|) = %.3e\n', max(relative_rdotv));satrec = satrec0这一行是关键。sgp4在返回前会把当前tsince、以及内部计算的x、y等坐标值写回satrec字段,如果下一次循环直接复用上一个satrec,新tsince传入时初始条件已经变了,表现就是轨迹出现不连续的跳点。自洽性检查里relative_rdotv是量纲无关的,近圆轨道一般小于1e-2,如果出现0.1以上的量级,多半是卫星进了椭圆轨道或者模型跑错了分支。
3.4 参数传递的两处易错点
twoline2rv的参数顺序在不同移植版本里有差异,常见的是twoline2rv(line1, line2, whichconst),但也有人把whichconst放在第一个参数位。拿到源码先看函数声明,不要凭记忆传参。第二个易错点是sgp4返回值里第一个satrec是更新后的结构体,有的版本会因此在satrec.error字段里写入本次传播的状态码,循环里如果你不检查这个字段,数值发散时只能看到NaN。
if satrec.error ~= 0 fprintf('tsince = %.2f min 时出错,error = %d\n', tsince_min(i), satrec.error); ends satrec.error的具体取值含义因源码版本而异,但 0 通常代表正常。这个检查放在循环里几乎不消耗时间,却能在第一时间把模型选型错误和 TLE 异常定位到具体时刻。
4. SGP4 输出的落地链路:坐标转换、精度核查与排错
4.1 TEME 不是 ECI 也不是 ECEF,先转 GMST 再说
sgp4返回的位置速度在 TEME 坐标系,它的 Z 轴指向瞬时真赤道、X 轴指向平均春分点,和地面站的经纬度没有直接关系。要算地面站可见性,第一步是把 TEME 绕 Z 轴旋转格林尼治恒星时角 GMST,转到 ECEF 再减法求站心矢量。
function r_ecef = teme2ecef(r_teme, gmst_rad) % 绕 Z 轴旋转 GMST,把 TEME 转到 ECEF c = cos(gmst_rad); s = sin(gmst_rad); R3 = [ c s 0; -s c 0; 0 0 1]; r_ecef = R3 * r_teme; end function gmst = gmst_from_jd(jd_ut1) % IAU 1982 简化公式,输入儒略日(UT1),输出弧度 T = (jd_ut1 - 2451545.0) / 36525.0; gmst = 67310.54841 + ... (876600.0 * 3600 + 8640184.812866) * T + ... 0.093104 * T^2 - 6.2e-6 * T^3; gmst = mod(gmst / 240.0, 360.0) * pi / 180.0; endgmst_from_jd的结果单位是弧度,gmst / 240是把格林尼治恒星时的秒数换算成度,因为 1 秒恒星时对应的角度是 1/240 度。忽略极移和章动的高阶项,这个转换对 LEO 的位置误差在 0.3 km 量级,做可见性分析和过境预报足够,做精密定轨就不够看了。注意jd_ut1直接用juliandate(t_utc)的结果即可,UTC 与 UT1 的差异在 0.9 秒内,对角度的影响可以忽略。
4.2 地面站方位角和仰角的快速算法
拿到 ECEF 位置后,用站心坐标系算方位角和仰角是标准做法。先把站点的 ECEF 坐标换算成站心 ENU 坐标,再求两个角度。
function [az, el, slant_range] = enu_from_ecef(r_sat_ecef, station_ecef, lat_rad, lon_rad) % 站心 ENU 基向量 rho = r_sat_ecef - station_ecef; slat = sin(lat_rad); clat = cos(lat_rad); slon = sin(lon_rad); clon = cos(lon_rad); e = [-slon, clon, 0]; n = [-clon * slat, -slon * slat, clat]; u = [clon * clat, slon * clat, slat]; east = dot(rho, e); north = dot(rho, n); up = dot(rho, u); slant_range = norm(rho); az = atan2(east, north) * 180 / pi; el = asin(up / slant_range) * 180 / pi; az = mod(az, 360); end站点的lat_rad和lon_rad是测站大地坐标转弧度,这个函数返回的方位角以正北为 0 度、顺时针增加,仰角为负时表示卫星在地平线以下。用这个函数扫一遍上一节生成的r_ecef序列,就能画出过境弧线,判断哪些时间段满足最小仰角约束。
4.3 精度核查:先自查,再和外部数据比
SGP4 的典型误差对 LEO 是沿迹方向几公里到几十公里,跨源对比时先要搞清楚两边的坐标系和时间基准。我的自查顺序是三层:第一层是 3.3 节里的r·v相对值检查,确认数值没有发散;第二层是取tsince = 0的位置,应该严格回到 TLE 历元附近的坐标,差异应该在毫米到厘米量级;第三层才是和外部星历对比,而且对比时要算上 TLE 发布时间与观测时刻之间的差值,TLE 越老误差越大。
[satrec_chk, r0, v0] = sgp4(satrec0, 0); fprintf('tsince=0 时 |r| = %.3f km\n', norm(r0));这一步的价值在于验证twoline2rv初始化是否正确。如果tsince=0时的高度和 TLE 第一行解算出的近地点、远地点明显矛盾,那问题一定出在 TLE 解析层,而不是传播层。
4.4 常见报错与排查对照表
| 现象 | 常见原因 | 对策 |
|---|---|---|
kerr非零 | TLE 行被截断、行首行尾空格丢失 | 重新下载原始 TLE,检查每行长度是否为 69 |
| 输出全是 NaN | tsince为 Inf 或 NaN | 检查t_utc里是否有 NaT,juliandate是否正常 |
| 轨迹出现跳点 | satrec被上次调用污染 | 每次循环前satrec = satrec0 |
| 结果和某软件差几十 km | 坐标系、重力常数或单位不一致 | 双方统一到 TEME、km、km/s、WGS-72 |
| 高轨卫星算出发散 | 源码只有 SGP4 分支,没有 SDP4 | 换完整版源码或换其他轨道预报库 |
排查这类问题有个通用思路:先把tsince限制在 0 到 1440 分钟之间,排除大时间外推的干扰,再看r的模长是否落在合理范围。近地轨道|r|约 6800 到 7600 km,如果你算出来是几万 km,问题大概率在坐标系或者单位换算上。
5. 让 tsince 为你工作:封装传播函数与批处理提速
sgp4每次只接受标量tsince,但工程上最常见的需求是多颗卫星、多个时刻的批量传播。我的做法是先把单星传播封装成函数,内部自动处理satrec的状态拷贝,调用方只传初始 satrec和时间轴。
function [r_out, v_out] = propagate_sat(satrec0, t_minutes) n = numel(t_minutes); r_out = zeros(3, n); v_out = zeros(3, n); for i = 1:n satrec = satrec0; [satrec, r_out(:, i), v_out(:, i)] = sgp4(satrec, t_minutes(i)); end end这个封装把最容易出错的satrec状态管理收口到一个函数里。多星场景下,把每颗星的satrec0存成结构体数组,外层用parfor按卫星并行,内层还是这个函数:
% satrec_list:1×M 结构体数组,每颗星一组 TLE 初始化结果 M = numel(satrec_list); r_all = cell(M, 1); parfor k = 1:M r_all{k} = propagate_sat(satrec_list(k), tsince_min); endparfor提速的前提是每颗星只读自己的satrec_list(k),不要把整个结构体数组共享进去。写入结果时也不要直接在循环里用fopen追加写文件,所有 worker 并行写同一个文件会互相覆盖,正确做法是先收集到cell或数组里,再统一用writetable落盘。
还有一个经常被忽视的校验技巧:把propagate_sat的返回值和tsince = 0的单点结果做差分,验证封装没有引入额外状态。具体做法是在封装内部加一个断言,t_minutes里包含 0 时,对应列的位置要和sgp4(satrec0, 0)的结果在 1e-6 km 量级一致。这样每次改完源码或者换一批 TLE,跑一次测试就能确认传播链路没有坏。把时间轴生成、传播、坐标转换三步拆成独立函数后,整个仿真流程的输入就只剩satrec0和tsince_min两个变量;后续换 TLE 文件、改采样步长、加多颗卫星,都不需要动上层代码,这是把 SGP4 用顺手的最终形态。
本文还有配套的精品资源,点击获取