因为不同生产厂家的接收机的观测量输出格式不一致,为了便于数据交换,制定了一个统一的标准格式,称为RINEX格式(Receiver Independent Exchange,RINEX)。
本文首先对RINEX2.1.1版本的观测值文件进行格式介绍,然后基于C语言,以程序设计的角度讲解如何读取数据,每行代码皆有详细注释和讲解,希望可以为测绘学子们带来帮助。
目录
N文件读取中讲解了如何下载观测值文件数据 ,这里不再进行赘述。
观测值文件由表头和数据块构成:
表头包括:RINEX版本号、接收机天线号、测站名、点号、观测值类型、采样间隔、开始时间、结束时间等等。
观测数据块包括:观测时刻、每颗卫星的伪距观测值和相位观测量,有的接收机还输出多普勒、信噪比值等。
示例:
以上是2.11版本的GPS观测值文件,红框标注部分是本次伪距定位部分读取的主要数据。(当然考虑到程序的可移植性,其实应该将所有内容全部读取),将下来将对头文件进行讲解。
示例:

历元信息:
数据块的第一行记录了该历元的UTC时刻,年、月、日、时分秒,紧跟着一个整型单位的历元标志,记录了该历元的状况(0表示正常),然后是该历元观测到的卫星数量,以及各个卫星的PRN号。
当观测到的卫星数>12时,一行的信息存储不下会自动换行,如下图所示:

当观测到的卫星数>24时,再次换行,并且卫星的PRN号与前一行对其。

