
简介一套面向卫星导航与定位算法研究人员的MATLAB工具包内含电离层与对流层延迟仿真及校正完整流程。资源共11个文件以10个m脚本和1个mat数据文件组成压缩包整体仅12KB代码简练且模块化既能独立运行单个模型也可串联成完整实验流程适合学习大气延迟建模、验证伪距修正算法或搭建导航仿真环境。目前已有185人学习下载。资源内含Klobuchar电离层模型与Hopfield对流层模型的实现并配套卫星轨道计算、ECEF/GPS坐标转换、方位仰角计算等工具函数可直接运行Experiment3主流程查看延迟仿真与校正效果。通过可视化卫星轨迹和内置实测数据能直观分析不同仰角下的延迟量变化帮助理解大气折射对定位精度的影响也适用于精密授时、多星座组合定位等扩展场景为精度优化提供可复现的实验基础。1. 电离层误差延迟到底有多大先让仿真目标落地单频GNSS接收机在白天和夜里定位结果能差出十几米很多时候不是接收机坏了而是电离层误差延迟在捣鬼。电离层里自由电子的数量随太阳照射剧烈变化信号穿过它的路径延时也就跟着变——这正是标题里 Ionospheric error delay 要解决的问题。把这个延时和配套的对流层仿真做明白你就能在定位解算前先把大气这条链路算准。压缩包名字后面那一串后缀看着像下载站自动补的乱码但剥开看里面无非三件事电离层延时模型怎么选、对流层怎么仿真、延时测量结果怎么验证。这篇笔记打算按这个顺序把可复现的路径讲清楚。无论你是在做接收机算法验证还是在搭导航仿真链路这套方法都值得先跑通一次。后面每一节都给出能直接跑的代码和参数边界你可以把坐标、时间和气象参数换成自己手里的数据。2. 电离层延时模型怎么选单频经验模型与双频实测的组合抉择2.1 电离层延时为什么能到几十米TEC与延时换算关系电离层分布在距地面约 60 到 1000 公里的范围太阳紫外线把中性大气电离出大量自由电子。信号在电离层里传播时群速度变慢、相速度变快于是伪距测量值多出一段延迟载波相位测量值反而少一段延迟。这个延时的量级怎么估先看总电子含量 TEC单位是 TECU1 TECU 等于每平方米 10 的 16 次方个电子。延时和 TEC 的换算关系很简单d 40.3 × TEC / f²。在 GPS L1 频点上1 TECU 大约对应 0.162 米。中纬度地区白天天顶方向的 TEC 通常在 30 到 50 TECU换算成延时就是 5 到 8 米如果卫星仰角低到 30 度以下路径斜穿电离层倾斜因子放大之后延时能到 15 到 25 米。太阳活动高年天顶 TEC 能冲到 100 TECU 以上这时候单频接收机的误差已经和星历误差不在一个量级了。搞懂这个量级关系你才能判断自己的场景该用哪种模型。如果你做的是双频接收机用无电离层组合实测改正最干净如果你手里只有单频数据或者在做仿真链路Klobuchar 经验模型就是最实际的选择。下面两节把这两条路都拆开讲。2.2 Klobuchar模型单频设备最常用的8参数经验模型Klobuchar 模型是单频 GNSS 接收机里最普及的电离层延时改正方法广播星历里直接携带 8 个系数接收机不用联网、不用外部数据就能算。它的核心假设是夜间电离层延时近似为一个常数基准约 5 纳秒白天在地方时 14 点附近出现余弦形状的峰值振幅和周期由 8 个系数按穿透点的地磁纬度展开得到。这套模型在中纬度地区的改正效果大约能消除 50% 到 60% 的电离层延时低纬度和太阳活动剧烈期效果会明显变差。它本质上是一个统计模型不追踪实时电离层变化所以别指望它在赤道异常区或磁暴期间给你精确结果。但在工程仿真里它的价值恰恰在于参数少、可复现、结果稳定——你给同一组输入任何机器上跑出来都一样。用 Klobuchar 模型需要准备的输入有接收机的大地纬度、经度、海拔卫星的仰角和方位角当前 UTC 时间以及星历里的 8 个系数。注意这里有个容易忽略的细节模型公式用的是地心纬度和穿透点处的地磁纬度不是直接拿接收机纬度代入很多人在这里翻车后面避坑章节我会专门说。2.3 双频消电离层组合什么时候该换掉经验模型双频接收机的做法完全不同。电离层延时和频率的平方成反比两个频点上的伪距测出来之后做一个线性组合就能把一阶项消掉。GPS L1/L2 的组合系数是固定的P3 (f1² × P1 − f2² × P2) / (f1² − f2²)。这个组合后的值几乎不含一阶电离层延时残留下来的只有高阶项和噪声。但双频组合不是免费的午餐。消电离层的同时会把伪距噪声放大两到三倍载波相位组合的噪声放大得更厉害。所以如果你的场景是短基线、低动态、又不追求厘米级精度用 Klobuchar 粗改正反而比双频组合更平滑。我在给某图像处理 Demo 做时间同步的时候就遇到过这个选择单频模块配上 Klobuchar 改正比强行用双频组合后噪声反而小定位抖动也小。所以选型逻辑很清楚做仿真、做单频接收机、做低成本设备走 Klobuchar做测量级接收机、做长基线解算、做精密钟差估计走双频实测。标题里那个延时测量的需求如果是验证接收机内部的时延链路Klobuchar 足够如果是做高精度时间传递必须上双频或外部电离层格网。3. 把电离层仿真写成代码Klobuchar与穿透点计算的完整实现3.1 仿真输入接收机坐标、UTC时间和卫星仰角方位角仿真第一步是准备干净的输入。接收机坐标用大地经纬度和海拔单位分别是度和公里卫星的仰角和方位角可以从星历外推获得也可以直接用一段实测数据的头文件里读。UTC 时间要精确到秒因为 Klobuchar 模型对地方时很敏感差 15 分钟经度就是 3.75 度穿透点就换位置了。我这里随便填一组中纬度示意坐标纬度 31.5 度、经度 102.3 度、海拔 0.4 公里卫星仰角 35 度、方位角 210 度、UTC 时间 14.5 小时。实际使用你只需要替换成自己的接收机真实参数。最容易被忽略的是仰角小于 10 度的卫星——这类卫星信号穿过电离层的路径极长延时误差可能到 30 米以上模型本身的误差也跟着放大仿真时建议直接设一个遮蔽角。3.2 Klobuchar延时计算的最小可运行Python实现下面这段代码实现了完整的 Klobuchar 电离层延时计算输入为接收机经纬度、卫星仰角方位角、UTC 小时以及广播星历中的 8 个系数输出为群延迟单位米。代码里保留了穿透点计算和地磁纬度换算这是很多人只抄公式不写全的部分。import math def klobuchar_ionospheric_delay(lat_deg, lon_deg, el_deg, az_deg, utc_hour, alpha, beta): 计算电离层群延迟单位米适用于单频GNSS信号。 参数: lat_deg / lon_deg: 接收机大地纬度/经度度 el_deg / az_deg : 卫星仰角/方位角度 utc_hour : UTC小时浮点数如14.5 alpha / beta : 广播星历中8个Klobuchar系数 # ---- 1. 穿透点IPP的地心角弧度近似公式 ---- # Klobuchar原论文中的经验式仰角单位为度 psi 0.0137 / (el_deg 0.11) - 0.022 az_rad math.radians(az_deg) lat_geo_rad math.radians(lat_deg) # 大地纬度转地心纬度地球椭球扁率引起的修正 lat_geoc_rad lat_geo_rad - 0.0053 * math.sin(2.0 * lat_geo_rad) # 穿透点地心纬度与经度 lat_ipp_rad lat_geoc_rad psi * math.cos(az_rad) lon_ipp_rad math.radians(lon_deg) ( psi * math.sin(az_rad) / math.cos(lat_ipp_rad) ) # 边界约束防止极区外推异常 lat_ipp_rad max(-math.pi/2, min(math.pi/2, lat_ipp_rad)) lon_ipp_rad (lon_ipp_rad math.pi) % (2*math.pi) - math.pi # ---- 2. 穿透点地磁纬度弧度 ---- lat_m_rad lat_ipp_rad 0.064 * math.cos(lon_ipp_rad - 1.617) # ---- 3. 穿透点本地时间峰值在14:00 ---- t_local_hour (utc_hour lon_deg / 15.0) % 24.0 t_sec t_local_hour * 3600.0 - 50400.0 # 50400秒 14:00 # ---- 4. 振幅A与周期P按地磁纬度三阶多项式展开 ---- A (alpha[0] alpha[1]*lat_m_rad alpha[2]*lat_m_rad**2 alpha[3]*lat_m_rad**3) P (beta[0] beta[1]*lat_m_rad beta[2]*lat_m_rad**2 beta[3]*lat_m_rad**3) if A 0.0: A 0.0 if P 72000.0: P 72000.0 # 周期下限保证余弦形状不过度压缩 # ---- 5. 垂直方向延时秒与倾斜因子 ---- x 2.0 * math.pi * t_sec / P if abs(x) math.pi / 2.0: # 余弦峰值附近的截断四项展开 v_delay 5e-9 A * (1.0 - x*x/2.0 x**4/24.0) else: v_delay 5e-9 # 夜间退化为常数基准5ns # 倾斜因子Klobuchar原式比1/sin(仰角)更稳健 slant_factor 1.0 2.0 * ((96.0 - el_deg) / 90.0) ** 3 delay_m v_delay * slant_factor * 2.99792458e8 # 秒转米 return delay_m if __name__ __main__: # 示意坐标中纬度某点非真实台站 lat, lon, h 31.5, 102.3, 0.4 el, az, utc_hour 35.0, 210.0, 14.5 # 注意这组alpha/beta只是示例值真实使用必须从广播星历读取 alpha [0.8382e-8, -0.5960e-8, -0.1490e-7, -0.5960e-8] beta [0.1272e6, -0.6554e5, 0.9830e5, -0.6554e5] ion_delay klobuchar_ionospheric_delay( lat, lon, el, az, utc_hour, alpha, beta) print(f电离层延时: {ion_delay:.3f} 米)代码里有两个地方特别说明。穿透点的地心角 psi 用的是 Klobuchar 论文里的经验近似式它把电离层当作 350 公里高度上的一个薄壳这个近似在仰角 10 度以上精度尚可低于 10 度误差快速增大。另一个是倾斜因子我用了原论文的立方形式它在仰角接近 0 度时收敛为一个有限值不会像 1/sin(仰角) 那样发散这在后面的避坑章节会展开。运行上面代码会输出大约 13 到 15 米的结果具体数值取决于你用的系数。如果你手头没有星历系数可以先用示例值跑通流程但正式仿真前务必换成真实星历参数否则结果没有工程意义。3.3 仿真结果自检白天和夜晚、高仰角和低仰角该差多少代码能跑通只是第一步结果对不对需要做几个常识性自检。第一个检查把 UTC 时间从 14.5 改成 2.5其他参数不变白天结果应该比夜晚大 3 到 5 倍因为模型里夜间退化为 5 纳秒基准。如果你的仿真结果白天夜晚几乎一样说明地方时计算有问题多半是经度除以 15 那一项没处理。第二个检查把仰角从 35 度改成 75 度延时应该明显变小改成 10 度延时应该显著变大。Klobuchar 倾斜因子在 75 度仰角时接近 1在天顶方向延时约等于垂直延时。低仰角结果的增幅如果不符合直觉检查穿透点纬度和地磁纬度是否被边界约束截断了。第三个检查给同一组输入换一组差异明显的 alpha/beta 系数输出应该跟着变。如果系数改了输出纹丝不动大概率是振幅 A 被负值截断成了 0或者周期 P 恒等于下限 72000 秒。这三个自检做完你的仿真链路基本就立住了。4. 对流层仿真和电离层配套的湿延迟与干延迟建模4.1 对流层仿真模型选型Saastamoinen与Hopfield怎么挑对流层延迟不像电离层那样有剧烈的昼夜变化但它同样会带来 2 到 25 米的误差取决于卫星仰角。对流层延时分成干延迟和湿延迟两部分干延迟由大气中干燥气体引起天顶方向大约 2.3 米变化缓慢可以用地面气压精确建模湿延迟由水汽引起天顶方向通常只有 0.1 到 0.4 米但水汽时空变化剧烈是建模误差的主要来源。工程仿真里最常用的两个模型是 Saastamoinen 和 Hopfield。Saastamoinen 模型直接用地面气压、温度、水汽压计算天顶干延迟和湿延迟形式简单参数少适合大多数地面场景。Hopfield 模型把大气分成两层用折射率积分计算在海拔较高的测站表现更好。我的习惯是海拔低于 1 公里的场景无脑用 Saastamoinen高海拔或飞机平台再用 Hopfield。选模型之外还有个参数来源问题。真实工程里你未必有测站气象传感器这时候可以用标准大气模型估算气压和温度海平面气压 1013.25 hPa温度 15 摄氏度气压随高度按指数衰减温度按每公里 6.5 摄氏度递减。这样算出来的干延迟误差不大湿延迟误差会大一些因为它对水汽变化太敏感了。4.2 干延迟与湿延迟的Python计算气象参数从哪来下面这段代码实现 Saastamoinen 模型的天顶延迟计算和倾斜映射输入是接收机纬度、海拔、卫星仰角、地面气压、温度和相对湿度输出总延迟、干延迟、湿延迟三项。这是对流层仿真最小可用实现。import math def saastamoinen_trop_delay(lat_deg, h_km, el_deg, pressure_hpa, temp_c, rh_pct): Saastamoinen对流层延迟模型。 返回: (总倾斜延迟, 天顶干延迟, 天顶湿延迟)单位均为米 phi math.radians(lat_deg) # ---- 天顶干延迟 ---- # 分母中的cos(2*phi)项是地球椭球和自转的修正 zd_dry (0.002277 * pressure_hpa / (1.0 - 0.00266 * math.cos(2.0 * phi) - 0.00028 * h_km)) # ---- 饱和水汽压Magnus公式单位hPa ---- es 6.105 * math.exp(17.15 * temp_c / (234.5 temp_c)) e rh_pct / 100.0 * es # ---- 天顶湿延迟 ---- zd_wet 0.002277 * (1255.0 / (temp_c 273.15) 0.05) * e # ---- 倾斜映射1/sin(仰角)仰角10度时误差可接受 ---- m 1.0 / math.sin(math.radians(el_deg)) return (zd_dry zd_wet) * m, zd_dry, zd_wet if __name__ __main__: # 示意气象参数海平面附近、夏季白天 lat_deg 31.5 h_km 0.4 el_deg 35.0 pressure_hpa 980.0 # 海拔0.4km附近的气压 temp_c 28.0 rh_pct 60.0 trop_delay, dry, wet saastamoinen_trop_delay( lat_deg, h_km, el_deg, pressure_hpa, temp_c, rh_pct) print(f天顶干延迟: {dry:.3f} 米) print(f天顶湿延迟: {wet:.3f} 米) print(f仰角35度总延迟: {trop_delay:.3f} 米)这段代码里有两个参数值得细说。天顶干延迟公式里的分母包含纬度项和海拔项纬度项来自地球椭球模型修正海拔项是重力位修正缺了它们在高纬度或高海拔测站会有厘米级偏差。湿延迟里的系数 1255 和 0.05 是 Saastamoinen 从气候统计里拟合出来的不要随意改除非你有本地的探空数据支撑。运行结果里天顶干延迟大约 2.2 到 2.3 米湿延迟 0.1 到 0.3 米35 度仰角的总延迟在 4 米左右。你手里的气象参数不同结果会变但干延迟不可能低于 2 米湿延迟很少超过 0.5 米——如果结果超出这个区间检查单位最常见的是把气压写成了千帕或者把相对湿度直接当水汽压用了。4.3 电离层与对流层延时测量合并总误差预算怎么算把电离层和对流层仿真合在一起才算把大气误差链路补齐。总大气延时等于电离层延时加对流层延时两者物理机制独立直接相加即可。在实际延时测量场景里我一般先把各误差源列成一张预算表再决定用什么样的观测弧段和模型组合。从这张表能直观看出仰角越低大气总延时越大而且对流层和电离层的倾斜因子都在放大。我做延时测量时习惯把最低仰角设在 10 度低于这个角度直接丢弃数据因为模型误差增长太快。把两个模型串进数据处理链路时要注意顺序先算电离层穿透点再算对流层天顶延迟两者不要互相污染参数。5. 电离层对流层仿真避坑指南坐标、高度与映射函数的五个翻车现场5.1 大地纬度和地心纬度混用穿透点位置偏了好几度现象仿真的电离层延时结果在低纬度地区明显偏大和实测数据对不上。原因Klobuchar 模型公式里穿透点纬度用的是地心纬度很多人直接把接收机的大地纬度代入两个纬度在中纬度差 0.2 度左右低纬度差更大穿透点位置跟着偏。解决在代码里保留地心纬度转换那一步lat_geoc_rad lat_geo_rad − 0.0053 × sin(2 × lat_geo_rad)。这个转换值是固定常数不会随时间变化属于一次性修正。5.2 电离层单层高度取350还是450穿透点计算跟着漂现象同一组卫星数据用不同参考代码算出的延时差 5% 到 10%。原因电离层薄壳模型假设电子集中在一个高度面上这个高度在 KLB 原始论文里取 350 公里但有些后续实现用 400 或 450 公里。高度不同地心角 psi 的计算公式就不同穿透点位置随之漂移。解决固定用 Klobuchar 原论文的 psi 经验式不要混用其他薄壳高度的公式。如果你是在做科研对比务必在文档里写明你用的薄壳高度。5.3 低仰角下倾斜因子发散映射函数在5度仰角直接翻车现象仰角低于 10 度时对流层延迟计算结果出现几十米甚至上百米的异常跳变。原因1/sin(仰角) 这个映射函数在仰角趋近 0 度时数学上发散而真实大气的球面曲率限制了路径增长。解决仰角大于 10 度用 1/sin(仰角) 没问题低于 10 度换用 Klobuchar 倾斜因子形式或者 NMF 映射函数。另一个工程习惯是直接设遮蔽角仿真链路里 5 度以下的卫星数据不进解算。5.4 延迟单位把TECU当成米数量级错了三个档现象有人把 TECU 数值直接当延时米数用结果电离层延时算出来是 30 米实际应该是 5 米。原因TECU 是电子总量单位必须除以频率的平方再乘 40.3 才得到秒再乘光速才是米。解决代码里统一用秒做中间单位最后一步再转米如果你用现成库函数先确认库的返回值是秒还是米。这个坑我把自己的血泪经验放这里某跨平台系统调试时电离层改正量级错了三倍查了两个小时才发现单位换算写在了一个不常走的分支里。5.5 仿真时间用UTC还是本地时时间差给延时引入额外偏差现象电离层延时结果与真实值偏差很大峰值位置对不上。原因Klobuchar 模型里的 t 是穿透点的地方时不是 UTC 时间。如果你直接用 UTC 小时代入忽略经度修正模型峰值会被平移到错误的时间点上。解决代码里做一步 t_local (utc_hour lon_deg / 15.0) % 24.0其中 lon_deg 用东经为正。经度差 30 度地方时就差 2 小时延时峰值偏差可能到 1 到 2 米这在延时测量里是不可接受的。6. 给延时测量结果做校准用最小二乘反推模型系数6.1 用最小二乘反推Klobuchar系数如果你手里有一批实测的电离层延时数据——比如从双频接收机或者电离层格网产品里提取的延时序列——就可以反过来校准 Klobuchar 模型的 8 个系数让模型在你所在的区域表现更好。做法是把前面写的 klobuchar_ionospheric_delay 函数当成一个非线性模型用最小二乘拟合它的参数。下面是一个简化示例import numpy as np from scipy.optimize import least_squares # 观测数据格式: [lat, lon, el, az, utc_hour, delay_m] # 这里从文件读入实际使用换成你自己的实测数据 obs np.loadtxt(ion_delay_obs.csv, delimiter,) def residual(theta, obs): alpha theta[0:4] beta theta[4:8] res [] for row in obs: d_pred klobuchar_ionospheric_delay( row[0], row[1], row[2], row[3], row[4], alpha, beta) res.append(d_pred - row[5]) return np.array(res) # 初值用广播星历常见的默认量级不能全给0 theta0 np.array([0.8e-8, -0.6e-8, -1.5e-8, -0.6e-8, 1.3e5, -0.7e5, 1.0e5, -0.7e5]) result least_squares(residual, theta0, methodlm) print(拟合后alpha:, result.x[0:4]) print(拟合后beta :, result.x[4:8])拟合有两个注意点。初值不能给零向量否则雅可比矩阵退化优化直接原地不动周期下限 72000 秒的约束在拟合过程中同样生效这是物理约束不要放开。另外拟合数据要覆盖至少一个完整的昼夜周期最好包含高仰角和低仰角卫星否则拟合出的系数会偏向你数据里占比最多的仰角区间。6.2 校准后的验证习惯拟合完成后别急着用先拿一段未参与拟合的数据做验证。比较校准前后模型对这段独立数据的均方根误差如果校准后误差没有明显下降说明你的实测数据本身有问题或者数据覆盖的时空范围太窄。我习惯把拟合数据的残差图打出来看残差是否随仰角或地方时呈现系统性形状——如果残差还在规律性漂移说明模型形式不够用该考虑换网格产品或往上加高阶项了。这个校准流程是我做延时测量时最常用的一招。每到一个新项目点先攒一周的实测数据拟合一组本地化的 Klobuchar 系数再进正式测量流程。效果通常比直接用广播星历系数好不少尤其是在低纬度和太阳活动爬升期。如果你做的是实时系统可以把这套拟合放到后台每小时跑一次定时更新系数文件让模型跟着电离层走。希望帮到你。本文还有配套的精品资源点击获取