简介:RTKLIB是开源GNSS数据处理软件库,这份压缩包提供的是作者修改后的精密单点定位(PPP)功能代码,面向GNSS定位技术学习者和从事北斗、GPS数据处理研究的开发者。原始RTKLIB对北斗(BDS)和GPS的多频PPP支持有限,该代码重点补齐了三频、双频及组合PPP解算能力,便于用户利用北斗和GPS观测数据进行高精度定位实验。包内共37个文件,以C语言源码为主,包括36个源文件和1个头文件,整体大小仅496KB,覆盖RTCM解析、星历计算、PPP解算等关键模块,结构清晰,适合直接阅读和二次编译。该资源已有4497人浏览学习,受到不少RTKLIB二次开发者的关注。读者能从这套代码中获得可运行的RTKLIB改进实现,理解北斗与GPS组合PPP的算法流程,包括L1/L2/L5三频信号处理、双频电离层改正、卫星钟差和大气延迟误差修正等;通过对比官方原版代码,还能深入掌握多系统精密单点定位的工程实现细节,适合用于课程设计、算法研究或工程项目入门。 做高精度定位的人,手里基本都有一份RTKlib源码。这两年问得最多的一个问题,不是“RTKlib怎么装”,而是“RTKlib的PPP到底能不能直接用北斗”。我自己的答案是:能用,但默认版本对北斗的支持非常勉强。如果你想实现“精密单点定位PPP,并且同时支持北斗和GPS”,那你大概率需要自己动手改代码。这篇我把自己实际改过的思路、踩过的坑、跑通的配置全部写出来,给正在折腾的朋友一个参考。
RTKlib本身是开源里做PPP非常成熟的框架,但“支持北斗”和“支持好北斗”完全是两回事。默认版本里北斗的观测值映射、信号频率定义、码间偏差处理都不完整,尤其是BDS-3的B1C、B2a这类新信号,经常解析出来直接被丢掉。所以真正能落地跑双系统PPP的版本,基本都是改过的。这篇文章就是围绕“改了什么、为什么改、怎么跑通”来展开。
1. 为什么默认RTKlib跑不好北斗PPP
1.1 先说清楚RTKlib的PPP能力边界
RTKlib目录下有个RTKPOST,是后处理工具,PPP功能主要落在pntpos.c和pppos.c这两个文件里。它本身支持GPS、GLONASS、Galileo、QZSS,北斗也有对应代码,也就是SYS_CMP。从架构上说,RTKlib设计得相当灵活,你只要给它观测值、精密星历、钟差这些输入,它就能在伪距和载波相位上做滤波解算。
但实际用下来,RTKlib对北斗的支持更像“能用,但没优化”。我碰到最直接的问题:同一组RINEX数据,GPS参与解算一切正常,加入北斗后要么卫星数量异常,要么残差明显偏大。排除了数据源质量问题之后,基本可以断定是代码层面的映射和处理不到位。
这里的核心矛盾在于,RTKlib 2.4.3这套代码诞生的时间比较早,当时BDS-3还没大规模组网。所以它的信号定义表里,北斗部分其实是按照BDS-2的B1I、B2I、B3I这些老信号来写的。现在采集到的数据里B1C、B2a、B2b这些新信号已经很常见,默认代码根本没处理。
1.2 北斗支持真实的短板在哪里
我总结下来,默认RTKlib跑北斗PPP有三个明显的短板:
第一,信号与频率映射不完整。RTKlib在rtklib.h里用NFREQ定义参与解算的最大频率数,代码里观测值映射函数obs2code()和code2obs()只覆盖了北斗少数几个信号。B1C和B2a这种新信号如果出现在RINEX文件里,解析时会走default分支,最后变成“未知码”,参与不了解算。
第二,DCB码偏差没有内置修正方案。PPP解算用的是精密钟差,这个钟差通常基于无电离层组合或者特定频率定义。BDS-2和BDS-3之间、不同频率之间的码间偏差如果不处理,伪距观测值会带一个系统性的硬件延迟,直接反映到定位结果上就是位置偏差好几米甚至更大。RTKlib本身没有一个自动下载和应用DCB文件的机制,需要你在数据准备阶段自己处理。
第三,时间系统差别容易被忽略。北斗的时间基准是北斗时(BDT),和GPS时差了整整14秒,而且BDT不做闰秒调整。RTKlib里虽然提供了bdt2gpst()这个函数,但实际读取RINEX时,如果接收机端或者转换程序没处理好,这个偏移量会导致钟差匹配对不上,解算结果自然是一团糟。
所以,所谓“更改后代码”,核心就是把这些缺失的环节补上。补完之后,GPS和北斗才能真正同时在同一个PPP滤波里互相配合。
2. 改代码前必须搞懂的三个关键点
2.1 PPP主流程和数据结构
先看整体流程。RTKlib处理PPP时,大致是:读取观测文件,得到obs_t结构体,存放每个历元所有卫星的伪距、载波、多普勒等观测值;读取导航文件和精密星历,填充nav_t结构体;然后进入pntpos(),做过粗定位、选星、构建观测方程;最后进pppos()做扩展卡尔曼滤波,估计接收机位置、钟差、对流层湿延迟、电离层参数和模糊度。
你在改代码时,主要打交道的就是obs_t和nav_t这两个结构体。obs_t里的每个obsd_t有一条卫星的观测值,里面有卫星编号sat、观测码code、伪距P、载波L、多普勒D等。nav_t里则存放星历、精密星历、电离层参数、UTC参数等。
如果要支持北斗,你需要保证两个层面的贯通:一是观测值层面,RINEX里的BDS信号能被正确解析到obsd_t里,并且被当成“可用观测值”;二是导航星历层面,精密星历文件里的BDS轨道和钟差能被读进nav_t的peph结构体里。这两个环节任何一处断了,北斗卫星就进不了解算。
2.2 卫星系统和频率编号的对应关系
RTKlib里卫星系统用一组宏定义,比如SYS_GPS、SYS_GLO、SYS_GAL、SYS_CMP等。北斗那个宏在代码里是SYS_CMP,不是很多人以为的SYS_BDS。这个细节很容易引起困惑,因为网上很多资料直接写SYS_BDS,但你拿这个宏去编译根本过不了。
频率编号也有讲究。rtklib.h里,freq_t数组定义了不同系统的频率值,比如GPS L1是1575.42MHz,BDS B1I是1561.098MHz,B3I是1268.52MHz。观测值代码里,比如GPS L1对应的码是“1C”,BDS B1I对应“2I”,B3I对应“6I”。修改代码时,一个常见的任务就是在obs2code()里把北斗新信号和已有频率编号对应起来,比如B1C对应L1频点、B2a对应L5频点。
改代码前,先在rtklib.h里确认你手上版本的NFREQ和信号定义表,再对照你RINEX数据里实际出现的北斗信号类型。别上来就改,否则很容易出现“编译过了但观测值全是0”的诡异情况。
2.3 精密星历和DCB怎么进解算
PPP定位必须使用精密星历和精密钟差。RTKlib支持读取SP3格式的精密轨道文件(.sp3)和钟差文件(.clk),这两个文件通常从IGS或者MGEX分析中心下载。北斗的精密产品现在公开渠道已经很多,比如武汉大学、德国地学中心等机构发布的multi-GNSS产品,里面就包含BDS-2和BDS-3的轨道钟差。
但即便你准备了精密星历,还是绕不开一个实际问题:代码在peph2pos()里对每颗卫星找精密星历的位置,如果星历文件里该卫星的位置不在参考时刻附近,或者钟差类型不匹配,RTKlib会选择插值失败然后跳过这颗星。所以,精密星历和观测数据的时间跨度要匹配,我一般会下载覆盖观测时段前后至少半小时的产品。
DCB文件的处理则更麻烦。RTKlib默认并不读取常见的DCB格式。你需要自己做一次转换,把DCB改正值加到伪距观测值上,或者写成RTKlib能识别的格式。实际操作中,我见过有人直接在观测值上做减法:
/* 以BDS B1I/B3I双频组合为例 */ P1_corrected = P_B1I + dcb_B1I_B3I * 0.5; P3_corrected = P_B3I - dcb_B1I_B3I * 0.5;这个公式的含义是把码间偏差按频率关系分配到两个伪距上。具体DCB数值可以从CAS或者DLR提供的月文件中读取。这个步骤不做,北斗的伪距残差会明显偏大,最终位置结果很不可靠。
3. 代码实际改造步骤
3.1 第一步:补齐北斗信号和观测值映射
我用的源码版本是RTKlib 2.4.3。打开rtklib.c,找到obs2code()函数,这里有一个很大的switch语句。你会看到GPS、GLONASS、Galileo都列出了很多信号,北斗部分却只有几行。我的做法是在BDS分支里补上B1C、B2a、B2b这些新信号和对应频率的映射:
case SYS_CMP: if (freq==1) return "2I"; /* B1I */ if (freq==2) return "7I"; /* B2I */ if (freq==3) return "6I"; /* B3I */ if (freq==5) return "1X"; /* B1C */ if (freq==7) return "5X"; /* B2a */ break;注意,这里的freq编号必须和rtklib.h里定义的频率数组下标一致。改完obs2code()之后,还要同步改code2obs(),它是反查函数,负责把RINEX文件里的观测码转换成内部的频率和系统编号。这两个函数必须成对修改,不然解析链路会中断。
另一个容易遗漏的地方:rtklib.h里NFREQ和NFREQG这两个宏控制参与解算的频率数量。如果你想让B1C、B2a参与双频组合,很可能需要把NFREQ从3改成更大的值,同时检查所有用NFREQ做循环的地方,比如观测方程里对频率的遍历。
3.2 第二步:开启GPS和北斗双系统联合解算
代码层面确认了北斗观测值能被解析,接下来就是让解算器真正把北斗卫星纳入观测方程。RTKlib里,处理选项prcopt_t结构体中有一个navsys字段,用来控制参与定位的卫星系统。
我一般在代码初始化时写成:
prcopt.navsys = SYS_GPS | SYS_CMP; /* GPS + 北斗 */如果你用的是RTKPOST图形界面,这一步对应勾选“GPS”和“BDS”。但注意,很多人死在这:界面勾选了BDS,代码里却没把SYS_CMP加进去,因为界面的选项和内部prcopt_t不一定是直接映射。所以建议你直接在代码里写死后编译,这样最可控。
选星逻辑同样要检查。RTKlib在selstar()函数里按卫星系统筛选可用星。如果navsys里没包含北斗,后面定位方程根本不会遍历到北斗卫星。另外,pppos()里的遍历范围是MAXOBS,只要北斗观测值能正确进入obs_t,这部分通常问题不大。
3.3 第三步:处理BDT和GPST的时间基准
这一步是我最想强调的。很多改完代码跑出来的位置偏差几十米,第一时间怀疑是代码逻辑问题,其实只是时间系统没对齐。RINEX 3.03之后,北斗观测数据的时间系统默认是BDT,而精密星历和钟差一般使用GPST或者对应的系统时间。
RTKlib在读RINEX文件时,对BDS观测值会调用bdt2gpst()做一次转换,把时间统一到GPST。但如果你用的数据源是某些接收机厂商自己的格式,转换之后的RINEX文件里时间系统标签可能会混乱。我的经验是,在处理完RINEX之后,先抽查几个历元:
extern gtime_t bdt2gpst(gtime_t t) { return timeadd(t, 14.0); /* BDT比GPST快14秒 */ }确认转换正确了再往下走。同时,精密星历文件SP3里的第一行有时间系统标识,如果你发现下载的精密星历是BDT,而你的程序按GPST处理,也需要在读取时做相应偏移。这条做不对,后面的收敛和精度无从谈起。
3.4 第四步:编译和常见编译错误
RTKlib支持Windows和Linux编译。Windows下我用Visual Studio打开sln工程,直接编译RTKPOST和RTKLIB库;Linux下用make,编译整个lib。改完代码后,最容易遇到两类编译错误。
第一类是宏定义或结构体字段名拼写错误。比如SYS_CMP写成SYS_BDS,会报未定义标识符。第二类是数组越界警告。因为修改了频率定义和信号映射后,某些循环的边界条件没跟上,比如obs2code()里对freq的默认值返回NULL,上层没有做空指针判断,运行到北斗新信号时就崩溃或者跳过。
我的建议是,每一步修改后先编译一次库,再编译调用程序,确认无误后再继续下一步。不要一次改动太多,否则排查成本很高。编译通过之后,先用一份短时长的静态观测数据做验证,确认北斗卫星能被识别、参与解算,再跑长时间动态数据。
4. 跑通PPP需要准备的数据与参数
4.1 RINEX观测数据怎么来
改好代码后,数据准备的质量直接决定定位结果。PPP最少需要双频伪距和载波相位观测值。对于GPS,至少要有L1和L2;对于北斗,至少要有B1I和B3I,或者B1C和B2a。市面上常见的测量型接收机,比如中海达、华测、司南等,都支持输出包含北斗信号的RINEX 3.02以上格式。
如果你手头没有接收机,可以到IGS数据中心下载一些MGEX测站的静态观测数据来测试。我经常用BKG或者CDDIS下载某个测站的RINEX 3文件。注意,下载时要选同时包含GPS和BDS数据的文件,很多老测站只保留了GPS观测值。
4.2 精密星历、钟差和DCB文件
PPP需要的第二个数据源是精密产品。IGS的最终产品里北斗覆盖不够全,所以一般推荐下载MGEX分析中心的产品。武汉大学的WUM产品、德国地学中心的GBM产品都是公开可下载的。
这些产品里包含SP3精密轨道文件和CLK钟差文件。下载时重点看文件的星历覆盖时间段,一定要覆盖你的观测时段,否则插值外推会导致很大的轨道误差。至于DCB文件,我一般在CAS的ftp下载BDS DCB月文件,然后自己写个小脚本把需要的那天数据提取出来,应用到观测值上。
4.3 RTKPOST里最关键的一组参数
如果你用RTKPOST跑后处理,最影响结果的参数有这些:
- Positioning Mode选PPP
- 卫星系统勾选GPS和BDS,其他系统保持默认也问题不大,但初期建议先只做GPS+BDS方便排查
- 双频组合方式选L1+L2,北斗端也要对应改成B1I+B3I或B1C+B2a,这取决于你的数据
- 高度截止角,静态数据设10度,动态数据可以适当提高
- 对流层延迟选Estimate ZTD,这是PPP的常规做法
- 电离层延迟选IFLC(无电离层组合),PPP收敛会快很多
还有一个容易被忽略的参数:观测值采样率。如果你手里是30秒采样率的静态数据,没问题;如果是1秒采样率的动态数据,RTKPOST运行时对内存和处理时间要求会明显提高,建议先用30秒数据做验证。
5. 实战:GPS+BDS联合PPP跑通全流程
5.1 完整跑一次的操作流程
我以一组静态测站数据为例。数据是某个MGEX测站某天的RINEX 3观测文件,时长24小时,采样率30秒,同时包含GPS和BDS观测值。精密星历用GBM的最终产品,DCB用CAS当天文件。
操作步骤大致如下:
- 把观测文件、精密星历、精密钟差、DCB文件放到同一个目录
- 先用自己写的脚本对RINEX观测值做DCB改正,生成一个新的RINEX文件。这一步,我会在伪距上应用DCB改正,载波相位不动
- 打开RTKPOST,观测文件选择改正后的RINEX,精密星历选择SP3文件,钟差选择CLK文件
- 设置PPP模式,勾选GPS+BDS,选择无电离层组合,对流层选Estimate ZTD
- 点击Execute开始处理,后台会跑出.pos文件
整个处理过程我这边大概需要几分钟。如果你的数据量很大,要注意电脑内存,RTKlib处理长时段数据时内存占用会上升,尤其是双系统观测值多的情况下。
5.2 结果怎么看:位置、残差与收敛
跑完之后打开.pos文件,里面记录了每个历元的XYZ坐标、Q标志、NS(参与解算的卫星数)、GDOP等。我一般会正常化到经纬度和高程,然后画时间序列图。
判断结果是否正常的几个标准:
- NS值在双系统下通常比单GPS多出5到10颗星
- GDOP值明显下降,这是观测量几何结构变好的直接证明
- 位置时间序列在静态场景下,初始阶段波动比较大,应该在30到60分钟内收敛到稳定值
- 北东高三个方向误差,双系统收敛后一般能到厘米级,至少比单GPS快
残差信息在RTKLIB的输出选项里可以打开,最好也看一下。北斗伪距残差如果始终带着固定偏移,基本可以确定DCB没改好,回头检查数据预处理环节。
5.3 我踩过的三个坑
第一个坑是RTKPOST界面勾选了北斗,但核心代码没加SYS_CMP。表现就是定位结果只有GPS卫星参与,NS和单GPS完全一样。我后来习惯先看NS有没有增加,再判断北斗是否真的参与了解算。
第二个坑是SP3精密星历文件时间覆盖不够。我有一回下载了整周的SP3文件,但RTKlib在插值某个时刻的卫星位置时,如果文件里对应卫星的位置在前后各2小时窗口内缺失,插值会失败。表现是某颗北斗卫星一直不进解算,但GPS正常。解决方式是确保精密星历覆盖观测时段,且轨道数据尽量使用最终产品,精度和连续性都更好。
第三个坑,也是最隐蔽的:RINEX数据本身从某些转换工具生成后,BDS观测值的“code”字段和RTKlib内部的obsid不匹配。比如同样的B1I信号,RINEX里标成“2I”,但有些工具标成“2X”。代码里obs2code()返回的是NULL,观测值直接被跳过。解决方法是打印一份观测值解析日志,检查被跳过的卫星,针对性补全映射。
6. 常见问题排查速查表
这里整理了我改代码和跑数据过程中频繁遇到的问题,按“现象、原因、解决方式”整理成了速查表。
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 北斗卫星完全不参与解算 | 代码里navsys没加SYS_CMP | 检查prcopt_t初始化,补上SYS_CMP |
| NS比单GPS没增加 | 北斗观测值在RINEX解析阶段被丢弃 | 打开观测值解析日志,检查code2obs()映射 |
| 北斗残差有固定大偏移 | DCB码偏差未处理 | 在伪距观测值上应用DCB改正 |
| 位置结果初始偏差几十米 | BDT和GPST时间系统未对齐 | 检查bdt2gpst()调用和时间标签 |
| 某颗星经常不收敛 | 精密星历插值窗口不够 | 更换覆盖更全的SP3文件 |
| 编译不通过,提示SYS_BDS未定义 | 宏名写错 | 统一使用SYS_CMP |
| RTKPOST界面卡死或内存暴涨 | 观测文件过大或采样率过高 | 先降采样到30秒再处理 |
| 静态结果持续漂移不收敛 | 对流层或电离层处理策略不对 | 确认开启ZTD估计和IFLC组合 |
排查技巧上,我个人的习惯是“从头卡数据链路”。先确认RINEX能被解析出北斗卫星,再看精密星历能不能给北斗卫星插值出轨道和钟差,最后看解算结果有没有北斗参与。每一步都用日志或输出量来验证,不要凭感觉判断。
在处理过程中还要特别注意输出文件的“Q”标志。Q为1表示浮点解,Q为5表示固定解。PPP通常是浮点解,如果结果里Q标志一直不达标,先别急着怀疑代码,回顾一下数据质量和参数配置。
7. 后续还可以往哪些方向扩展
改完“GPS+BDS”双系统PPP之后,后续可以继续扩展的方向不少。比如加入Galileo和QZSS,多系统联合后卫星数更多,城市峡谷环境下定位连续性会好很多。再比如把代码从后处理挪到实时处理,对接NTRIP协议或者串口数据流,那就是一套实时PPP的原型。
还有一个方向是模糊度固定的实现。RTKlib 2.4.3对PPP模糊度固定支持比较有限,如果你有研究需求,可以基于现有的双系统PPP代码,把宽巷和窄巷小数偏差(FCB)估计加进去,这是一条很值得深入的路。
我个人在实际操作中的体会是:RTKlib这套代码虽然看着老,但胜在结构清晰、可控性强,非常适合做研究和二次开发。改代码支持北斗这件事,最难的不是算法本身,而是把数据链路的每个环节都打通。正因为这样,跑通第一份GPS+BDS联合PPP结果的时候,那种成就感确实很实在。希望这篇内容能帮正在折腾RTKlib的朋友少走点弯路。
本文还有配套的精品资源,点击获取