上述两个情况是从多系统中截取到的,对于单系统而言,每个历元的卫星观测数>10就是优质观测。所以只需要考虑卫星数>12的情况即可,当然为了程序的可移植性,将其写完善也没问题。
数据块信息:
历元信息往下一行就是记录观测值的数据块,以每颗卫星为单位,依照头文件中的观测值类型及顺序,从左到右依次排列,每行记录5个观测值,一行不够时转下行。当所有卫星数据记录完后,转到下一个历元。
O文件结构体的创建分为三个部分:O文件头结构体、历元信息结构体和数据块结构体。
为了之后调用方便,在创建结构体时,最好用typedef对结构体进行重命名。
用typedef重命名后,在创建结构体变量或结构体指针时,用下列等式右边的即可:
时间系统中,年、月、日、时、分都以整数形式记录,秒用小数记录。
O文件头结构体:
//观测值头文件
typedef struct obs_head
{
double ver;//RINEX文件版本号
char type[30];//文件类型
double apX;//测站近似位置XYZ(WGS84)
double apY;
double apZ;
double ANTH;//天线高
double ANTdeltaE;//天线中心对于测站标志在东方向上的偏移量
double ANTdeltaN;//天线中心对于测站标志在北方向上的偏移量
int WAFL1;//缺省的L1载波的波长因子
int WAFL2;//缺省的L2载波的波长因子
int WAFflag;
int obstypenum;//观测值类型数量
char obstype[15];//rinex2.0观测值类型列表
double interval;//观测值的历元间隔
int f_y;//第一个观测记录的时刻
int f_m;
int f_d;
int f_h;
int f_min;
double f_sec;
char tsys[5];//时间系统
}obs_head,*pobs_head;
O文件历元信息结构体
//观测值历元数据结构体
typedef struct obs_epoch
{
//观测历元时刻
int y;
int m;
int d;
int h;
int min;
double sec;
int p_flag;//历元标志
int sat_num;//当前历元所观测到的卫星数量
int sPRN[24];//当前历元所能观测到的卫星的PRN列表
}obs_epoch,*pobs_epoch;
O文件数据块结构体
//观测值数据结构体
typedef struct obs_body
{
double obs[24][15];//观测值
}obs_body,*pobs_body;
这里的24对应同时观测到的最大卫星数,15对应着15种类型的观测值,对于单系统而言,这是非常安全的数字,不会出现溢出的现象。
文件读取的思路是:
具体代码如下:
//数据读取
FILE* fp_obs = NULL;//观测值文件指针
pobs_head obs_h = NULL;
pobs_epoch obs_e = NULL;
pobs_body obs_b = NULL;//创建结构体指针,并将其初始化为空
//O文件读取
fp_obs = fopen("abpo0480.15o", "r");//以只读的方式打开O文件
int o_epochnum = get_epochnum(fp_obs);//获取O文件历元数
rewind(fp_obs);
obs_h = (pobs_head)malloc(sizeof(obs_head));//给O文件头开辟空间
obs_e = (pobs_epoch)malloc(sizeof(obs_epoch) * o_epochnum);
obs_b = (pobs_body)malloc(sizeof(obs_body) * o_epochnum);
if (obs_h && obs_e && obs_b)
{
read_h(fp_obs, obs_h);//读取O文件头
read_b(fp_obs, obs_e, obs_b, obs_h->obstypenum);//读取O文件数据块
}
fclose(fp_obs);//关闭O文件
上述代码种,get_epochnum为自行创建的函数,获取O文件的历元数,read_h和read_b也是创建的函数,分别是读取文件头和读取数据块。
用fgets函数逐行对文件进行读取:
fegts函数需要包含头文件<stdio.h>,使用方法如下:
#define MAXRINEX 84//最大读取字符数
//获取O文件历元数
extern int get_epochnum(FILE* fp_obs)
{
int n = 0;//记录历元数
int satnum = 0;//记录每个历元的卫星数
char flag;//存放卫星标志符号'G'
char buff[MAXRINEX];//存放读取的字符串
while (fgets(buff, MAXRINEX, fp_obs))
{
satnum = (int)strtonum(buff, 30, 2);
strncpy(&flag, buff + 32, 1);
if (flag == 'G')
{
n++;
}
//当卫星数超过12个时,一行存不下会转到下下一行,并且与上一行对齐
//所以当出现这种情况时,要减1
if (flag == 'G' && satnum > 12)
{
n--;
}
}
return n;
}
上述代码中:
在用fgets函数读取数据时,我们是创建了一个buff的字符串接收的,对于正好是字符串类型的信息,例如观测类型tpye,我们可以利用strncpy函数,对字符串进行拷贝,拷贝到我们创建的结构体变量中。
先介绍一下strncpy函数:
但是很多参数是int、double类型的,那么我们需要先将其从char类型转换成double类型的,再对结构体成员进行赋值。
stronum的讲解在N文件读取中有详细解释,这里只放代码,代码中有详细注释。
//将字符串转换为浮点数,i起始位置,n输入多少个字符
static double strtonum(const char* buff, int i, int n)
{
double value = 0.0;
char str[256] = { 0 };
char* p = str;
/************************************
* 当出现以下三种情况报错,返回0.0
* 1.起始位置<0
* 2.读取字符串个数<i
* 3.str里面存放的字节数<n
*************************************/
if (i < 0 || (int)strlen(buff) < i || (int)sizeof(str) - 1 < n)
{
return 0.0;
}
for (buff += i; *buff && --n >= 0; buff++)
{
//三目操作符:D和d为文件中科学计数法部分,将其转换成二进制能读懂的e
*p++ = ((*buff == 'D' || *buff == 'd') ? 'e' : *buff);
}
*p = '\0';
//三目操作符,将str中存放的数以格式化读取到value中。
return sscanf(str, "%lf", &value) == 1 ? value : 0.0;
}
当前期的准备工作完成后,后期的读取工作则非常简单了,主要思想如下:
//读取O文件数据头
extern void read_h(FILE* fp_obs, pobs_head obs_h)
{
char buff[MAXRINEX] = { 0 };
char* lable = buff + 60;
int i = 0;
int j = 0;
while (fgets(buff, MAXRINEX, fp_obs))
{
if (strstr(lable, "RINEX VERSION / TYPE"))
{
obs_h->ver = strtonum(buff, 0, 9);
strncpy(obs_h->type, buff + 20, 30);
continue;
}
else if (strstr(lable, "APPROX POSITION XYZ"))
{
obs_h->apX = strtonum(buff, 0, 14);
obs_h->apY = strtonum(buff, 0 + 14, 14);
obs_h->apZ = strtonum(buff, 0 + 14 + 14, 14);
continue;
}
else if (strstr(lable, "ANTENNA: DELTA H/E/N"))
{
obs_h->ANTH = strtonum(buff, 0, 14);
obs_h->ANTdeltaE = strtonum(buff, 14, 14);
obs_h->ANTdeltaN = strtonum(buff, 14 + 14, 14);
continue;
}
else if (strstr(lable, "WAVELENGTH FACT L1/2"))
{
obs_h->WAFL1 = (int)strtonum(buff, 0, 6);
obs_h->WAFL2 = (int)strtonum(buff, 6, 6);
obs_h->WAFflag = (int)strtonum(buff, 6 + 6, 6);
continue;
}
else if (strstr(lable, "# / TYPES OF OBSERV"))
{
obs_h->obstypenum = (int)strtonum(buff, 0, 6);
if (obs_h->obstypenum <= 9)
{
for (i = 0; i < obs_h->obstypenum; i++)
{
strncpy(&(obs_h->obstype[i]), buff + 10 * i, 2);
}
}
else if (obs_h->obstypenum > 9)
{
for (i = 0; i < 9; i++)
{
strncpy(&(obs_h->obstype[i]), buff + 10 * i, 2);
}
fgets(buff, MAXRINEX, fp_obs);
for (i = 0; i < obs_h->obstypenum - 9; i++)
{
strncpy(&(obs_h->obstype[i + 9]), buff + 10 * i, 2);
}
}
continue;
}
else if (strstr(lable, "INTERVAL"))
{
obs_h->interval = strtonum(buff, 0, 11);
continue;
}
else if (strstr(lable, "TIME OF FIRST OBS"))
{
obs_h->f_y = (int)strtonum(buff, 0, 6);
obs_h->f_m = (int)strtonum(buff, 6, 6);
obs_h->f_d = (int)strtonum(buff, 6 + 6, 6);
obs_h->f_h = (int)strtonum(buff, 6 + 6 + 6, 6);
obs_h->f_min = (int)strtonum(buff, 6 + 6 + 6 + 6, 6);
obs_h->f_sec = strtonum(buff, 6 + 6 + 6 + 6 + 6, 6);
strncpy(obs_h->tsys, buff + 6 + 6 + 6 + 6 + 6 + 18, 3);
continue;
}
else if (strstr(lable, "END OF HEADER"))
break;
}
}
上述代码中,char* lable +60用来定位到文件头的标签位置。通过strstr(需要包含<string,h>)来查找相匹配的字符串。
在观测值类型那一行,需要注意同样的问题,即:观测值类型过多时会转到下一行,这里需要判断一下最左侧的数字是否>9。
其他位置则是对应O文件,输入特定的格式即可。
主要思想:
//读取O文件数据块
extern void read_b(FILE* fp_obs, pobs_epoch obs_e, pobs_body obs_b, int type)
{
int n = 0;//历元数
int i = 0;//第i颗卫星
int j = 0;//第i颗卫星的第j行观测值
int k = 0;//第j行第k个观测值
char buff[MAXRINEX] = { 0 };
char flag = { 0 };//判断符号
while (fgets(buff, MAXRINEX, fp_obs))
{
//按照格式将历元参考时间依次存入,年需+2000
//除秒以外,均转换成整型
obs_e[n].y = (int)strtonum(buff, 1, 2) + 2000;
obs_e[n].m = (int)strtonum(buff, 4, 2);
obs_e[n].d = (int)strtonum(buff, 7, 2);
obs_e[n].h = (int)strtonum(buff, 10, 2);
obs_e[n].min = (int)strtonum(buff, 13, 2);
obs_e[n].sec = strtonum(buff, 15, 11);
obs_e[n].p_flag = (int)strtonum(buff, 28, 1);
//输入卫星数
obs_e[n].sat_num = strtonum(buff, 29, 3);
strncpy(&flag, buff + 32, 1);
//判断卫星数是否超过12,分类处理
if (obs_e[n].sat_num <= 12 && flag == 'G')
{
for (i = 0; i < obs_e[n].sat_num; i++)
{
//将卫星编号存储到第n个历元中,第i个PRN位置(只读数字,不读'G')
//两个卫星编号的间隔为3个字符
obs_e[n].sPRN[i] = strtonum(buff, 33 + 3 * i, 2);
}
}
//如果卫星数超过12个,则下一行继续执行读取卫星数
else if (obs_e[n].sat_num >= 12 && flag == 'G')
{
for (i = 0; i < 12; i++)
{
obs_e[n].sPRN[i] = strtonum(buff, 33 + 3 * i, 2);
}
fgets(buff, MAXRINEX, fp_obs);
for (i = 0; i < obs_e[n].sat_num - 12; i++)
{
obs_e[n].sPRN[i + 12] = strtonum(buff, 33 + 3 * i, 2);
}
}
//对一块历元中的i个卫星进行数据读取
for (i = 0; i < obs_e[n].sat_num; i++)
{
//根据头文件读取的观测值类型判断每一颗卫星循环几行
for (j = 0; j < (int)ceil(type / 5.0); j++)//ceil(X),返回比X大的最小整数
{
int count = type / 5;//计数
fgets(buff, MAXRINEX, fp_obs);
//依次读取一行中的观测值
//这里加个三目操作符判断一下,是不是到了最后一行,如果到了最后一行则不进行整行读取
for (k = 0; j == count ? k < type % 5 : k < 5; k++)
{
obs_b[n].obs[i][k + 5 * j] = strtonum(buff, 16 * k, 16);
}
}
}
n++;
}
}
上述代码中,需要注意:
不同版本的观测值文件格式会有差异,但是大体思路都是一致的,历元——卫星——观测值,以上,便是观测值文件读取的全部内容了,
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只