ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战 简介基于MATLAB开发的Rinex3.02版观测文件o文件读取代码包面向卫星定位导航方向的学习者与研究人员用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件包含两个m脚本、一个19o测试观测文件和一个txt说明文本整体仅4.3MB其中r_rinex302_o_data.m是读取观测文件的主程序UTC_GPSsecond.m负责将UTC时间换算为GPS周秒另有原始测试数据及运行结果便于校验。该代码由作者根据多方资料自行编制测试过程无误虽时间转换函数仍可优化但足以完成历元数计算适合需要读取Rinex3.02文件或二次开发定位程序的MATLAB使用者参考。目前已有5244人浏览学习下载后结合说明文件即可快速上手。 做GNSS数据处理的同学十有八九遇到过这种场景手里拿到一台新接收机的Rinex 3.02格式观测文件o文件想用MATLAB做伪距单点定位或多系统融合实验结果第一步就卡在读取上。MATLAB自带函数并没有直接支持Rinex 3.02网上搜到的大多数老代码又基于2.11一跑就报错。这篇文章就是把这层窗户纸捅破从Rinex 3.02头文件格式到多系统观测值怎么解析给你一套可以“抄作业”的MATLAB读取方案。适合正在做卫星导航数据处理、精密单点定位预处理或者只是想快速把o文件变成可用矩阵的研究生和工程师。1. 项目背景与适用场景1.1 为什么MATLAB读Rinex 3.02会让人头疼RinexReceiver Independent Exchange Format是GNSS领域最通用的数据交换格式几乎每台接收机、每个处理软件都会涉及。Rinex 3.02版本正式支持多系统混合观测比如同一时刻能看到GPS的G、BDS的C、GLONASS的R、Galileo的E卫星标识带系统前缀观测类型也从2.11的C1、L1、P2这类短编码变成了C1C、L1C、C2W、L5Q这种带频率和跟踪模式的组合编码。MATLAB里常见的readmatrix、textscan直接怼上去是读不出来的因为o文件混合了文本头部和数值数据行头部每个类型列数还不一样。更麻烦的是每个卫星系统的观测类型数量不同导致数据记录中每行的观测值个数也各不相同。直接按列宽读或者用%f全量扫描很容易把数据错位。这也是很多人在论坛上问到一半就弃坑的原因。1.2 适合谁读这篇文章不是给纯GNSS理论研究者看的而是给实际要动手处理数据的人写的。如果你在做RTK/PPP预处理、多系统组合定位、接收机性能评估或者只是想把一个站点的o文件快速变成结构体/表格用于后续画图和计算那么下面的内容可以直接套用。代码基于MATLAB的文本读取函数不需要额外工具箱基础版MATLAB就能运行。2. RINEX 3.02 格式必须知道的关键点2.1 头文件版本、系统与观测类型Rinex 3.02的头文件是纯文本行比较重要的是下面几行RINEX VERSION / TYPE版本和文件类型一般在第1行。第1~9字符是版本号第20字符是文件类型O代表观测文件。SYS / # / OBS TYPES系统观测类型列表每个卫星系统一行一个文件可能有多行。TIME OF FIRST OBS、TIME OF LAST OBS起止时间用于粗检查。SYS / # / OBS TYPES这一行的格式很有规律举个例子G 16 C1C L1C D1C S1C C2W L2W D2W S2W C5Q L5Q D5Q S5Q C2L L2L D2L第一个字符是系统标识比如G、R、E、C第7~9字符是当前系统的观测类型数量从第10或11字符开始是具体的类型名称列表。有些软件导出的文件列位会有点偏差所以我个人不太建议去死抠绝对列位置而是用strsplit按空格拆之后再取第一个字符和后面的类型名这样兼容性更高。2.2 数据记录历元与观测值行的对应关系数据部分由若干个历元组成每个历元的第一行是时间信息称为“历元记录行”格式类似2024 07 01 12 00 00.000000 0 8第1~6组数字是年月日时分秒后面跟着历元标志一般0代表正常和卫星数量。紧接着会有多行卫星观测数据每行以卫星号开头例如G01 23012345.678 23012345.678 123456789.012 ... R02 21098765.432 ...卫星号占前3个字符后面的观测值顺序必须严格对应该卫星系统在头文件里SYS / # / OBS TYPES那行给出的类型顺序。这是整个读取过程最容易出问题的地方。2.3 和RINEX 2.11的几个重要区别建议先明确差异避免把旧版思路带过来对比项RINEX 2.11RINEX 3.02卫星标识PRN只有数字如01带系统前缀如G01、R02观测类型C1、L1、P2C1C、L1C、C2W、L5Q系统支持主要是GPS少数扩展GLONASSG/R/E/C/J多系统混合观测类型列表全文件统一一个列表每个系统单独一个列表数据行对齐按观测值个数固定读必须按系统各自类型数对应读正是由于每个系统都有自己的观测类型列表读数据时不能直接按“文件里所有类型的并集”去统一截取第几个字段必须动态判断这个卫星属于哪个系统再找该系统对应的类型顺序。3. MATLAB读取函数设计与实现3.1 设计思路读取逻辑可以拆成两条线头部解析逐行读到END OF HEADER为止把每个系统的观测类型存成结构体比如obsTypes.G、obsTypes.R。数据解析继续逐行处理遇到历元记录就开一个新历元然后按照该历元下的卫星系统去解析后续卫星行。可以用状态机模型理解正常状态下遇到“日期时间卫星数”的行就进入“等待卫星数据”状态遇到卫星号行就解析并填充当前历元遇到下一个历元行再刷新状态。这样不用一次把所有文件读进内存对动辄几十MB的o文件更友好。我实践的结论是用fgetl逐行读是最稳的。textscan虽然也能用但一旦遇到某些接收机输出的非标准空格数很容易把时间字段和观测字段粘在一起。3.2 头部解析核心代码片段如下负责提取版本号和每个系统的观测类型fid fopen(fname, rt); obsTypes struct(); version []; while true line fgetl(fid); if ~ischar(line), break; end if contains(line, END OF HEADER), break; end if contains(line, RINEX VERSION / TYPE) version str2double(strtrim(line(1:9))); end if contains(line, SYS / # / OBS TYPES) sysStr strtrim(line(1)); n str2double(strtrim(line(7:9))); typesTxt strtrim(line(10:60)); typeList regexp(typesTxt, [A-Z][0-9A-Z], match); if isempty(typeList) typeList strsplit(typesTxt); end if ~isnan(n) n 0 typeList typeList(1:min(n, length(typeList))); end obsTypes.(sysStr) typeList; end end这里有个小技巧用regexp(typesTxt, [A-Z][0-9A-Z], match)提取类型名比直接strsplit更能过滤掉多余的空格和换行符。如果某行只有一个类型regexp也能正确匹配。3.3 历元与观测数据解析首先要解决“怎么判断当前行是新的历元”的问题。我用一个正则表达式匹配年份、月、日、时、分、秒的连续数字epochRegex ^\s*\d{1,4}\s\d{1,2}\s\d{1,2}\s\d{1,2}\s\d{1,2}\s\d{1,2}(\.\d)?\s;卫星观测行以字母开头例如G01、R02所以不会被这个正则误判。这个做法在绝大多数Rinex 3.02文件里都成立。判断出是历元行之后再用sscanf(line, %f)把整行的数字都读出来分别对应年月日时分秒、历元标志和卫星数。接下来循环读取后续卫星行并按16字符宽度截取观测值字段obsStr line(4:end); row NaN(1, length(allTypes)); % allTypes是全部系统观测类型的并集 for k 1:length(types) colIdx find(strcmp(allTypes, types{k}), 1); if isempty(colIdx), continue; end startPos (k-1)*16 1; endPos startPos 13; % 前14个字符是观测值 if startPos length(obsStr), continue; end field obsStr(startPos:min(endPos, length(obsStr))); val str2double(strtrim(field)); if ~isnan(val) row(colIdx(1)) val; end end重点说明一下为什么取前14个字符Rinex 3.02观测值字段固定占16个字符其中前14位是数值后2位分别是失锁指示LLI和信噪比SSI。我们读取数值时会把后2位忽略避免LLI或SSI混入数字导致str2double失败。如果某些文件是14字符对齐的紧凑格式这段代码依然能工作因为取不到的就直接保持NaN。3.4 调用方法与输出结构把上面的逻辑组合成一个函数read_rinex302_obs保存为read_rinex302_obs.m后调用方式很简单obs read_rinex302_obs(test.obs);输出结构包含obs.versionRinex版本号obs.obsTypes每个系统的观测类型如obs.obsTypes.Gobs.time每个历元的UTC时间datetime列向量obs.sat每个历元的卫星号cell数组obs.data一个表格列名是合并去重后的所有观测类型每列是一个cell数组内部再按历元保存数值向量比如想要第1个历元、GPS卫星G01的C1C伪距可以通过obs.data.C1C{1}(1)访问。如果后续要做位置解算更常用的做法是把表格展开成“一行一颗卫星”的宽表加一个for循环把cell数组拆开即可。4. 实操验证与结果展示4.1 测试文件读取我自己用一个多系统静态站数据做了测试文件头能看到这样的观测类型列表G 16 C1C L1C D1C S1C C2W L2W D2W S2W C5Q L5Q D5Q S5Q C2L L2L D2L R 8 C1C L1C D1C S1C C2P L2P D2P S2P E 15 C1X L1X D1X S1X C5X L5X D5X S5X C7X L7X D7X S7X C8X L8X D8X C 14 C2I L2I D2I S2I C7I L7I D7I S7I C6I L6I D6I S6I C1I L1I运行读取函数后MATLAB工作区里能看到obs.time的区间覆盖了外业时段obs.data.C1C{1}的长度和该历元GPS卫星数一致。用一个小脚本验证读取结果和原始文件是否相符% 检查第一个历元的卫星数量和卫星号 disp(obs.time(1)); disp(obs.sat{1}); % 检查GPS L1伪距是否有异常 c1c_vals obs.data.C1C{1}; fprintf(C1C非NaN个数: %d\n, sum(~isnan(c1c_vals)));如果C1C列中非NaN个数明显少于该历元GPS卫星数说明观测值字段截取可能偏了或者该文件里某些观测类型没被接收机记录。4.2 数据快速检查还有一个很实用的检查点是卫星号是否带频率槽位尤其是GLONASS。标准Rinex 3.02中GLONASS卫星号同样写成R01、R02但部分接收机在观测值行后面可能会附加频率信息。遇到这种情况时解析卫星号用strtrim(line(1:3))仍然有效但如果文件里出现R01和频率槽作为单独字段的扩展格式就需要在读取后额外提取。多数情况下我们只需要关注前3个字符的卫星号其余扩展字段可以忽略。5. 常见问题与排查技巧实录问题现象可能原因解决办法版本始终为空头部前9列被软件导出成空格或版本号不在第1行用contains(line, RINEX VERSION / TYPE)定位再strtrim(line(1:9))不要假设绝对列位置某个系统没读到观测类型SYS / # / OBS TYPES行第一个字符是空格用strtrim(line(1))而不是line(1)观测值全是NaN字段宽度不对或类型顺序和头文件不一致检查行首是否对齐尝试把字段宽改成14或16分别测试卫星数偏少或历元缺失正则表达式没匹配到某些非标准时间格式如秒数无小数且后面连续空格改用regexp(line, epochRegex, once)并对行首字符是否为数字做二次判断GLONASS卫星号解析异常某些文件里卫星号占了4位如R01统一用strtrim(line(1:min(3, length(line))))再补零几个容易踩的坑单独补充一下不要用textscan(fid, %f%f%f%f%f%f%f%f)直接读整个数据段因为不同系统的观测值个数不一样读到最后会错位。不要简单地把所有观测类型并集后固定成统一列索引就完事必须通过卫星系统的判断去映射。否则GPS的数据可能会被塞到BDS的列里。对空值一定要保留NaN而不是用0填充。后续做伪距定位时0值会直接污染最小二乘过程。genvarname在MATLAB新版里会有warning可以换成matlab.lang.makeValidName效果一样还更安全。最后再分享一个我自己的习惯拿到任何新格式的o文件先不要跑完整代码先用一个几十KB的小文件读完后打印第一个历元的前5行原始数据再打印解析后的obs.data人工对比几列。这一步看起来笨但比任何调试log都管用几分钟就能定位是列宽问题还是类型映射问题。多试几个不同品牌的接收机文件自然就能总结出哪些字段是“弹性”的哪些是必须严格按标准来的。本文还有配套的精品资源点击获取
返回列表