rtklib---RTK代码流程

该博客主要用C语言实现RTK相关计算。介绍了整体框架,包括relpos函数、rtkinit初始化及nx和na求法,还有中间信息文件输出;阐述基准站单站残差计算,涉及主函数、多种改正及残差计算;最后说明了共识卫星的查找方法,即找出基准站和流动站共同观测到的卫星。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 整体框架

1.1 relpos( )

    rs---卫星位置/速度
    dts---卫星钟/钟差
    var---方差
    y---残差
    e---设计矩阵x/y/z系数
    azel---高度角方位角
    v---残差
    H---设计矩阵x/y/z/钟等全部系数
    R---方差-协方差阵
    xp---KF中间量

 

static int relpos(rtk_t *rtk, const obsd_t *obs, int nu, int nr,
                  const nav_t *nav)
{
    prcopt_t *opt=&rtk->opt;
    gtime_t time=obs[0].time;
    double *rs,*dts,*var,*y,*e,*azel,*freq,*v,*H,*R,*xp,*Pp,*xa,*bias,dt;
    int i,j,f,n=nu+nr,ns,ny,nv,sat[MAXSAT],iu[MAXSAT],ir[MAXSAT],niter;
    int info,vflg[MAXOBS*NFREQ*2+1],svh[MAXOBS*2];
    int stat=rtk->opt.mode<=PMODE_DGPS?SOLQ_DGPS:SOLQ_FLOAT;
    int nf=opt->ionoopt==IONOOPT_IFLC?1:opt->nf;
    
    trace(3,"relpos  : nx=%d nu=%d nr=%d\n",rtk->nx,nu,nr);
    
    dt=timediff(time,obs[nu].time);
    
    rs=mat(6,n); dts=mat(2,n); var=mat(1,n); y=mat(nf*2,n); e=mat(3,n);
    azel=zeros(2,n); freq=zeros(nf,n);
    
    for (i=0;i<MAXSAT;i++) {
        rtk->ssat[i].sys=satsys(i+1,NULL);
        for (j=0;j<NFREQ;j++) rtk->ssat[i].vsat[j]=0;
        for (j=1;j<NFREQ;j++) rtk->ssat[i].snr [j]=0;
    }
    /* satellite positions/clocks 卫星位置/钟*/
    satposs(time,obs,n,nav,opt->sateph,rs,dts,var,svh);
    
    /* UD (undifferenced) residuals for base station 基准站非差残差*/
    if (!zdres(1,obs+nu,nr,rs+nu*6,dts+nu*2,var+nu,svh+nu,nav,rtk->rb,opt,1,
               y+nu*nf*2,e+nu*3,azel+nu*2,freq+nu*nf)) {
        errmsg(rtk,"initial base station position error\n");
        
        free(rs); free(dts); free(var); free(y); free(e); free(azel);
        free(freq);
        return 0;
    }
    /* time-interpolation of residuals (for post-processing) */
    if (opt->intpref) {
        dt=intpres(time,obs+nu,nr,nav,rtk,y+nu*nf*2);
    }
    /* select common satellites between rover and base-station 选择基准站和流动站共识卫星 */
    if ((ns=selsat(obs,azel,nu,nr,opt,sat,iu,ir))<=0) {
        errmsg(rtk,"no common satellite\n");
        
        free(rs); free(dts); free(var); free(y); free(e); free(azel);
        free(freq);
        return 0;
    }
    /* temporal update of states 状态一步预测--卡尔曼 */
    udstate(rtk,obs,sat,iu,ir,ns,nav);
    
    trace(4,"x(0)="); tracemat(4,rtk->x,1,NR(opt),13,4);
    
    xp=mat(rtk->nx,1); Pp=zeros(rtk->nx,rtk->nx); xa=mat(rtk->nx,1);
    matcpy(xp,rtk->x,rtk->nx,1);
    
    ny=ns*nf*2+2;
    v=mat(ny,1); H=zeros(rtk->nx,ny); R=mat(ny,ny); bias=mat(rtk->nx,1);
    
    /* add 2 iterations for baseline-constraint moving-base */
    niter=opt->niter+(opt->mode==PMODE_MOVEB&&opt->baseline[0]>0.0?2:0);
    
    for (i=0;i<niter;i++) {
        /* UD (undifferenced) residuals for rover 流动站非差残差*/
        if (!zdres(0,obs,nu,rs,dts,var,svh,nav,xp,opt,0,y,e,azel,freq)) {
            errmsg(rtk,"rover initial position error\n");
            stat=SOLQ_NONE;
            break;
        }
        /* DD (double-differenced) residuals and partial derivatives 站星双差和设计矩阵*/
        if ((nv=ddres(rtk,nav,dt,xp,Pp,sat,y,e,azel,freq,iu,ir,ns,v,H,R,
                      vflg))<1) {
            errmsg(rtk,"no double-differenced residual\n");
            stat=SOLQ_NONE;
            break;
        }
        /* Kalman filter measurement update 卡尔曼滤波*/
        matcpy(Pp,rtk->P,rtk->nx,rtk->nx);
        if ((info=filter(xp,Pp,H,v,R,rtk->nx,nv))) {
            errmsg(rtk,"filter error (info=%d)\n",info);
            stat=SOLQ_NONE;
            break;
        }
        trace(4,"x(%d)=",i+1); tracemat(4,xp,1,NR(opt),13,4);
    }


    if (stat!=SOLQ_NONE&&zdres(0,obs,nu,rs,dts,var,svh,nav,xp,opt,0,y,e,azel,
                               freq)) {
        
        /* post-fit residuals for float solution 后验残差 */
        nv=ddres(rtk,nav,dt,xp,Pp,sat,y,e,azel,freq,iu,ir,ns,v,NULL,R,vflg);
        
        /* validation of float solution 后验残差检验*/
        if (valpos(rtk,v,R,vflg,nv,4.0)) {
            
            /* update state and covariance matrix 更新状态和转移矩阵*/
            matcpy(rtk->x,xp,rtk->nx,1);
            matcpy(rtk->P,Pp,rtk->nx,rtk->nx);
            
            /* update ambiguity control struct */
            rtk->sol.ns=0;
            for (i=0;i<ns;i++) for (f=0;f<nf;f++) {
                if (!rtk->ssat[sat[i]-1].vsat[f]) continue;
                rtk->ssat[sat[i]-1].lock[f]++;
                rtk->ssat[sat[i]-1].outc[f]=0;
                if (f==0) rtk->sol.ns++; /* valid satellite count by L1 */
            }
            /* lack of valid satellites */
            if (rtk->sol.ns<4) stat=SOLQ_NONE;
        }
        else stat=SOLQ_NONE;
    }
//后面就是存储结果了
    .....
    .....
    .....
}

