@taqikema
2018-07-02T09:42:24.000000Z
字数 31229
阅读 38014
GNSS
RTKLIB
单点定位
int pntpos (const obsd_t *obs, int n, const nav_t *nav, const prcopt_t *opt, sol_t *sol,
double *azel, ssat_t *ssat, char *msg)
函数参数,8个:
obsd_t *obs I observation data
int n I number of observation data
nav_t *nav I navigation data
prcopt_t *opt I processing options
sol_t *sol IO solution
double *azel IO azimuth/elevation angle (rad) (NULL: no output)
ssat_t *ssat IO satellite status (NULL: no output)
char *msg O error message for error exit
返回类型:
int O (1:ok,0:error)
如无特别说明,本文所出现的流程图中,纵向代表时间上的先后调用顺序,横向代表层次上的逐级调用顺序。
pntpos satposs estpos raim_fde estvel
处理过程:
- 检查卫星个数是否大于 0
- 当处理选项
opt
中的模式不是单点模式时,电离层校正采用Klobuchar模型
,对流层校正则采用Saastamoinen模型
;相反,当其为单点模式时,对输入参数opt
不做修改。- 调用 satposs函数,按照所观测到的卫星顺序计算出每颗卫星的位置、速度、{钟差、频漂}。
- 调用 estpos函数,通过伪距实现绝对定位,计算出接收机的位置和钟差,顺带返回实现定位后每颗卫星的{方位角、仰角}、定位时有效性、定位后伪距残差。
- 调用 raim_fde函数,对上一步得到的定位结果进行接收机自主正直性检测(
RAIM
)。通过再次使用vsat
数组,这里只会在对定位结果有贡献的卫星数据进行检测。- 调用 estvel函数,依靠多普勒频移测量值计算接收机的速度。这里只使用通过了上一步
RAIM_FDE
操作的卫星数据,所以对于计算出的速度就没有再次进行RAIM
了。- 首先将
ssat_t
结构体数组的 vs(定位时有效性)、azel(方位角、仰角)、resp(伪距残余)、resc(载波相位残余)和 snr(信号强度)都置为 0,然后再将实现定位后的 azel、snr赋予ssat_t
结构体数组,而 vs、resp则只赋值给那些对定位有贡献的卫星,没有参与定位的卫星,这两个属性值为 0。
注意事项:
- 这里只计算了接收机的钟差,而没有计算接收机的频漂,原因在于 estvel函数中虽然计算得到了接收机频漂,但并没有将其输出到
sol_t:dtr
中。- C语言中用
malloc
申请的内存需要自己调用free
来予以回收,源码中的mat
、imat
、zeros
等函数都只是申请了内存,并没有进行内存的回收,在使用这些函数时,用户必须自己调用free
来回收内存!源码中将使用这些函数的代码放置在同一行,在调用函数结尾处也统一进行内存回收,位置较为明显,不致于轻易忘记。
void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
int ephopt, double *rs, double *dts, double *var, int *svh)
函数参数,9个:
gtime_t teph I time to select ephemeris (gpst)
obsd_t *obs I observation data
int n I number of observation data
nav_t *nav I navigation data
int ephopt I ephemeris option (EPHOPT_???)
double *rs O satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts O satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *var O sat position and clock error variances (m^2)
int *svh O sat health flag (-1:correction not available)
返回类型:
none
处理过程:
- 按照观测数据的顺序,首先将将当前观测卫星的 rs、dts、var和svh数组的元素置 0。
- 通过判断某一频率下信号的伪距是否为 0,来得到此时所用的频率个数。注意,频率个数不能大于
NFREQ
(默认为 3)。- 用数据接收时间减去伪距信号传播时间,得到卫星信号的发射时间。
- 调用 ephclk函数,由广播星历计算出当前观测卫星的钟差。注意,此时的钟差是没有考虑相对论效应和 TGD的。
- 用 3中的信号发射时间减去 4中的钟偏,得到 GPS时间下的卫星信号发射时间。
- 调用 satpos函数,计算信号发射时刻卫星的 P(ecef,m)、V(ecef,m/s)、C((s|s/s))。注意,这里计算出的钟差是考虑了相对论效应的了,只是还没有考虑 TGD。
- 如果由 6中计算出的钟偏为 0,就再次调用 ephclk函数,将其计算出的卫星钟偏作为最终的结果。
注意事项:
- 对于处理过程中的第3步,数据接收时间减去伪距信号传播时间,这里的数据接收时间应该是有接收机得到的,本身应该是包含接收机钟差的,所以最终得到的信号发射时间应该也是不准确的。难道说接收机钟差较小,在此时可以忽略不计?
- ephclk函数最终是通过调用 eph2clk来计算卫星钟偏的,但对于后者,我有问题。见 eph2clk处我的疑惑
- 为什么要进行 7中操作?
int estpos(const obsd_t *obs, int n, const double *rs, const double *dts,
const double *vare, const int *svh, const nav_t *nav,
const prcopt_t *opt, sol_t *sol, double *azel, int *vsat,
double *resp, char *msg)
函数参数,13个:
obsd_t *obs I observation data
int n I number of observation data
double *rs I satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts I satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *vare I sat position and clock error variances (m^2)
int *svh I sat health flag (-1:correction not available)
nav_t *nav I navigation data
prcopt_t *opt I processing options
sol_t *sol IO solution
double *azel IO azimuth/elevation angle (rad)
int *vsat IO 表征卫星在定位时是否有效
double *resp IO 定位后伪距残差 (P-(r+c*dtr-c*dts+I+T))
char *msg O error message for error exit
返回类型:
int O (1:ok,0:error)
estpos rescode lsq valsol
- 处理过程:
- 将
sol->rr
的前 3项赋值给 x数组。- 开始迭代定位计算,首先调用 rescode函数,计算在当前接收机位置和钟差值的情况下,定位方程的右端部分
v(nv\*1)
、几何矩阵H(NX*nv)
、此时所得的伪距残余的方差var
、所有观测卫星的azel
{方位角、仰角}、定位时有效性vsat
、定位后伪距残差resp
、参与定位的卫星个数ns
和方程个数nv
。- 确定方程组中方程的个数要大于未知数的个数。
- 以伪距残余的标准差的倒数作为权重,对 H和 v分别左乘权重对角阵,得到加权之后的 H和 v。
- 调用 lsq函数,根据 和 ,得到当前 x的修改量和定位误差协方差矩阵中的权系数阵。
- 将 5中求得的 x加入到当前 x值中,得到更新之后的 x值。
- 如果 5中求得的修改量小于截断因子(目前是1e-4),则将 6中得到的 x值作为最终的定位结果,对
sol
的相应参数赋值,之后再调用 valsol函数确认当前解是否符合要求(伪距残余小于某个 值和GDOP
小于某个门限值)。否则,进行下一次循环。- 如果超过了规定的循环次数,则输出发散信息后,返回 0。
注意事项:
- 关于第 1步,如果是第一次定位,即输入的
sol
为空,则 x初值为 0;如果之前有过定位,则通过 1中操作可以将上一历元的定位值作为该历元定位的初始值。- 关于定位方程,书中的写法如下:
其中,
而 RTKLIB中采用的方程是下面这样的(这里先假设未知数的个数为 4):
其中,。- 关于加权最小二乘,这里的权重值是对角阵,这是建立在假设不同测量值的误差之间是彼此独立的;另外,这个权重值并不单是距测量误差的,而是方程右端
b
整体的测量误差。最后,大部分资料上这里都是把权重矩阵W
保留到方程的解的表达式当中,而这里是直接对 H和 v分别左乘权重对角阵,得到加权之后的 H和 v,其表示形式像是没有加权一样。- 如果某次迭代过程中步长小于门限值(1e-4),但经 valsol函数检验后该解无效,则会直接返回 0,并不会再进行下一次迭代计算。
- 因为该函数有两个返回路径,而且又调用了
mat
函数来构建矩阵,所以在两个返回路径处都需要调用free
函数来回收内存。- 源码中的 dtr实际上单位是 m,是乘以了光速之后的。
- 在对
sol
结构体赋值时,并没有直接将接收机钟差 dtr赋值给sol_dtr
,而是通过在sol
中存储的是减去接收机钟差后的信号观测时间,来讲该信息包括到sol
中了。- 源码中定位方程的个数
nv
要大于有效观测卫星的个数ns
,这里为了防止亏秩,似乎又加了 3个未知数和观测方程。- 在每一次重新调用 rescode函数时,其内部并没有对 v、H和 var清零处理,所以当方程数变少时,可能会存在尾部仍保留上一次数据的情况,但是因为数组相乘时都包含所需计算的长度,所以这种情况并不会对计算结果造成影响。
我的疑惑:
int raim_fde(const obsd_t *obs, int n, const double *rs,
const double *dts, const double *vare, const int *svh,
const nav_t *nav, const prcopt_t *opt, sol_t *sol,
double *azel, int *vsat, double *resp, char *msg)
RAIM
)。通过再次使用 vsat
数组,这里只会在对定位结果有贡献的卫星数据进行检测。
函数参数,13个:
obsd_t *obs I observation data
int n I number of observation data
double *rs I satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts I satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *vare I sat position and clock error variances (m^2)
int *svh I sat health flag (-1:correction not available)
nav_t *nav I navigation data
prcopt_t *opt I processing options
sol_t *sol IO solution
double *azel IO azimuth/elevation angle (rad)
int *vsat IO 表征卫星在定位时是否有效
double *resp IO 定位后伪距残差 (P-(r+c*dtr-c*dts+I+T))
char *msg O error message for error exit
返回类型:
int O (1:ok,0:error)
处理过程:
- 关于观测卫星数目的循环,每次舍弃一颗卫星,计算使用余下卫星进行定位的定位值。
- 舍弃一颗卫星后,将剩下卫星的数据复制到一起,调用 estpos函数,计算使用余下卫星进行定位的定位值。
- 累加使用当前卫星实现定位后的伪距残差平方和与可用微信数目,如果
nvsat<5
,则说明当前卫星数目过少,无法进行RAIM_FDE
操作。- 计算伪距残差平方和的标准平均值,如果大于
rms
,则说明当前定位结果更合理,将stat
置为 1,重新更新sol
、azel
、vsat
(当前被舍弃的卫星,此值置为0)、resp
等值,并将当前的rms_e
更新到 `rms'中。- 继续弃用下一颗卫星,重复 2-4操作。总而言之,将同样是弃用一颗卫星条件下,伪距残差标准平均值最小的组合所得的结果作为最终的结果输出。
- 如果 stat不为 0,则说明在弃用卫星的前提下有更好的解出现,输出信息,指出弃用了哪科卫星。
- 使用
free
函数回收相应内存。
注意事项:
- 使用了
mat
和malloc
函数后要使用free
函数来回收内存。- 源码中有很多关于 i、j、k的循环。其中,i表示最外面的大循环,每次将将第 i颗卫星舍弃不用,这是通过
if (j==i) continue
实现的;j表示剩余使用的卫星的循环,每次进行相应数据的赋值;k表示参与定位的卫星的循环,与 j一起使用。
- 这里执行
RAIM
至少要有 6颗可用卫星,可是我感觉 5颗就够了呀!
void estvel(const obsd_t *obs, int n, const double *rs, const double *dts,
const nav_t *nav, const prcopt_t *opt, sol_t *sol,
const double *azel, const int *vsat)
函数参数,9个:
obsd_t *obs I observation data
int n I number of observation data
double *rs I satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts I satellite clocks,长度为2*n, {bias,drift} (s|s/s)
nav_t *nav I navigation data
prcopt_t *opt I processing options
sol_t *sol IO solution
double *azel IO azimuth/elevation angle (rad)
int *vsat IO 表征卫星在定位时是否有效
返回类型:
int O (1:ok,0:error)
int ephclk(gtime_t time, gtime_t teph, int sat, const nav_t *nav, double *dts)
函数参数,5个:
gtime_t time I transmission time by satellite clock
gtime_t teph I time to select ephemeris (gpst)
int sat I satellite number (1-MAXSAT)
nav_t *nav I navigation data
double *dts O satellite clocks,长度为2*n, {bias,drift} (s|s/s)
返回类型:
int O (1:ok,0:error)
处理过程:
注意事项:
- 此时计算出的卫星钟偏是没有考虑相对论效应和 TGD的。
- 目前我还只关心 RTKLIB对于 GPS导航所进行的数据处理,所以这里在选择星历和计算钟差时都只介绍与 GPS系统有关的函数。
int satpos(gtime_t time, gtime_t teph, int sat, int ephopt, const nav_t *nav,
double *rs, double *dts, double *var, int *svh)
函数参数,9个:
gtime_t time I time (gpst)
gtime_t teph I time to select ephemeris (gpst)
int sat I satellite number
nav_t *nav I navigation data
int ephopt I ephemeris option (EPHOPT_???)
double *rs O sat position and velocity {x,y,z,vx,vy,vz} (ecef)(m|m/s)
double *dts O sat clock {bias,drift} (s|s/s)
double *var O sat position and clock error variance (m^2)
int *svh O sat health flag (-1:correction not available)
返回类型:
int O (1:ok,0:error)
处理过程:
- 判断星历选项的值,如果是
EPHOPT_BRDC
,调用 ephpos函数,根据广播星历计算出算信号发射时刻卫星的 P、V、C
注意事项:
- 此时计算出的卫星钟差考虑了相对论,还没有考虑 TGD。
- 目前还只阅读了如何从广播星历中计算卫星 P、V、C的代码,关于如何从精密星历中计算,等对精密星历的理论背景有了更多了解之后再予以添加。
int satsys(int sat, int *prn)
PRN
编号
函数参数,2个:
int sat I satellite number (1-MAXSAT)
int *prn IO satellite prn/slot number (NULL: no output)
返回类型:
int satellite system (SYS_GPS,SYS_GLO,...)
处理过程:
- 为处理意外情况(卫星号不在
1-MAXSAT
之内),先令卫星系统为SYS_NONE
。- 按照 TRKLIB中定义相应宏的顺序来判断是否是 GPS、GLO、GAL系统等,判断标准是将卫星号减去前面的导航系统所拥有的卫星个数,来判断剩余卫星个数是否小于等于本系统的卫星个数。
- 确定所属的系统后,通过加上最小卫星编号的
PRN
再减去 1,得到该卫星在该系统中的PRN
编号。
注意事项:
- 这里的卫星号是从 1开始排序的,这也是很多函数中与之有关的数组在调用时形式写为
A[B.sat-1]
eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav)
iode>=0
时,选择与输入期号相同的星历;否则,选择 toe
值与星历选择时间标准 time
最近的那个星历。
函数参数,4个:
gtime_t time I time to select ephemeris (gpst)
int sat I satellite number (1-MAXSAT)
int iode I 星历数据期号
nav_t *nav I navigation data
返回类型:
eph_t * 星历数据
处理过程:
- 根据该卫星所属的导航系统给与星历参考时间的最大时间间隔
tmax
赋予相应的值。- 遍历导航数据,首先确保所查找星历的卫星号是否相同,接着确保星历期号是否相同。
- 接着确保星历选择时间与代查找星历的参考时间的间隔是否小于
tmax
。- 对于通过了 2-3验证的星历,如果此时对于输入的期号,有
iode>=0
,则该星历就是所要寻找的星历;否则,验证 3中的t
是否满足t<=tmin
。满足的话,就令tmin=t
,该星历目前是距离参考时间最近的。- 循环 2-4步操作,遍历完导航数据中的所有星历。如果都没有符合条件的,就输出信息并返回 NULL;否则,返回所查找到的星历。
注意事项:
- 对于 GPS系统,星历数据期号每 2h更新一次,所以 RTKLIB中对
MAXDTOE
的定义为 7200。- 如果存在两个相邻时间段的星历,通过 4中令
tmin=t
的操作可以最终查找出参考时间距time
最近的那个星历。
- 为什么
tmax
和tmin
都要加 1?- IODE正常情况下应该都是 >=0的,为什么还要考虑 <0的情况?
- 考虑到星历中卫星的健康状况,这里在选择星历时是否也应该验证
eph.svh==0
呢?
int eph2clk (gtime_t time, const eph_t *eph)
函数参数,2个
gtime_t time I time by satellite clock (gpst)
eph_t *eph I broadcast ephemeris
返回类型:
double satellite clock bias (s) without relativeity correction
- 计算与星历参考时间的偏差 dt = t-toc。
- 利用二项式校正计算出卫星钟差,从 dt中减去这部分,然后再进行一次上述操作,得到最终的 dt。
- 使用二项式校正得到最终的钟差。
- 看不懂上述处理过程,跟以往资料上都不一样。咋回事?
int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
int iode, double *rs, double *dts, double *var, int *svh)
函数参数,9个
gtime_t time I transmission time by satellite clock
gtime_t teph I time to select ephemeris (gpst)
int sat I satellite number (1-MAXSAT)
nav_t *nav I navigation data
int iode I 星历数据期号
double *rs O satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts O satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *var O sat position and clock error variances (m^2)
int *svh O sat health flag (-1:correction not available)
返回类型:
int O (1:ok,0:error)
处理过程:
注意事项:
- 这里是使用扰动法计算卫星的速度和频漂,并没有使用那些位置和钟差公式对时间求导的结果。
- 由于是调用的 eph2pos函数,计算得到的钟差考虑了相对论效应,还没有考虑 TGD
void eph2pos(gtime_t time, const eph_t *eph, double *rs, double *dts, double *var)
函数参数,5个
gtime_t time I transmission time by satellite clock
eph_t *eph I broadcast ephemeris
double *rs O satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts O satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *var O sat position and clock error variances (m^2)
返回类型:
none
处理过程:
- 与大部分资料上计算卫星位置和钟差的过程是一样的,只是这里在计算偏近点角 E时采用的是牛顿法来进行迭代求解。
- 计算误差直接采用
URA
值来标定,具体对应关系可在ICD-GPS-200C P83
中找到。
注意事项:
- 这里在计算钟差时,就与大部分资料上介绍的一样了,只进行了一次二项式校正。另外,这里的钟差考虑了相对论效应,并没有考虑 TGD。
- 在计算偏近点角 E时,这里采用的是牛顿法来进行迭代求解,这里与很多资料上可能不一样,具体内容可在
RTKLIB manual P143
中找到。- 补充几个程序中的参数说明:
mu, 地球引力常数
eph->A, 轨道长半径
omgea, 地球自转角速度
int rescode(int iter, const obsd_t *obs, int n, const double *rs,
const double *dts, const double *vare, const int *svh,
const nav_t *nav, const double *x, const prcopt_t *opt,
double *v, double *H, double *var, double *azel, int *vsat,
double *resp, int *ns)
v(nv\*1)
、几何矩阵 H(NX*nv)
、此时所得的伪距残余的方差 var
、所有观测卫星的 azel
{方位角、仰角}、定位时有效性 vsat
、定位后伪距残差 resp
、参与定位的卫星个数 ns
和方程个数 nv
。
函数参数,17个
int iter I 迭代次数
obsd_t *obs I observation data
int n I number of observation data
double *rs I satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts I satellite clocks,长度为2*n, {bias,drift} (s|s/s)
double *vare I sat position and clock error variances (m^2)
int *svh I sat health flag (-1:correction not available)
nav_t *nav I navigation data
double *x I 本次迭代开始之前的定位值
prcopt_t *opt I processing options
double *v O 定位方程的右端部分,伪距残余
double *H O 定位方程中的几何矩阵
double *var O 参与定位的伪距残余方差
double *azel O 对于当前定位值,每一颗观测卫星的 {方位角、高度角}
int *vsat O 每一颗观测卫星在当前定位时是否有效
double *resp O 每一颗观测卫星的伪距残余, (P-(r+c*dtr-c*dts+I+T))
int *ns O 参与定位的卫星的个数
返回类型:
int O 定位方程组的方程个数
rescode ecef2pos satsys geodist satazel prange satexclude ionocorr tropcorr varerr
处理过程:
- 将之前得到的定位解信息赋值给
rr
和dtr
数组,以进行关于当前解的伪距残余的相关计算。- 调用 ecef2pos函数,将上一步中得到的位置信息由 ECEF转化为大地坐标系。
- 将
vsat
、azel
和resp
数组置 0,因为在前后两次定位结果中,每颗卫星的上述信息都会发生变化。- 调用 satsys函数,验证卫星编号是否合理及其所属的导航系统。
- 检测当前观测卫星是否和下一个相邻数据重复。是,则
i++
后继续下一次循环;否,则进入下一步。- 调用 geodist函数,计算卫星和当前接收机位置之间的几何距离和
receiver-to-satellite
方向的单位向量。然后检验几何距离是否 >0。- 调用 satazel函数,计算在接收机位置处的站心坐标系中卫星的方位角和仰角,检验仰角是否 截断值。
- 调用 prange函数,得到伪距值。
- 可以在处理选项中事先指定只选用哪些导航系统或卫星来进行定位,这是通过调用 satexclude函数完成的。
- 调用 ionocorr函数,计算电离层延时(m)。
- 上一步中所得的电离层延时是建立在 L1信号上的,当使用其它频率信号时,依据所用信号频组中第一个频率的波长与 L1波长的关系,对上一步得到的电离层延时进行修正。
- 调用 tropcorr函数,计算对流层延时(m)。
- 由 ,计算出此时的伪距残余。
- 组装几何矩阵,前 3行为 6中计算得到的视线单位向量的反向,第 4行为 1,其它行为 0。
- 将参与定位的卫星的定位有效性标志设为 1,给当前卫星的伪距残余赋值,参与定位的卫星个数
ns
加 1.- 调用 varerr函数,计算此时的导航系统误差(可能会包括
IFLC
选项时的电离层延时),然后累加计算用户测距误差(URE)。- 为了防止亏秩,人为的添加了几组观测方程。
注意事项:
- 返回值
v
和resp
的主要区别在于长度不一致,v
是需要参与定位方程组的解算的,维度为 nv*1;而 resp仅表示所有观测卫星的伪距残余,维度为 n*1,对于没有参与定位的卫星,该值为 0。- 源码中
dtr
的单位是 m。- 16中的
URE
值包括 ①卫星星历和钟差的误差 ②大气延时误差 ③伪距测量的码偏移误差 ④导航系统的误差
我的疑惑:
- 关于 5中的去除重复数据的过程,有以下几个看法:
① 这样做的前提是相同卫星的重复数据必须相邻出现!
② 为什么在这里要进行重复数据检测,在构建obsd_t
结构体时就可以进行这项工作呀?
③ 5中当数据重复时,i++
后继续下一次循环,这样的话会直接略去后面所重复的数据,这样做就会将两个相邻重复数据都忽略掉,就相当于重复数据都不使用。感觉可以用其中一个的啊!- 11步中,为什么要选择所用信号频组中第一个频率的波长来进行电离层延时修正呢?还有,电离层延时的值发生了改变,那这里的方差是否也需要跟着一起改变呢?
- 在计算电离/对流层延时时,均传入了
iter>0?opt->ionoopt:IONOOPT_BRDC
或iter>0?opt->tropopt:TROPOPT_SAAS
参数,都强调了当iter==0
时,会强制使用Klobuchar
或Saastamoinen
模型。这会不会是因为这两种模型都是属于对接收机位置不是非常敏感的类型?- 这里亏秩应该就是欠定方程的意思吧?这里 17中的操作没有看懂,也没能找到相关理论依据
int lsq(const double *A, const double *y, int n, int m, double *x, double *Q)
函数参数,6个
double *A I transpose of (weighted) design matrix (n x m)
double *y I (weighted) measurements (m x 1)
int n,m I number of parameters and measurements (n<=m)
double *x O estmated parameters (n x 1)
double *Q O esimated parameters covariance matrix (n x n)
返回类型:
int O (0:ok,0>:error)
处理过程:
- 首先计算右半部分 。
- 再计算左半部分括号里面的值 。
- 计算 Q矩阵的逆 ,但仍存储在 Q中,最后再右乘 ,得到 x的值。
注意事项:
- 对于加权最小二乘,可以直接在调用该函数之前直接将 A、y进行加权处理,之后在调用该函数,这样得到的就是加权最小二乘的解。
- 所有的矩阵都是列优先存储的,对于整个源代码来说,矩阵都是这样存储的。所以对于代码中出现的一维矩阵,基本都应该是列向量。在阅读数组下标时,记住这一点是非常重要的。
- 矩阵求逆并不简单,尤其是对于接近奇异的矩阵。但是由于这是个基本功能,并不打算继续深入下去。
int valsol(const double *azel, const int *vsat, int n,
const prcopt_t *opt, const double *v, int nv, int nx, char *msg)
GDOP
小于某个门限值)
函数参数,8个
double *azel I azimuth/elevation angle (rad)
int *vsat I 表征卫星在定位时是否有效
int n I number of observation data
prcopt_t *opt I processing options
double *v I 定位后伪距残差 (P-(r+c*dtr-c*dts+I+T))
int nv I 定位方程的方程个数
int nx I 未知数的个数
char *msg O error message for error exit
返回类型:
int O (1:ok,0:error)
处理过程:
- 先计算定位后伪距残余平方加权和
vv
。- 检查是否满足 。是,则说明此时的定位解误差过大,返回 0;否则,转到下一步。
- 复制 azel,这里只复制那些对于定位结果有贡献的卫星的 zael值,并且统计实现定位时所用卫星的数目。
- 调用 dops函数,计算各种精度因子(DOP),检验是否有
0<GDOP<max
。否,则说明该定位解的精度不符合要求,返回 0;是,则返回 1。
注意事项:
- 关于 2中的 检验,这里这里与
RTKLIB manual P160
中不一致(但与教材一致),文档中满足 时就会不合格。与文档中相比,这里的写法将会放宽对于位置解的检验。
源码中定义了两个 matmul函数,一个是在包含了 LAPACK/BLAS/MKL
库使用,调用其中的 degmn
函数来完成矩阵相乘操作。这里主要说明在没有包含上述库时自定义的矩阵相乘函数。
void matmul(const char *tr, int n, int k, int m, double alpha,
const double *A, const double *B, double beta, double *C)
函数参数,6个
char *tr I transpose flags ("N":normal,"T":transpose)
int n,k,m I size of (transposed) matrix A,B
double alpha I alpha
double *A,*B I (transposed) matrix A (n x m), B (m x k)
double beta I beta
double *C IO matrix C (n x k)
返回类型:
none
处理过程:
- 根据
tr
的值确定矩阵相乘标志,即
- 按照
f
的值,分别执行相应的元素相乘并累加操作。- 对于 2中得到的乘积 tmp,执行 C = alpha*tmp + beta*C操作。
注意事项:
- 在调用该函数时,要确保矩阵的维度是否和上述参数说明一致。
- 所有的矩阵都是列优先存储的,记住这一点,对于看明白 2中不同相乘方式时,元素如何相乘累加是至关重要的。
- 矩阵求逆并不简单,尤其是对于接近奇异的矩阵。但是由于这是个基本功能,并不打算继续深入下去。
void dops(int ns, const double *azel, double elmin, double *dop)
函数参数,4个
int ns I number of satellites
double *azel I satellite azimuth/elevation angle (rad)
double elmin I elevation cutoff angle (rad)
double *dop O DOPs {GDOP,PDOP,HDOP,VDOP}
返回类型:
none
处理过程:
我的疑惑:
- 1中几何矩阵 与书中的不一致,前 3行均少了一个负号,我自己推导之后也觉得应该有负号,即为
不过,由于 ,在括号里面的矩阵相乘时,是否有负号只对底边靠左 3个元素和右边靠上 3个元素有影响(多了个负号),然后再进行求逆之后,前 3行是否有负号就对对角线上的元素似乎没有影响了。- 感觉在 rescode函数中,在检验一个观测卫星的伪距信息是否可用时,已经进行过是否大于截断高度角的检测了。这里所用的卫星仰角又都是属于参与了定位的卫星,所以感觉这里应该不需要再进行一次高度角检测了吧?
void ecef2enu(const double *pos, const double *r, double *e)
函数参数,3个
double *pos I geodetic position {lat,lon} (rad)
double *r I vector in ecef coordinate {x,y,z}
double *e O vector in local tangental coordinate {e,n,u}
返回类型:
none
void xyz2enu(const double *pos, double *E)
函数参数,2个
double *pos I geodetic position {lat,lon} (rad)
double *E O vector in local tangental coordinate {e,n,u}
返回类型:
none
- 按照大部分资料上的写法计算 3*3的矩阵,优先按列存储。
void ecef2pos(const double *r, double *pos)
函数参数,2个
double *r I ecef position {x,y,z} (m)
double *pos O geodetic position {lat,lon,h} (rad,m)
返回类型:
none
这里采用的方法与很多资料上的并不一致,而关于源码中方法的具体理论推导和计算过程,见 ECEF和大地坐标系的相互转化一文。
double geodist(const double *rs, const double *rr, double *e)
receiver-to-satellite
方向的单位向量。
函数参数,3个
double *rs I satellilte position (ecef at transmission) (m)
double *rr I receiver position (ecef at reception) (m)
double *e O line-of-sight unit vector (ecef)
返回类型:
double O geometric distance (m) (0>:error/no satellite position)
处理过程:
- 检查卫星到 WGS84坐标系原点的距离是否大于基准椭球体的长半径。
ps-pr
,计算由接收机指向卫星方向的观测矢量,之后在计算出相应的单位矢量。- 考虑到地球自转,即信号发射时刻卫星的位置与信号接收时刻卫星的位置在 WGS84坐标系中并不一致,进行关于
Sagnac效应
的校正。
我的疑惑:
- 3中使用关于
Sagnac效应
的校正来考虑地球自转对卫星位置的影响,与教材中的地球自转校正并不一样,二者是否描述的是同一个事情?
double satazel(const double *pos, const double *e, double *azel)
函数参数,3个
double *pos I geodetic position {lat,lon,h} (rad,)
double *e I receiver-to-satellilte unit vevtor (ecef)
double *azel IO azimuth/elevation {az,el} (rad) (NULL: no output) (0.0<=azel[0]<2*pi,-pi/2<=azel[1]<=pi/2)
返回类型:
double O elevation angle (rad)
处理过程:
- 调用 ecef2enu函数,将
pos
处的向量转换到以改点为原点的站心坐标系中。- 调用
atan2
函数计算出方位角,然后再算出仰角。
注意事项:
- 这里在计算方位角时,要使用
atan2
函数,而不能是atan
函数,详细原因见 C语言中的atan和atan2。
double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
int iter, const prcopt_t *opt, double *var)
函数参数,6个
obsd_t *obs I observation data
nav_t *nav I navigation data
double *azel I 对于当前定位值,每一颗观测卫星的 {方位角、高度角}
int iter I 迭代次数
prcopt_t *opt I processing options
double *vare O 伪距测量的码偏移误差
返回类型:
double O 最终能参与定位解算的伪距值
处理过程:
- 首先调用 satsys确定该卫星属于 RTKLIB设计时给定的几个导航系统之中。
- 如果
NFREQ>=3
且该卫星属于GAL/SBS
系统,则j=2
。而如果出现NFREQ<2||lam[i]==0.0||lam[j]==0.0
中的其中一个,直接返回 0.- 当
iter>0
时,调用 testsnr函数,测试第i、j(IFLC
)个频率信号的载噪比是否符合要求。- 计算出 值(f1^2/f2^2,见ICD-GPS-200C P90),从
obs
和nav
数据中读出测量伪距值和码偏移值(?)
。- 从数据中读出的P1_P2==0,则使用 TGD代替,TGD值由 gettgd函数计算得到。
- 如果
ionoopt==IONOOPT_IFLC
,根据obs->code
的值来决定是否对 P1、P2进行修正,之后再组合出 IFLC时的伪距值(ICD-GPS-200C P91)。否则,则是针对单频接收即进行的数据处理。先对 P1进行修正,然后再计算出伪距值(PC)- 如果
sateph==EPHOPT_SBAS
,则还要对 PC进行处理。之后给该函数计算出的伪距值的方差赋值。
我的疑惑:
- 该函数到底在对伪距进行哪部分的计算?计算进行 C/A码修正后的伪距值?
- 在调用 testsnr函数时,为什么要有
iter>0
的限制?为什么第一次迭代就不能调用这些函数呢?- 2中操作的含义不明白,还有为什么出现 3个条件中的一个,就要返回 0呢?
- 5中关于 IFLC模型伪距的重新计算是看明白了,但是
P1_P2
、P1_C1
、P1_C2
这些变量具体代表什么含义,以及P1_P2==0.0
时使用 TGD代替和最后关于sbas clock based C1
的操作看不懂。。。
int satexclude(int sat, int svh, const prcopt_t *opt)
函数参数,3个
int sat I satellite number,从 1开始
int svh I sv health flag(0:ok)
prcopt_t *opt I processing options (NULL: not used)
返回类型:
int O 1:excluded,0:not excluded
处理过程:
注意事项:
- 3中再在比较该卫星与预先规定的导航系统是否一致时,使用了
sys&opt->navsys
来进行比较。这样做的好处是当opt->navsys=sys
或opt->navsys=SYS_ALL
时,结果都会为真。之所以会这样,是因为在rtklib.h
文件中定义这些导航系统变量的时候,所赋的值在二进制形式下都是只有一位为1
的数。
- 对于 3中检测,先验证状态排除标志,后验证导航系统,这样就可能出现排除标志符合要求而所属系统不符合要求的状况,而 3中做法会将上述状况设为
included
!- 另外,注意到在 3中检测之后仍验证了
svh>0
,那如果出现svh
不合乎要求而排除标志符合要求的状况,3中做法却会将上述状况设为included
!
int ionocorr(gtime_t time, const nav_t *nav, int sat, const double *pos,
const double *azel, int ionoopt, double *ion, double *var)
函数参数,8个
gtime_t time I time
nav_t *nav I navigation data
int sat I satellite number
double *pos I receiver position {lat,lon,h} (rad|m)
double *azel I azimuth/elevation angle {az,el} (rad)
int ionoopt I ionospheric correction option (IONOOPT_???)
double *ion O ionospheric delay (L1) (m)
double *var O ionospheric delay (L1) variance (m^2)
返回类型:
int O (1:ok,0:error)
处理过程:
注意事项:
int int tropcorr(gtime_t time, const nav_t *nav, const double *pos,
const double *azel, int tropopt, double *trp, double *var)
函数参数,7个
gtime_t time I time
nav_t *nav I navigation data
double *pos I receiver position {lat,lon,h} (rad|m)
double *azel I azimuth/elevation angle {az,el} (rad)
int tropopt I tropospheric correction option (TROPOPT_???)
double *trp O tropospheric delay (m)
double *var O tropospheric delay variance (m^2)
返回类型:
int O (1:ok,0:error)
处理过程:
- 当
tropopt==TROPOPT_SAAS
或一些其它情况时,调用 tropmodel函数,计算Saastamoinen模型
下的对流层延时。
注意事项:
- 貌似对流层延时与信号频率无关,所以这里计算得到的值并不是只针对于
L1
信号!
double varerr(const prcopt_t *opt, double el, int sys)
函数参数,3个
prcopt_t *opt I processing options
double el I elevation angle (rad)
int sys I 所属的导航系统
返回类型:
double O 导航系统伪距测量值的误差
处理过程:
- 确定 sys系统的误差因子。
- 计算由导航系统本身所带来的误差的方差。
- 如果
ionoopt==IONOOPT_IFLC
时,IFLC
模型的方差也会添加到最终计算得到的方差中。
我的疑惑:
- 本函数整体到底是为了计算哪一部分的误差,还是没搞清楚。
IFLC
模型的方差为什么可以用varr*=SQR(3.0)
计算?
int testsnr(int base, int freq, double el, double snr,
const snrmask_t *mask)
函数参数,5个
int base I rover or base-station (0:rover,1:base station)
int freq I frequency (0:L1,1:L2,2:L3,...)
double el I elevation angle (rad)
double snr I C/N0 (dBHz)
snrmask_t *mask I SNR mask
返回类型:
int O (1:masked,0:unmasked)
处理过程:
- 满足下列情况之一
!mask->ena[base]||freq<0||freq>=NFREQ
,返回 0.- 对
el
处理变换,根据后面i
的值得到不同的阈值minsnr
,而对1<=i<=8
的情况,则使用线性插值计算出minsnr
的值。
我的疑惑:
- 这个函数貌似是根据接收机高度角和信号频率来检测该信号是否可用,但
mask
在这里应该翻译成什么?看了下调用该函数的地方,返回 0(unmasked)似乎是合理的、希望看到的情况,即snr>=minsnr
。- 满足 1中条件的情况,感觉应该是不合理的情形,为什么反而返回 0呢?
double gettgd(int sat, const nav_t *nav)
函数参数,2个
int sat I satellite number,从 1开始
nav_t *nav I navigation data
返回类型:
double O tgd parameter (m)
- 从导航数据的星历中选择卫星号与
sat
相同的那个星历,读取 tgd[0]参数后乘上光速。
double ionmodel(gtime_t t, const double *ion, const double *pos,
const double *azel)
Klobuchar模型
时的电离层延时 (L1,m)。
函数参数,4个
gtime_t t I time (gpst)
double *ion I iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3}
double *pos I receiver position {lat,lon,h} (rad,m)
double *azel I azimuth/elevation angle {az,el} (rad)
返回类型:
double O ionospheric delay (L1) (m)
处理过程:
- 主要都是数学计算,其过程可以在
ICD-GPS-200C P148
中找到。
注意事项:
- 这里计算的电离层延时是相对于 GPS-L1信号而言的,其它频率信号需要进行一次转换。
- 计算过程中很多角度的单位是半圆,即 弧度。在阅读代码时,记住这一点非常重要!比如,虽然上述过程与
ICD-GPS-200C P148
中一致,但可能与大部分资料上的过程还是会有所区别。尤其是下面这个公式。
但是将下面公式的角度转化成半圆,即左右两边都除以 180,就可以得到上面的公式了!
int iontec(gtime_t time, const nav_t *nav, const double *pos,
const double *azel, int opt, double *delay, double *var)
函数参数,3个
gtime_t time I time (gpst)
nav_t *nav I navigation data
double *pos I receiver position {lat,lon,h} (rad,m)
double *azel I azimuth/elevation angle {az,el} (rad)
int opt I model option
bit0: 0:earth-fixed,1:sun-fixed
bit1: 0:single-layer,1:modified single-layer
double *delay O ionospheric delay (L1) (m)
double *var O ionospheric dealy (L1) variance (m^2)
返回类型:
int O (1:ok,0:error)
处理过程:
- 检测高度角和接收机高度是否大于阈值。否,则延迟为 0,方差为
VAR_NOTEC
,返回 1;是,则进入下一步。- 从
nav_t.tec
中找出第一个tec[i].time
>time
(输入参数,信号接收时间)的tec
数据。然后通过i==0||i>=nav->nt
,确保time
是在所给出的nav_t.tec
包含的时间段之中!- 通过确认所找到的时间段的右端点减去左端点,来确保时间间隔
!=
0。- 调用 iondelay来计算所属时间段两端端点的电离层延时。
- 由两端的延时,插值计算出观测时间点处的值。而对于两端延时的组合,有 3种情况。
① 两个端点都计算出错,输出错误信息,返回 0.
② 两个端点都有值,线性插值出观测时间点的值,返回 1.
③ 只有一个端点有值,将其结果作为观测时间处的值,返回 1.
注意事项:
- 由于是通过调用 iondelay来计算所属时间段端点的电离层延时,所以这里求出来的值是以
L1信号
为前提的。- 关于 5中的第 ②种情况,
RTKLIB manual P152 (E.5.20)式
是错误的,左端点TEC值得时间权重值应该是 。manual中可能是搞反了,源码中是正确的,与我的看法相同。
我的疑惑:
- 1中当高度角和接收机高度较小时,为什么延迟要为 0呢?
- 可能是个对最终结果没有什么影响的小细节, 虽然时间间隔
tt
后面用得到,但是由于 2中的操作,其实3中的时间间隔肯定是>0
的!- 目前关于
tec model
,我还没有找到很好的相关方面的文章!
int iondelay(gtime_t time, const tec_t *tec, const double *pos,
const double *azel, int opt, double *delay, double *var)
函数参数,3个
gtime_t time I time (gpst)
tec_t *tec I tec grid data
double *pos I receiver position {lat,lon,h} (rad,m)
double *azel I azimuth/elevation angle {az,el} (rad)
int opt I model option
bit0: 0:earth-fixed,1:sun-fixed
bit1: 0:single-layer,1:modified single-layer
double *delay O ionospheric delay (L1) (m)
double *var O ionospheric dealy (L1) variance (m^2)
返回类型:
int O (1:ok,0:error)
处理过程:
- 按照
RTKLIB manual P152
中的公式(E.5.19),先计算出与频率有关的中间项。- 整体过程是按照电离层的高度,从起始高度开始,逐层计算每一层的延时和方差,之后累加到一起。下面再具体阐述。
- 首先调用 ionppp函数,计算出在当前电离层高度时,电离层穿刺点的位置
{lat,lon,h} (rad,m)
和倾斜率( )。- 按照
opt
的值可能再次进行修正。opt&1
,则按照M-SLM映射函数
重新计算倾斜率;opt&2
,则在日固坐标系中考虑地球自转,重新计算穿刺点经度;- 由 TEC网格计算穿刺点的电子数总量,然后按照 (E.5.19)累加电离层延时(m)和方差。
注意事项:
- 这里在计算电离层延时(m)时,是假设信号为 L1的!
我的疑惑:
- 4中操作看不懂,貌似跟 地/日坐标系和
SL/M-SL
有关?- IONEX文件中的电离层模型,高度真的有很多层吗?
double ionppp(const double *pos, const double *azel, double re,
double hion, double *posp)
{lat,lon,h} (rad,m)
和倾斜率( )。
函数参数,5个
double *pos I receiver position {lat,lon,h} (rad,m)
double *azel I azimuth/elevation angle {az,el} (rad)
double re I earth radius (km)
double hion I altitude of ionosphere (km)
double *posp O pierce point position {lat,lon,h} (rad,m)
返回类型:
double O 倾斜率
处理过程:
- 与处理过程相对应的公式,请见
RTKLIB manual P151
注意事项:
- 说明文档中的
z
并不是仰角azel[1]
,而是仰角关于的补角,所以程序中在计算rp
是采用的是cos(azel[1])
的写法。- 可能因为后面再从 TEC网格数据中插值时,并不需要高度信息,所以这里穿刺点位置
posp
中的第三项高度,其实并没有进行赋值,
int interptec(const tec_t *tec, int k, const double *posp, double *value,
double *rms)
函数参数,5个
tec_t *tec I tec grid data
int k I 高度方向上的序号,可以理解为电离层序号
double *posp I pierce point position {lat,lon,h} (rad,m)
double *value O 计算得到的刺穿点处的电子数总量(tecu)
double *rms O 所计算的电子数总量的误差的标准差(tecu)
返回类型:
int O (1:ok,0:error)
处理过程:
- 将
value
和rms
所指向的值置为 0。- 检验
tec
的纬度和经度间隔是否为 0。是,则直接返回 0。- 将穿刺点的经纬度分别减去网格点的起始经纬度,再除以网格点间距,对结果进行取整,得到穿刺点所在网格的序号和穿刺点所在网格的位置(比例)。
- 按照下图的顺序,调用
dataindex
函数分别计算这些网格点的tec
数据在tec.data
中的下标,从而得到这些网格点处的TEC
值和相应误差的标准差。
- 如果四个网格点的 TEC值都 >0,则说明穿刺点位于网格内,使用双线性插值计算出穿刺点的 TEC值;否则使用最邻近的网格点值作为穿刺点的 TEC值,不过前提是网格点的 TEC>0;否则,选用四个网格点中 >0的值的平均值作为穿刺点的 TEC值。
注意事项:
- 对于lats[3],其含义分别为起始纬度、终止纬度和间隔,对 lons[3]、hgts[3],其含义也是类似的。
- 对于
dataindex
函数, i、j、k都是从 0开始的,意味着分别代表各自方向上第 i+1、j+1、k+1层,并且是按照纬度、经度、高度的优先顺序来存储网格点数据的。
我的疑惑:
- 关于输出参数
rms
,按照其名称,应该是均方根值
,但是在调用了该函数的 iondelay中,确是把它的平方当做方差的一部分进行累加。所以我估计tec.rms
值得应该是相应网格点数据值的方差?- 老实说,源码中关于
tec->lons[2]
大于或者小于 0所做的处理,并没有看得太明白。另外,个人感觉 3中减去网格点起始经度后的差值应该也不会超过 360°吧?- 整体由网格点数据插值穿刺点值得过程可以明白,但
tec.data
会 <0吗?还有在网格外是指某一个网格的外面,还是整体 TEC大网格的四个角外面?
double tropmodel(gtime_t time, const double *pos, const double *azel,
double humi)
Saastamoinen
模型,计算电离层延时(m)
函数参数,4个
gtime_t time I time
double *pos I receiver position {lat,lon,h} (rad,m)
double *azel I azimuth/elevation angle {az,el} (rad)
double humi I relative humidity
返回类型:
double O tropospheric delay (m)
处理过程:
- 与处理过程相对应的公式,请见
RTKLIB manual P149
我的疑惑:
- 源码中关于
trph
的计算,与大多数文献和RTKLIB manual P149 (E.5.4)
有所不同,咋回事儿呢?
int resdop(const obsd_t *obs, int n, const double *rs, const double *dts,
const nav_t *nav, const double *rr, const double *x,
const double *azel, const int *vsat, double *v, double *H)
函数参数,11个:
obsd_t *obs I observation data
int n I number of observation data
double *rs I satellite positions and velocities,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *dts I satellite clocks,长度为2*n, {bias,drift} (s|s/s)
nav_t *nav I navigation data
double *rr I receiver positions and velocities,长度为6,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
double *x I 本次迭代开始之前的定速值
double *azel I azimuth/elevation angle (rad)
int *vsat I 表征卫星在定速时是否有效
double *v O 定速方程的右端部分,速度残余
double *H O 定速方程中的几何矩阵
返回类型:
int O 定速时所使用的卫星数目
resdop ecef2pos xyz2enu matmul
处理过程:
- 调用 ecef2pos函数,将接收机位置由 ECEF转换为大地坐标系。
- 调用 xyz2enu函数,计算此时的坐标转换矩阵。
- 满足下列条件
obs[i].D[0]==0.0||lam==0.0||!vsat[i]||norm(rs+3+i*6,3)<=0.0
之一,则当前卫星在定速时不可用,直接进行下一次循环。- 计算当前接收机位置下 ENU中的视向量,然后转换得到 ECEF中视向量的值。
- 计算 ECEF中卫星相对于接收机的速度,然后再计算出考虑了地球自转的用户和卫星之间的几何距离变化率,校正公式见
RTKLIB manual P159 (E.6.29)
。- 根据公式 计算出方程右端项的多普勒残余,然后再构建左端项的几何矩阵。最后再将观测方程数增 1.
注意事项:
- 这里与定位不同,构建几何矩阵时,就只有 4个未知数,而定位时是有
NX
个。并且没有像定位那样为了防止亏秩而进行约束处理。- 多普勒定速方程中几何矩阵 G与定位方程中的一样,前三行都是 ECEF坐标系中由接收机指向卫星的单位观测矢量的反向。而由于转换矩阵 S本身是一个正交单位矩阵(),所以这里在计算 ECEF中的视向量时,对
E
进行了转置处理。- 这里构建的左端几何矩阵 H,也与定位方程中的一样,是大部分资料上的几何矩阵的转置。