从SPP到组合导航:一个面向科研的GNSS Python代码库实践

张开发
2026/4/13 4:56:27 15 分钟阅读

分享文章

从SPP到组合导航:一个面向科研的GNSS Python代码库实践
软件包下载链接2026.4.12上传状态未审核成功Github仓库链接请点击这里项目背景与设计目标本资源是在原文章《Python伪距单点定位》的基础上进行完善与工程化扩展的版本实现了从原理验证到完整代码框架的升级。早期的文章更多侧重于单点定位原理本身目的是把伪距观测方程、卫星位置计算和最小二乘求解这些核心概念讲清楚。而这一次的实现则更进一步把原来的理论推导和验证性代码整理成了一个真正能够运行、能够批处理、能够做多系统组合实验的 Python 软件包。之所以想把它继续做下去是因为 GNSS 领域里一直存在一个很现实的问题很多内容在纸面上都可以理解但一旦进入工程实现层面就会迅速变得复杂起来。广播星历怎么解算RINEX 观测文件如何组织GPS、Galileo、北斗、GLONASS 混合观测时该怎样建模系统间偏差如何处理这些问题如果没有一个完整的软件载体很难真正建立起“从观测到结果”的直觉。因此这个项目的目标从一开始就不是单纯做一个“能出结果”的脚本而是希望形成一个面向科研的 GNSS 代码库。它基于 Python 编写强调模块化、可读性和可扩展性既能够作为单点定位原理的工程化实现也能够作为后续 PPP、PPK 以及 GNSS-INS 组合导航开发的基础框架。在实现过程中项目参考了 RTKLIB 和 GREAT-PVT 的部分思路。前者提供了大量成熟而稳健的工程经验后者在现代化结构组织和多系统建模方面给了很多启发。这个代码库不是对它们的复刻而是在理解这些工程思想之后用一种更适合科研验证和教学演示的方式进行了重构。SPP定位原理SPP的基本思想是通过多颗卫星的伪距观测来反推出接收机的位置。每一颗卫星提供一个观测方程本质上描述的是“距离 误差”的关系。从原理上讲观测值包含以下几部分接收机到卫星的几何距离接收机与卫星的钟差影响电离层与对流层延迟其他测量误差在简化模型中可以将问题归结为一个非线性方程组。实际求解时需要对其进行线性化处理并通过最小二乘方法进行迭代求解。也就是说从一个初始位置出发不断修正接收机坐标和钟差直到解收敛。在这个过程中最关键的一步是卫星位置的计算。卫星位置需要根据广播星历推算并结合信号传播时间进行修正同时还要考虑地球自转效应。这些内容虽然在理论上比较成熟但在代码实现中往往是最容易出错的部分。软件结构从数据读取到底层求解的分层设计整个工程的组织方式非常清晰它不是把所有逻辑堆在一个脚本里而是划分成了入口层、数据读取层、基础类型层和定位算法层。项目的核心结构大致如下整体结构如下spp_standalone/ ├── run_spp.py ├── test_multisys_spp.py ├── pyproject.toml ├── data/ │ ├── KVH01960.21o │ ├── brdm1960.21p │ ├── KVH0_fix.pos │ └── _smoke.csv └── compass/ ├── core/ │ ├── constants.py │ ├── gnss_types.py │ ├── transforms.py │ └── types.py ├── io/ │ └── rinex_native.py └── gnss/ ├── satellite.py ├── ionosphere.py ├── bias_sinex.py └── spp.py这个结构并不复杂但分工非常明确。run_spp.py是命令行入口负责组织输入参数、读取观测与导航文件、筛选指定系统并将每个历元的定位结果输出为 CSV。test_multisys_spp.py则不直接面向最终用户而更像一个实验脚本用来在同一份 RINEX 数据上测试不同 GNSS 系统组合的解算表现并可与参考轨迹进行对比。真正的底层能力集中在compass包内。core目录定义了坐标转换、常量和数据结构是整个项目的基础层。io目录中的rinex_native.py负责原生解析 RINEX 导航文件和观测文件不依赖第三方专业 GNSS 库。gnss目录则实现了核心算法包括卫星位置计算、电离层改正、码偏差处理以及 SPP 解算器本身。这种划分方式的好处在于数据格式解析、数学基础工具和定位求解逻辑彼此独立。这样一来后续无论是替换观测模型、增加 PPP 功能还是引入 INS 组合滤波都不会破坏当前的代码主干。这种工程化结构其实也是这个项目区别于“课堂作业式脚本”的一个关键点。原理落地SPP 并不只是一个最小二乘公式从定位原理上看SPP 的基本思想并不陌生。接收机通过多颗卫星的伪距观测建立方程组未知量是接收机坐标和接收机钟差。理论上只要有四颗以上卫星就可以求解出一个位置解。但在工程实现里这件事远没有“列方程、求最小二乘”那么简单。伪距观测并不只是几何距离它还叠加了卫星钟差、接收机钟差、电离层延迟、对流层延迟、多系统偏差以及接收机硬件相关误差。也就是说真正进入程序中的观测模型往往已经是一个包含多项改正与偏差吸收的模型而不是最简单的课本形式。这个项目里的 SPP 解算器并不是一个特别简化的教学版本而是明显吸收了工程软件中的一些做法。compass/gnss/spp.py的文件开头就表明它参考了pntpos.c一类实现并且针对多系统情形加入了系统间偏差和频间偏差的考虑。代码中定义了 GPS、GLONASS、Galileo、北斗、QZSS 等系统对应的状态维度同时预留了 BDS 的频间 IFB 状态以及 GLONASS 的按星码偏差建模逻辑。这意味着这个解算器已经不再停留在“只有一个钟差未知数”的最基础 SPP 层面而是在试图向更真实的多系统单点定位靠近。从状态设计上看程序将位置、钟差和系统偏差统一纳入一个参数向量中。代码里定义了NUM_SYS 6并进一步构造了NX_BASE 3 NUM_SYS N_IFB的状态维度这种写法非常能体现工程思路。它意味着在观测方程中不同系统之间不是简单混合使用而是通过附加状态项来吸收系统级别的偏差差异。这也是为什么在多系统组合实验中一些组合表现非常稳定而另一些组合则会因为偏差处理不充分而出现失败或精度下降。卫星位置计算项目中最有工程味的一部分在 GNSS 软件中卫星位置计算往往是最容易“看起来简单、实际上细节很多”的部分。这个项目在compass/gnss/satellite.py中实现了这一部分而且写得相当认真。对于 GPS、Galileo、北斗和 QZSS程序走的是典型的广播星历开普勒轨道解算流程。首先根据星历参考时刻计算时间差再用长半轴、平均角速度和平近点角求解偏近点角之后得到真近点角、纬度幅角、轨道半径和轨道倾角最后投影到 ECEF 坐标系。整个过程的变量组织与广播星历模型是高度一致的属于相当标准的工程实现。但更值得一提的是代码没有停留在“标准 GPS 模型”的层面而是考虑到了不同系统的特殊性。北斗 GEO 卫星采用了特殊的坐标变换逻辑代码中用is_bds_geo单独判断 C01-C05 以及 C59 的 GEO 卫星并对其引入了与 MEO/IGSO 不同的计算流程。这一点很能说明项目不是简单把所有卫星都当成一个模板在算而是意识到了不同轨道类型在广播星历解释上的差异。GLONASS 的实现则更加体现工程细节。由于 GLONASS 广播星历并不是开普勒参数而是位置、速度、加速度形式程序使用了数值积分来外推卫星状态。代码中实现了_deq_glo、_glorbit_glo和_geph2pos使用四阶 Runge-Kutta 的思路逐步推进状态。同时程序还专门实现了 PZ-90.11 到 ITRF2008 的坐标变换_pz90_to_itrf2008_great这一点尤其关键因为多系统混合定位时如果不统一坐标框架后续定位方程会天然带有系统级偏差。这部分代码非常适合在博客中强调因为它恰恰体现出一个 GNSS 工程项目真正“难”的地方并不总在矩阵求逆而在于这些容易被忽略却直接决定结果质量的细节实现。解算器内部不是从零初始化而是尽量让问题更容易收敛单点定位中一个常被忽视的工程问题是初始值。理论上可以从非常粗略的位置出发不断迭代但在多系统和较复杂的误差模型下如果初值太差最小二乘很容易震荡甚至发散。这个项目在这方面做了比较务实的处理。run_spp.py支持用户通过--approx提供 ECEF 概略位置也支持--approx-llh直接输入经纬高。若用户没有显式提供程序会尽量利用 RINEX 头中的概略位置并交给求解器内部逻辑处理。同时在spp.py中还实现了bancroft_solve_lorentz这样的初值求解方法利用 Bancroft 闭式解作为初始定位估计。这说明项目不是简单把初值固定成零向量而是参考了工程软件中更稳妥的初值构造方式。除此之外程序也加入了很多数值稳定性的保护。例如设置了最大迭代次数MAXITR 10给出了残差剔除阈值RES_EXC_TH 30.0还对每次迭代中的位置更新量和钟差更新量设置了上限MAX_DX_POS和MAX_DX_CLK。这些保护项看似不起眼却决定了程序在真实数据上是不是“能稳定跑完”。如果一个定位程序在单次样例上能跑通却在批处理时频繁发散那它就很难称得上是一个可用的软件包。多系统测试不仅看能不能解还看不同组合的行为差异这个项目非常有意思的一点是它没有把测试停留在“跑一个例子看看有结果”这种层面而是专门写了test_multisys_spp.py来做多系统组合实验。这个脚本把 GPS、Galileo、北斗、GLONASS 之间的不同组合都枚举出来包括GE、GR、GC、EC、GRE、GCE、GRCE以及ALL等组合并在同一份观测数据上逐组求解。从代码逻辑看这个脚本并不是只统计是否解算成功还可以加载参考.pos轨迹文件把每个历元的 SPP 结果与参考 ECEF 坐标按周和整秒对齐进一步计算三维误差的均值和 RMS。也就是说这个测试脚本不仅是“跑程序”更是一个评估框架。通过它不同系统组合的效果差异可以被定量展示出来而不仅仅靠主观感受判断。从最终测试结果来看部分组合已经达到了非常稳定的米级精度说明这套 Python 实现已经具备了较好的广播星历单点定位能力。同时也有一些组合表现一般甚至失败这恰恰反映出多系统混合并不只是“卫星越多越好”而是取决于系统间偏差、码偏差和具体观测模型是否处理到位。这样的结果反而更有研究意义因为它表明项目不是一个只会输出漂亮数字的黑盒而是真实暴露了当前建模水平与改进空间。为了验证程序的有效性我选取了一段连续100个历元的数据进行了测试并与参考轨迹进行了对比。实验结果表明在多系统组合情况下程序能够稳定输出定位结果并达到米级精度。例如在GPS与Galileo组合下定位结果稳定误差约为2米左右在引入北斗系统后卫星数量增加但误差略有上升说明不同系统之间的误差特性会影响结果。在多系统联合如GPS北斗Galileo情况下整体解算成功率接近100%且稳定性明显提升。这说明多系统融合对于提升定位可用性具有重要意义。同时也可以观察到在某些组合中如包含GLONASS解算成功率明显下降这反映出系统间偏差问题的存在。在高精度定位中这类问题通常需要通过更复杂的模型进行处理。资源包说明这不是单脚本而是一套可以继续生长的代码库从资源包角度看这个项目已经具备了一个小型研究代码库的雏形。它不是某个单独函数的展示也不是只有一段主程序和几张截图的“半成品”而是一套包含入口程序、底层模块、测试脚本和样例数据的完整软件包。对于希望了解 GNSS 定位工程实现的人来说它提供了从 RINEX 原始数据读取到广播星历卫星位置计算再到伪距观测建模与单点定位求解的完整链条。对于希望做算法扩展的人来说它的模块化结构又足够清晰可以很方便地在现有基础上替换模型、增加状态、引入滤波器甚至接入机器学习方法做误差建模或数据驱动优化。这也是为什么它更适合作为“代码库”而不仅仅是“一个程序”。当前的 SPP 功能只是这个框架的第一阶段而不是终点。从已有的数据结构和模块设计已经可以看出它天然适合向更高精度、更高复杂度的方向生长。未来方向从 SPP 走向 PPP、PPK 和 GNSS-INS如果说当前这套实现解决的是“如何把伪距单点定位做成一个完整的软件包”那么后续要做的就是在这个基础上进一步走向高精度和组合导航。第一步是 PPP。PPP 的引入并不只是把广播星历换成精密星历那么简单它还要求更细致的误差建模、更严谨的时间系统处理以及对钟差、对流层、电离层、模糊度等问题的更完整描述。但从当前的项目结构来看PPP 完全可以作为一个新的求解模块加入而不需要推翻现有框架。第二步是 PPK。相较于单点定位PPK 需要处理基准站与流动站之间的差分观测并引入整周模糊度固定等更复杂的逻辑。这一步会让项目从单接收机模型走向双接收机模型也意味着软件架构必须更强调观测管理和状态传播。最终目标则是 GNSS-INS 组合导航。到了这一阶段GNSS 不再只是一个独立解算器而会成为多传感器系统中的观测源之一。位置、速度和姿态的传播将更多依赖惯导而 GNSS 则负责提供外部约束。届时项目的重点将从“单次定位解算”转向“连续状态估计”并引入卡尔曼滤波或更高级的图优化方法。对于一个以 Python 为基础的研究型框架来说这将使它具备更强的实验价值。结语真正的价值在于把理论与工程之间的那段路走通这个项目最值得记录的并不是最后得到了一组米级的 SPP 结果而是它完整地走通了从原理文章到工程实现的过程。从一篇讲述伪距单点定位思想的文字出发逐步发展成一个具备数据读取、星历计算、误差建模、单点解算和结果评估能力的 Python 软件包这本身就是一次很有意义的实践。在 GNSS 领域里真正困难的地方往往不是某一个公式而是如何把多个看似独立的公式、文件格式和系统差异组织成一套可以稳定运行的软件。这个项目的意义就在于尝试把这条链条清晰地搭建出来。它让 SPP 不再只是一个“课堂知识点”而成为了一个可以继续扩展、继续演化的导航代码框架。从这个角度看它既是对《Python 伪距单点定位》一文的工程化完善也是一套面向未来 PPP、PPK 和 GNSS-INS 组合导航研究的起点。

更多文章