还在为解码发愁吗?面对二进制文件还是无从下手吗?一篇文章帮你搞定。
我们从接收机获取的数据并不是rinex格式的文件,而是NovAtel数据格式的二进制文件。我们需要从文件中提取出我们需要的导航数据,也就是解码的过程。废话不多说,我们直接开始讲解。
一、Binary数据头格式
请不要使用文本文件打开二进制文件,会显示乱码。使用Binary Viewer软件可以打开二进制的文件,大概长下面这个样子。但是我们不需要读懂这个文件。
AA,44,12是三个同步符,读取到这三个同步符代表同步到了消息,就继续往下面读。
匹配同步符的函数如下:
/*匹配同步符的函数*/
static int sync_oem4(uint8_t *buff, uint8_t data)
{buff[0] = buff[1]; buff[1] = buff[2]; buff[2] = data;return buff[0] == OEM4SYNC1 && buff[1] == OEM4SYNC2 && buff[2] == OEM4SYNC3;
}
在数据头里,我们需要以下数据
第五列为每个数据所占的字节,按照字节数提取可。因为我们需要提取不同的数据,有的为整数,有的为浮点数,所以我们先定义一下提取不同类型数据的函数。代码如下:
/*解码辅助函数,用于获取不同类型的数据*/
class DataReader
{
public://读取一个无符号8位整数static uint8_t U1(const uint8_t *p){return *p;}// 获取一个有符号8位整数static int8_t I1(const uint8_t *p){return *reinterpret_cast<const int8_t *>(p);}// 获取一个无符号16位整数 (小端)static uint16_t U2(const uint8_t *p){uint16_t u;memcpy(&u, p, sizeof(u));//复制字节(第一个参数为目标空间的地址,第二个为赋值字节的地方,第三个参数为复制字节的大小)return u;}// 获取一个无符号32位整数 (小端)static uint32_t U4(const uint8_t *p){uint32_t u;memcpy(&u, p, sizeof(u));return u;}// 获取一个有符号32位整数 (小端)static int32_t I4(const uint8_t *p){int32_t i;memcpy(&i, p, sizeof(i));return i;}// 获取一个单精度浮点数 (小端)static float R4(const uint8_t *p){float r;memcpy(&r, p, sizeof(r));return r;}// 获取一个双精度浮点数 (小端)static double R8(const uint8_t *p){double r;memcpy(&r, p, sizeof(r));return r;}
};
DataReader DR;
然后我们就可以开始提取数据了。
/*获取当前解码的历元,判断消息的类型,转到不同的解码函数*/
/*------------------二进制数据解码函数-----------------*/
static int decode_oem4(raw_data *raw)
{double tow;int msg, stat, week;int type = DR.U2(raw->buff + 4);//从第四个字节开始读取一个16位的整数,即为数据的类型编码...raw->type = type;/* crc校验码检查 */if (crc32(raw->buff, raw->len) != DR.U4(raw->buff + raw->len)) {std::cout << "oem4 crc error: type=" << type << " len=" << raw->len << std::endl;return -1;}msg = (DR.U1(raw->buff + 6) >> 4) & 0x3; /* 消息的种类: 00=binary,01=ascii */stat = DR.U1(raw->buff + 13);//时间的状态week = DR.U2(raw->buff + 14);//参考历元的周数/*时间状态和周数检验,stat的值为0-23*/if (stat == 20 || week == 0){cout << "oem4 time error: type=" << type << " msg=" << msg << " stat=" << stat << " week=" << week << endl;return 0;}//week = adjgpsweek(week);//改正周数tow = DR.U4(raw->buff + 16)*0.001;//GPS参考周秒(将ms单位转换成s)raw->time = gpst2time(week, tow);//转换为时间戳Commontime C;time2Commontime(raw->time, C);cout << ">" << " " << C.Year << " " << C.Month << " " << C.Day << " " << C.Hour << " " << C.Minute << " " << C.Second << endl;cout << type << endl;/*检查是否为我们关注的二进制数据*/if (msg != 0){return 0;}/*判断需要解码的数据类型*/switch (type) {case ID_RANGE: return decode_rangeb(raw);case ID_GPSEPHEM: return decode_gpsephem(raw);case ID_BDSEPHEMERIS: return decode_bdsephemerisb(raw);}return 0;
}
crc32为校验码函数,用来检查数据在通信过程中是否存在错误,代码如下:
unsigned int crc32(const unsigned char *buff, int len)
{int i, j;unsigned int crc = 0;for (i = 0; i < len; i++){crc ^= buff[i];for (j = 0; j < 8; j++) {if (crc & 1) crc = (crc >> 1) ^ POLYCRC32;else crc >>= 1;}}return crc;
}
接下来就是解不同类型的消息文件了。我们要求是解RANGE,GPSEPHEM,BDSEPHEMERIS三种类型的文件。对应的消息编码为43,7,1696。
注意,在rtklib里面没有解GPSEPHEM的函数。
二、星历及观测值函数
range格式:
GPSEPHEM格式:
BDSEPHEMERIS格式:
大家也可以自行查阅oem4的官方文档,会有以上说明。
解观测值代码:
static int decode_rangeb(raw_data *raw)
{double psr, adr, dop, snr, lockt, tt;/*伪距,相位,多普勒频移,载噪比,锁定时间,时间差*/char *msg;int i,index,nobs, prn, sat, sys, code, freq, pos;int track, plock, clock, parity, halfc, lli, gfrq;int number_sys;unsigned char *p = raw->buff + OEM4HLEN;//开始截取数据的位置cout<<"decode_rangeb: len="<<raw->len<<endl;nobs = DR.U4(p);//截取卫星观测数量if (raw->len < OEM4HLEN + 4 + nobs * 44)//每个卫星的观测记录为44个字节{cout << "oem4 rangeb length error: len=" << raw->len << " nobs=" << nobs << endl;}//检查消息的长度是否包含观测数据/* p移动4个字节为第一个数据的位置,每44个字节为一个卫星的观测记录,循环读取数据*/for (i = 0, p += 4; i < nobs; i++, p += 44){/* 解码卫星跟踪状态,返回值为0或1,0为第一个频点,1为第二个频点 */pos = decode_trackstat(DR.U4(p + 40), &sys, &code, &track, &plock, &clock,&parity, &halfc,number_sys);if (pos < 0){continue;}prn = DR.U2(p);//截取卫星的PRN号sat = satno(sys, prn);//对PRN号进行判断if (sat <= 0){continue;}gfrq = DR.U2(p + 2);psr = DR.R8(p + 4);//伪距adr = DR.R8(p + 16);//相位dop = DR.R4(p + 28);//多普勒观测值snr = DR.R4(p + 32);//观测噪声lockt = DR.R4(p + 36);//锁定时间/*-----周跳检测-----*/if (raw->tobs[sat - 1][pos].time != 0){tt = timediff(raw->time, raw->tobs[sat - 1][pos]);lli = lockt - raw->lockt[sat - 1][pos] + 0.05 <= tt ? LLI_SLIP : 0;}else {lli = 0;}raw->tobs[sat - 1][pos] = raw->time;raw->lockt[sat - 1][pos] = lockt;raw->halfc[sat - 1][pos] = halfc;if (!clock) psr = 0.0; /* code unlock */if (!plock) adr = dop = 0.0; /* phase unlock */if (fabs(timediff(raw->obs.data[0].time, raw->time)) > 1E-9){raw->obs.n = 0;}if ((index = obsindex(&raw->obs, raw->time, sat,number_sys)) >= 0){raw->obs.data[index].L[pos] = -adr;raw->obs.data[index].P[pos] = psr;raw->obs.data[index].D[pos] = (float)dop;raw->obs.data[index].SNR[pos] =0.0 <= snr && snr < 255.0 ? (unsigned char)(snr*4.0 + 0.5) : 0;raw->obs.data[index].LLI[pos] = (unsigned char)lli;raw->obs.data[index].code[pos] = code;raw->obs.data[index].sat = sat;raw->obs.data[index].num_sys = number_sys;raw->obs.data[index].time = raw->time;}}save_obsfile(raw);//输出到文件return 1;
}
星历按照同样的方法读即可,我就不放代码了。
三、读文件和主函数
由于我们是从文本文件获取的数据,所以我们还有一个读文件的函数。
int readfile(ifstream &fp,raw_data *raw)
{if (raw->nbyte == 0) {for (int i = 0; ; i++) {if ((data = fp.get()) == EOF){return -2; // 读取到文件结束符}if (sync_oem4(raw->buff, static_cast<uint8_t>(data))){break;//匹配到同步符则退出循环}if (i >= 4096){return 0;}//如果循环次数超过4096,说明同步失败}}// 从文件流读取数据 fp.read(reinterpret_cast<char *>(raw->buff + 3), 7); // 读取7字节raw->len = DR.U2(raw->buff + 8) + OEM4HLEN;//获取消息的长度/* 读取有效数据 */fp.read(reinterpret_cast<char *>(raw->buff + 10), raw->len - 6);//从buff的第10个字节开始,读取(消息长度-6字节)长度的数据raw->nbyte = 0;/* 解码 NovAtel OEM4 信息 */decode_oem4(raw);return 1;
}
主函数:
int main()
{string filename = "NovatelOEM20211114-01.log";ifstream fp(filename, ios::binary);//以二进制格式打开文件raw_data raw;while (true){int result = readfile(fp, &raw); // 循环调用 readfile 函数if (result == -2){std::cout << "读取到文件结束符" << std::endl;break; // 读取结束或失败,退出循环}else if (result == -1) {std::cerr << "消息长度错误,跳过此消息" << std::endl;continue; // 如果长度错误,跳过此消息}}outfile.close();outfile_g.close();outfile_n.close();fp.close();std::cout << "Hello World!\n";return 0;
}
本次内容就分享到这里了,大家有什么疑问欢迎和我交流。