1.2 rtkinit( ) ---初始化

initialize RTK control struct

int nx,na;          /* number of float states/fixed states 浮点解和固定解状态量个数*/

double *x, *P;      /* float states and their covariance 浮点状态存储和方差*/

extern void rtkinit(rtk_t *rtk, const prcopt_t *opt)
{
    sol_t sol0={{0}};
    ambc_t ambc0={{{0}}};
    ssat_t ssat0={0};
    int i;
    
    trace(3,"rtkinit :\n");
    
    rtk->sol=sol0;
    for (i=0;i<6;i++) rtk->rb[i]=0.0;
    rtk->nx=opt->mode<=PMODE_FIXED?NX(opt):pppnx(opt); //NX比NR多NB个
    rtk->na=opt->mode<=PMODE_FIXED?NR(opt):pppnx(opt);
    rtk->tt=0.0;
    //根据nx、na申请内存。rtklib用的都是最大卫星数,会造成不必要的空间浪费
    rtk->x=zeros(rtk->nx,1);
    rtk->P=zeros(rtk->nx,rtk->nx);
    rtk->xa=zeros(rtk->na,1);
    rtk->Pa=zeros(rtk->na,rtk->na);
    rtk->nfix=rtk->neb=0;
    for (i=0;i<MAXSAT;i++) {
        rtk->ambc[i]=ambc0;
        rtk->ssat[i]=ssat0;
    }
    for (i=0;i<MAXERRMSG;i++) rtk->errbuf[i]=0;
    rtk->opt=*opt;
}

1.2.1 nx和na的求法

先不考虑PPP

    rtk->nx=opt->mode<=PMODE_FIXED?NX(opt):pppnx(opt); //NX比NR多NB个
    rtk->na=opt->mode<=PMODE_FIXED?NR(opt):pppnx(opt);

可知,nx比na多NB( ) 。这就是两者的差别

#define NR(opt)     (NP(opt)+NI(opt)+NT(opt)+NL(opt))
#define NX(opt)     (NR(opt)+NB(opt))

当<=PMODE_DGPS时,说明就是伪距单点或差分,不用模糊度固定,那么就是0

MAXSAT*NF(opt)),所有卫星*每个卫星参与解算频点数。其中MAXSAT是所有系统卫星的数量。假如总得卫星是200颗,虽然你只能观测到40颗,那MAXSAT仍为200。这种做法,省去了统计观测卫星数麻烦,但是占用大量内存。NF( )是参与解算的频点数量( L1,L1+2,L1+2+5等)。

#define NB(opt)     ((opt)->mode<=PMODE_DGPS?0:MAXSAT*NF(opt))

无电离层组合,那就是1;非差,是参与的频点数

#define NF(opt)     ((opt)->ionoopt==IONOOPT_IFLC?1:(opt)->nf)

int nf;             /* number of frequencies (1:L1,2:L1+L2,3:L1+L2+L5) */

是否为动态模型。3--position  9--位置、速度、加速度

#define NP(opt)     ((opt)->dynamics==0?3:9)

IONOOPT_EST是估计电离层,长基线适用。最大卫星数,每个频率的电离层延迟不一样,但是不同频率有相关性,所以只需要每颗卫星第1频点就可

#define NI(opt)     ((opt)->ionoopt!=IONOOPT_EST?0:MAXSAT)

对流层。不是估计--0;是估计--(根据估计的方法)2/6

#define NT(opt)     ((opt)->tropopt<TROPOPT_EST?0:((opt)->tropopt<TROPOPT_ESTG?2:6))

启用GLONASS?

#define NL(opt)     ((opt)->glomodear!=2?0:NFREQGLO)

1.3 中间信息文件(.stat)输出

outsolstat(rtk)----->rtkoutstat( )

卫星的中间过程信息,ssat结构体中的。高度角、方位角、伪距残差啥的。

一些中间的位置、速度参数、电离层对流层啥。以后用的时候再看

2 基准站单站残差计算--zdres( )

2.1 主函数--zdres( )

基准站非差残差
nu是流动站个数,流动站和基准站观测值都在obs[ ]中,前半是流动,后半是基准,所以我们处理基准站数据需要移动指针
obs+nu,将指针移到第一个基准站
这都是一维仿多维,所以移下标就有nu*2,nu*nf*2

nf是选择的频点
nu*nf*2--每个卫星每个频点,×2是因为有伪距和载波

zdres(1,obs+nu,nr,rs+nu*6,dts+nu*2,var+nu,svh+nu,nav,rtk->rb,opt,1,
               y+nu*nf*2,e+nu*3,azel+nu*2,freq+nu*nf)

/*
    index   和天线改正有关
    n   卫星数量
    rs  卫星位置和速度
    dts 卫星钟和钟差
    var 方差
    svh 卫星健康状态
    nav 星历
    rr 接收机位置(一般概略位置)xyz
    y   残差
    e   设计矩阵XYZ
    freq    选择的频点
    base    基准站--1 ;流动站---0

*/

static int zdres(int base, const obsd_t *obs, int n, const double *rs,
                 const double *dts, const double *var, const int *svh,
                 const nav_t *nav, const double *rr, const prcopt_t *opt,
                 int index, double *y, double *e, double *azel, double *freq)
{
    double r,rr_[3],pos[3],dant[NFREQ]={0},disp[3];
    double zhd,zazel[]={0.0,90.0*D2R};
    int i,nf=NF(opt);
    
    trace(3,"zdres   : n=%d\n",n);
    //初始化残差
    for (i=0;i<n*nf*2;i++) y[i]=0.0;
    
    if (norm(rr,3)<=0.0) return 0; /* no receiver position,没有接收机位置就返回 */
    //复制一份,因为后面在固体潮改正时会有改动
    for (i=0;i<3;i++) rr_[i]=rr[i];
    
    /* 
        earth tide correction 固体海洋潮改正
        基准站和流动站相近,影响一样,作差可消除。所以RTK中一般不改正
        PPP要改
    */
    if (opt->tidecorr) {
        tidedisp(gpst2utc(obs[0].time),rr_,opt->tidecorr,&nav->erp,
                 opt->odisp[base],disp);
        for (i=0;i<3;i++) rr_[i]+=disp[i];//修正xyz方向坐标
    }
    ecef2pos(rr_,pos);
    //遍历每一颗卫星
    for (i=0;i<n;i++) {
        /* compute geometric-range and azimuth/elevation angle */
        if ((r=geodist(rs+i*6,rr_,e+i*3))<=0.0) continue; //与概略位置距离
        if (satazel(pos,e+i*3,azel+i*2)<opt->elmin) continue;//高度角方位角
        
        /* excluded satellite? */
        if (satexclude(obs[i].sat,var[i],svh[i],opt)) continue;
        
        /* satellite clock-bias 参考先验残差计算公式*/
        r+=-CLIGHT*dts[i*2];
        
        /* troposphere delay model (hydrostatic)对流层 很少改正,理由同上 */
        zhd=tropmodel(obs[0].time,pos,zazel,0.0);
        r+=tropmapf(obs[i].time,pos,azel+i*2,NULL)*zhd;
        
        /* 
            receiver antenna phase center correction 天线相位中心改正 
            antdel[0]是流动站天线中心偏差 [1]是基准站天线中心偏差
            对于RTK天线改正可有可无,PPP是一定要有的
        */
        antmodel(opt->pcvr+index,opt->antdel[index],azel+i*2,opt->posopt[1],
                 dant);
        
        /* 
            UD phase/code residual for satellite 
            卫星残差计算
            针对的是卫星的每个频点的载波和伪距(2*nf)
            dant    每个频点的距离改正
        */
        zdres_sat(base,r,obs+i,nav,azel+i*2,dant,opt,y+i*nf*2,freq+i*nf);
    }
    
    return 1;
}

2.2 固体/海洋潮改正--tidedisp

RTK中一般不作要求,PPP要改

返回值是disp,displacement by earth tides (ecef) (m)  返回--xyz方向下的改正

2.3 对流层电离层改正

中短基线,基准站和流动站很近,作差可消除。这里暂时不讨论

2.4 天线相位中心改正--antmodel

            antdel[0]是流动站天线中心偏差 [1]是基准站天线中心偏差
            对于RTK天线改正可有可无,PPP是一定要有的

* compute antenna offset by antenna phase center parameters
* args   : pcv_t *pcv       I   antenna phase center parameters 天线相位中心参数
*          double *del      I   antenna delta {e,n,u} (m)  天线相位中心相对于标志点的enu方向偏差
*          double *azel     I   azimuth/elevation for receiver {az,el} (rad) 高度角方位角
*          int     opt      I   option (0:only offset,1:offset+pcv)
*          double *dant     O   range offsets for each frequency (m) 每个频点的距离偏移。因为不同频率的天线偏差不一样(码偏差)

extern void antmodel(const pcv_t *pcv, const double *del, const double *azel,int opt,double *dant)

注意返回,dant。每个频点的天线相位改正,因为不同频率不一样(码偏差)

2.5 残差计算--zdres_sa

我们注意到,如果是无电离层组合,默认使用的是第1/2频点的观测值。如果我们想做L1+L3的无电离层组合,需要自行改写代码

只讨论下面电离层非差组合的代码

在这个函数中遍历了卫星的所有频点。我们知道在主函数zdres( )中遍历的是所有卫星,所以最后处理的是每颗卫星的所有频点

        //遍历每一个频点
        for (i=0;i<nf;i++) {
            if ((freq[i]=sat2freq(obs->sat,obs->code[i],nav))==0.0) continue; //频点的频率
            
            /* check SNR mask 信噪比检验 */
            if (testsnr(base,i,azel[1],obs->SNR[i]*SNR_UNIT,&opt->snrmask)) {
                continue;
            }
            /* 
                residuals = observable - pseudorange 残差计算
                残差=观测-概略位置伪距。???残差应该是真实值和观测值之差。这个好奇怪,以后再说
                2*nf是因为有载波和伪距
                y[]前半部分存载波,后半部分是伪距残差
            */
            if (obs->L[i]!=0.0) y[i   ]=obs->L[i]*CLIGHT/freq[i]-r-dant[i];//载波以波长存储,所以有C/frq(速度/频率)
            if (obs->P[i]!=0.0) y[i+nf]=obs->P[i]               -r-dant[i];
        }

3 共识卫星--selsat

基准站和流动站共同观测到的卫星。每个卫星都有编号sat,我们只需找到哪个卫星编号在基准站和流动站中都出现过即可。

iu,ir分别记录共识卫星sat在观测值obs[ ](流动站和基准站的下标)

static int selsat(const obsd_t *obs, double *azel, int nu, int nr,
                  const prcopt_t *opt, int *sat, int *iu, int *ir)
{
    int i,j,k=0;
    
    trace(3,"selsat  : nu=%d nr=%d\n",nu,nr);
    /*
        obs[i]前面是流动站,后面是基准站。这个函数是要找流动站和基准站共同观测到的卫星
        [i]从流动站变量,[j]从基准站遍历。i--,j--都是在sat不相等时在调整
        当找到共同的卫星sat相等,sat[k]负责记录该共同卫星编号(sat)
        iu[k]记录卫星在流动站的索引(在obs中,obs[i])
        iu[k]记录卫星在基准站的索引(在obs中,obs[j])
    */


    for (i=0,j=nu;i<nu&&j<nu+nr;i++,j++) {
        if      (obs[i].sat<obs[j].sat) j--;
        else if (obs[i].sat>obs[j].sat) i--;
        else if (azel[1+j*2]>=opt->elmin) { /* elevation at base station */
            sat[k]=obs[i].sat; iu[k]=i; ir[k++]=j;
            trace(4,"(%2d) sat=%3d iu=%2d ir=%2d\n",k-1,obs[i].sat,i,j);
        }
    }
    return k;
}

### RTKLIB源码示例与GPS GNSS实现 RTKLIB 是一种广泛应用于全球导航卫星系统(GNSS)定位技术的开源库,支持实时动态(RTK)差分定位和其他多种定位模式。以下是基于 RTKLIB 的典型代码片段以及其实现 GPS 和 GNSS 功能的核心逻辑。 #### 配置环境 为了运行和理解 RTKLIB 源码,首先需要设置开发环境。假设已按照引用描述完成了 RTKLIB 在 Visual Studio 中的配置[^4],接下来可以通过分析核心功能模块来了解其工作原理。 --- #### 核心代码示例:基线解算 以下是一个简化版的 RTKLIB 基线解算过程: ```c #include "rtklib.h" int main() { obs_t obs; // 卫星观测数据结构体 nav_t nav; // 星历数据结构体 sol_t sol; // 解算结果结构体 rtk_t rtk; // RTK 结构体 double pos[3]; // 输出位置坐标 init_rtk(&rtk); // 初始化 RTK 参数 read_obs("base.obs", &obs); // 读取基准站观测文件 read_nav("gps.nav", &nav); // 读取星历文件 for (int i = 0; i < obs.n; i++) { if (!rtkpos(obs.data[i], &nav, &rtk, &sol)) { printf("Error in positioning at epoch %d\n", i); continue; } ecef2pos(sol.rr, pos); // 将 ECEF 转换为地理坐标 printf("Position: Lat=%.6f, Lon=%.6f, Ht=%.3f\n", DEG(pos[0]), DEG(pos[1]), pos[2]); } return 0; } ``` 上述代码展示了如何利用 `rtkpos` 函数进行单历元的基线解算,并将最终的位置转换为纬度、经度和高程形式输出[^3]。 --- #### 关键算法解析 ##### 1. **几何距离计算** 在 RTK 差分解算过程中,几何距离的计算是非常重要的一步。通常会通过如下方式实现: ```c double geo_distance(double *rx_pos, double *sat_pos) { double dx = rx_pos[0] - sat_pos[0]; double dy = rx_pos[1] - sat_pos[1]; double dz = rx_pos[2] - sat_pos[2]; return sqrt(dx*dx + dy*dy + dz*dz); } ``` 此函数用于计算接收机与卫星之间的欧几里得距离[^2]。 ##### 2. **伪距残差计算** 伪距残差是非差分定位的关键部分之一,具体实现可能类似于以下代码: ```c void zdres(int type, const obsd_t *obs, int nr, const double rs[][6], const double dts[], unsigned char svh[], const nav_t *nav, rtksol_t *rb, opt_t *opt, int flag, double y[], double e[], double azel[]) { for (int i = 0; i < nr && i < 2 * MAXOBS; i++) { double pr_res = compute_pseudorange_residual(obs[i].P, rs[i][0], rb->rb); y[i] = pr_res; if (pr_res > THRESHOLD || !check_satellite_health(svh[i])) { e[i] = 0; // 设置权重为零表示该测量不可靠 } else { e[i] = WEIGHT_FACTOR / pow(pr_res, 2); } } } ``` 这段代码实现了伪距残差的计算及其可靠性评估。 --- #### 数据流概述 RTKLIB 的整体数据处理流程可以分为以下几个阶段: 1. 输入原始观测数据和星历数据。 2. 对观测数据进行预处理,包括剔除异常值和健康状态检测。 3. 使用卡尔曼滤波或其他方法估计接收机的状态向量(如位置、速度等)。 4. 输出最终的定位结果或误差统计信息。 这些步骤的具体实现依赖于多个内部函数和外部输入文件的支持[^1]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值