MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
mrtk_clas_osr.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * mrtk_clas_osr.c : CLAS SSR-to-OSR (Observation Space Representation) conversion
3 *
4 * Copyright (C) 2026 H.SHIONO (MRTKLIB Project)
5 * Copyright (C) 2023-2025 Cabinet Office, Japan
6 * Copyright (C) 2024-2025 Lighthouse Technology & Consulting Co. Ltd.
7 * Copyright (C) 2023-2025 Japan Aerospace Exploration Agency
8 * Copyright (C) 2023-2025 TOSHIBA ELECTRONIC TECHNOLOGIES CORPORATION
9 * Copyright (C) 2015- Mitsubishi Electric Corp.
10 * Copyright (C) 2014 Geospatial Information Authority of Japan
11 * Copyright (C) 2014 T.SUZUKI
12 * Copyright (C) 2007-2023 T.TAKASU
13 *
14 * SPDX-License-Identifier: BSD-2-Clause
15 *----------------------------------------------------------------------------*/
16/**
17 * @file mrtk_clas_osr.c
18 * @brief CLAS SSR-to-OSR (Observation Space Representation) conversion.
19 *
20 * References:
21 * [1] claslib cssr2osr.c -- upstream SSR->OSR implementation
22 * [2] IS-QZSS-L6-003 -- CLAS Compact SSR Specification
23 *
24 * Notes:
25 * - Ported from upstream claslib cssr2osr.c.
26 * - ENA_PPP_RTK path always enabled; CSSR2OSR_VRS path removed.
27 * - Converts SSR compact corrections (orbit, clock, bias, STEC, ZWD) into
28 * per-satellite observation-space residuals (clas_osrd_t).
29 * - Static variables from upstream moved to clas_osr_ctx_t for thread safety.
30 * - Uses nav->ssr_ch[0][sat-1] (channel 0 by default).
31 * - nav->lam[sat-1] replaced by local lam[] computed from sat2freq().
32 * - Several upstream functions stubbed (ISB, L2C, etc.).
33 */
34#include <math.h>
35#include <stdlib.h>
36#include <string.h>
37
39#include "mrtklib/mrtk_astro.h"
40#include "mrtklib/mrtk_atmos.h"
41#include "mrtklib/mrtk_clas.h"
42#include "mrtklib/mrtk_const.h"
43#include "mrtklib/mrtk_coords.h"
44#include "mrtklib/mrtk_eph.h"
45#include "mrtklib/mrtk_geoid.h"
46#include "mrtklib/mrtk_mat.h"
47#include "mrtklib/mrtk_nav.h"
48#include "mrtklib/mrtk_obs.h"
49#include "mrtklib/mrtk_opt.h"
51#include "mrtklib/mrtk_rtkpos.h"
52#include "mrtklib/mrtk_sol.h"
53#include "mrtklib/mrtk_tides.h"
54#include "mrtklib/mrtk_time.h"
55#include "mrtklib/mrtk_trace.h"
56
57/*============================================================================
58 * Local Macros
59 *===========================================================================*/
60
61#define SQR(x) ((x) * (x))
62
63/* PPP-RTK state index macros (duplicated from mrtk_ppp_rtk.c) */
64#define NF_RTK(opt) ((opt)->nf)
65#define NP_RTK(opt) ((opt)->dynamics == 0 ? 3 : 9)
66#define NI_RTK(opt) (((opt)->ionoopt == IONOOPT_EST || (opt)->ionoopt == IONOOPT_EST_ADPT) ? MAXSAT : 0)
67static inline int NT_RTK(const prcopt_t* opt) {
68 if (opt->tropopt < TROPOPT_EST) return 0;
69 if (opt->tropopt < TROPOPT_ESTG) return 1;
70 return 3;
71}
72#define NR_RTK(opt) (NP_RTK(opt) + NI_RTK(opt) + NT_RTK(opt))
73#define IB_RTK(s, f, opt) (NR_RTK(opt) + MAXSAT * (f) + (s) - 1)
74
75/** Earth gravitational constant (m^3/s^2) -- for Shapiro correction */
76#define GME 3.986004415E+14
77
78/**
79 * @brief Check if two obs codes are on the same frequency band.
80 *
81 * CLAS corrections use combined tracking codes (e.g. L2X = L2C M+L),
82 * while receivers report specific codes (e.g. L2W, L2L). These are
83 * equivalent for bias correction purposes since CLAS biases are
84 * frequency-band level, not tracking-mode specific.
85 *
86 * @param[in] sys Satellite system (SYS_GPS, SYS_GAL, etc.)
87 * @param[in] code1 First obs code (CODE_L??)
88 * @param[in] code2 Second obs code (CODE_L??)
89 * @return 1 if codes are on the same frequency band, 0 otherwise
90 */
91static int code_on_same_freq(int sys, uint8_t code1, uint8_t code2) {
92 int f1, f2;
93 if (code1 == code2) return 1;
94 if (code1 == 0 || code2 == 0) return 0;
95 f1 = code2freq_idx(sys, code1);
96 f2 = code2freq_idx(sys, code2);
97 return (f1 >= 0 && f1 == f2);
98}
99
100static int pcv_slot_has_data(const pcv_t* pcv, int slot) {
101 return pcv && 0 <= slot && slot < NFREQPCV && (norm(pcv->off[slot], 3) > 0.0 || norm(pcv->var[slot], 19) > 0.0);
102}
103
104static int claslib_pcv_slot(const pcv_t* pcv, int f) {
105 /* claslib compacts ANTEX frequency numbers {1,2,5} into OSR slots {0,1,2}.
106 * Its reader ignores the constellation prefix, so C02 can overwrite the
107 * compact L2 slot after G02/J02 in multi-GNSS receiver ANTEX entries. */
108 if (f == 1 && pcv_slot_has_data(pcv, 4)) return 4;
109 return f;
110}
111
112/** Frequency pair selection modes (upstream claslib rtklib.h) */
113#define POSL1 1 /* L1 single freq positioning */
114#define POSL1L2 2 /* L1+L2 dual freq positioning */
115#define POSL1L5 3 /* L1+L5 dual freq positioning */
116#define POSL1L2L5 4 /* L1+L2+L5 triple freq positioning */
117#define POSL1L5_L2 5 /* L1+L5(or L2) dual freq positioning */
118
119/** CSSR signal mode to frequency band mapping table.
120 * Index = signal tracking mode code, value = 1:L1,2:L2,3:L5,4:L6,5:L7,6:L8
121 */
122static const unsigned char obsfreqs[] = {
123 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0- 9 */
124 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, /* 10-19 */
125 2, 2, 2, 2, 3, 3, 3, 5, 5, 5, /* 20-29 */
126 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, /* 30-39 */
127 2, 2, 4, 4, 3, 3, 3, 1, 1, 0 /* 40-49 */
128};
129
130/*============================================================================
131 * Local Helper: Shapiro Time Delay (from upstream rtkcmn.c)
132 *
133 * Not yet available as a public MRTKLIB function. Defined here as static.
134 *===========================================================================*/
135
136/**
137 * @brief Compute Shapiro (relativistic) time delay correction.
138 * @param[in] rsat Satellite position (ECEF) (m)
139 * @param[in] rrcv Receiver position (ECEF) (m)
140 * @return Shapiro correction (m)
141 */
142static double shapiro(const double* rsat, const double* rrcv) {
143 double rs, rr, rrs, rsr[3];
144 int i;
145
146 rs = norm(rsat, 3);
147 rr = norm(rrcv, 3);
148 for (i = 0; i < 3; i++) {
149 rsr[i] = rsat[i] - rrcv[i];
150 }
151 rrs = norm(rsr, 3);
152
153 return 2.0 * GME / CLIGHT / CLIGHT * log((rs + rr + rrs) / (rs + rr - rrs));
154}
155
156/*============================================================================
157 * Local Helper: Phase Windup Correction (from upstream rtkcmn.c)
158 *
159 * Not yet available as a public MRTKLIB function. Defined here as static.
160 *===========================================================================*/
161
162/**
163 * @brief Compute phase windup correction.
164 * @param[in] time Time (GPST)
165 * @param[in] rs Satellite position + velocity (ECEF, 6 elements)
166 * @param[in] rr Receiver position (ECEF, 3 elements)
167 * @param[in,out] phw Phase windup correction (cycle), previous value as input
168 */
169static void windupcorr(gtime_t time, const double* rs, const double* rr, double* phw) {
170 double ek[3], exs[3], eys[3], ezs[3], ess[3], exr[3], eyr[3];
171 double eks[3], ekr[3], E[9];
172 double dr[3], ds[3], drs[3], r[3], pos[3], rsun[3], cosp, ph;
173 double erpv[5] = {0};
174 double we[3] = {0.0, 0.0, OMGE}, satpos[3], o_cross_r[3];
175 int i;
176
177 /* sun position in ecef */
178 sunmoonpos(gpst2utc(time), erpv, rsun, NULL, NULL);
179
180 /* unit vector satellite to receiver */
181 for (i = 0; i < 3; i++) {
182 r[i] = rr[i] - rs[i];
183 }
184 if (!normv3(r, ek)) {
185 return;
186 }
187
188 /* unit vectors of satellite antenna */
189 for (i = 0; i < 3; i++) {
190 r[i] = -rs[i];
191 }
192 if (!normv3(r, ezs)) {
193 return;
194 }
195
196 for (i = 0; i < 3; i++) {
197 satpos[i] = rs[i];
198 }
199 cross3(we, satpos, o_cross_r);
200 for (i = 0; i < 3; i++) {
201 r[i] = rs[i + 3] + o_cross_r[i];
202 }
203
204 if (!normv3(r, ess)) {
205 return;
206 }
207 cross3(ezs, ess, r);
208 if (!normv3(r, eys)) {
209 return;
210 }
211 cross3(eys, ezs, exs);
212
213 /* unit vectors of receiver antenna */
214 ecef2pos(rr, pos);
215 xyz2enu(pos, E);
216 exr[0] = E[0];
217 exr[1] = E[3];
218 exr[2] = E[6];
219 eyr[0] = E[1];
220 eyr[1] = E[4];
221 eyr[2] = E[7];
222
223 /* phase windup effect */
224 cross3(ek, eys, eks);
225 cross3(ek, eyr, ekr);
226 for (i = 0; i < 3; i++) {
227 ds[i] = exs[i] - ek[i] * dot(ek, exs, 3) - eks[i];
228 dr[i] = exr[i] - ek[i] * dot(ek, exr, 3) + ekr[i];
229 }
230 cosp = dot(ds, dr, 3) / norm(ds, 3) / norm(dr, 3);
231 if (cosp < -1.0) {
232 cosp = -1.0;
233 } else if (cosp > 1.0) {
234 cosp = 1.0;
235 }
236 ph = acos(cosp) / 2.0 / PI;
237 cross3(ds, dr, drs);
238 if (dot(ek, drs, 3) < 0.0) {
239 ph = -ph;
240 }
241
242 *phw = ph + floor(*phw - ph + 0.5); /* in cycle */
243}
244
245/*============================================================================
246 * Local Helper: Compute Wavelengths for a Satellite
247 *
248 * Upstream claslib uses nav->lam[sat-1][f]. MRTKLIB does not have this
249 * field in nav_t; we compute wavelengths via sat2freq().
250 *===========================================================================*/
251
252extern double sat2freq(int sat, uint8_t code, const nav_t* nav);
253
254/**
255 * @brief Fill wavelength array for a satellite from observation codes.
256 * @param[in] sat Satellite number
257 * @param[in] obs Observation data for this satellite
258 * @param[in] nav Navigation data (for GLONASS FCN)
259 * @param[out] lam Wavelength array [NFREQ+NEXOBS]
260 */
261static void sat_wavelengths(int sat, const obsd_t* obs, const nav_t* nav, double* lam) {
262 int f;
263 double freq;
264 for (f = 0; f < NFREQ + NEXOBS; f++) {
265 freq = sat2freq(sat, obs->code[f], nav);
266 lam[f] = (freq > 0.0) ? CLIGHT / freq : 0.0;
267 }
268}
269
270/*============================================================================
271 * Local Helper: Count Signal Modes in corr->smode[sat-1][]
272 *===========================================================================*/
273
274/**
275 * @brief Count non-zero entries in a signal mode array (equivalent to nsig).
276 * @param[in] smode Signal mode array for one satellite
277 * @return Number of valid signal modes
278 */
279static int count_nsig(const int smode[MAXCODE]) {
280 int i, n = 0;
281 for (i = 0; i < MAXCODE; i++) {
282 if (smode[i] != 0) {
283 n++;
284 }
285 }
286 return n;
287}
288
289/*============================================================================
290 * clas_osr_ctx_init
291 *===========================================================================*/
292
293/**
294 * @brief Initialize OSR conversion context (zero all persistent state).
295 * @param[out] ctx OSR context to initialize
296 */
298 int ch, i;
299 memset(ctx, 0, sizeof(clas_osr_ctx_t));
300 for (ch = 0; ch < CLAS_CH_NUM; ch++) {
301 for (i = 0; i < MAXSAT; i++) {
302 ctx->satcorr[ch][i].currtow = -1;
303 ctx->satcorr[ch][i].prevtow = -1;
304 ctx->satcorr[ch][i].previode = -1;
305 ctx->satcorr[ch][i].prev_orb_tow = -1;
306 }
307 }
308 ctx->initialized = 1;
309}
310
311/*============================================================================
312 * clas_osr_prectrop -- Precise Troposphere Model
313 *===========================================================================*/
314
315/**
316 * @brief Precise troposphere model for CLAS (MOPS standard + mapping function).
317 *
318 * Computes slant tropospheric delay from zenith wet and total delays
319 * obtained from CLAS grid interpolation.
320 *
321 * @param[in] time Observation time
322 * @param[in] pos Receiver position {lat,lon,hgt} (rad,m)
323 * @param[in] azel Azimuth/elevation {az,el} (rad)
324 * @param[in] zwd Zenith wet delay from grid (m)
325 * @param[in] ztd Zenith total delay from grid (m)
326 * @return Slant tropospheric delay (m), 0 on error
327 */
328double clas_osr_prectrop(gtime_t time, const double* pos, const double* azel, double zwd, double ztd) {
329 double m_d, m_w, tdvd, twvd, gh;
330
331 gh = geoidh(pos);
332
333 /* get_stTv is static in mrtk_clas_grid.c; use the public wrapper.
334 * clas_get_stTv() is declared in mrtk_clas.h but implemented as the
335 * static get_stTv() in mrtk_clas_grid.c. Since this function is in the
336 * same library, we forward-declare and call it directly. */
337 extern int get_stTv(gtime_t t, const double lat, const double hs, const double hg, double* tdv, double* twv);
338 if (get_stTv(time, pos[0], pos[2], gh, &tdvd, &twvd)) {
339 return 0.0;
340 }
341
342 /* mapping function */
343 m_d = tropmapf(time, pos, azel, &m_w);
344
345 return m_d * tdvd * ztd + m_w * twvd * zwd;
346}
347
348/*============================================================================
349 * clas_osr_selfreqpair -- Select Frequency Pair
350 *===========================================================================*/
351
352/**
353 * @brief Select frequency pair for multi-frequency processing.
354 *
355 * Returns a bitmask indicating which secondary frequencies to use alongside L1.
356 * The selection depends on the satellite system and the processing options
357 * posopt[10] (GPS) and posopt[12] (QZS).
358 *
359 * Note: MRTKLIB's prcopt_t.posopt[] is 6 elements (indices 0-5) while upstream
360 * claslib uses indices up to 12. For forward compatibility the indices 10/12
361 * are checked against the array size; if out of range, default to L1+L2.
362 *
363 * @param[in] sat Satellite number
364 * @param[in] opt Processing options
365 * @param[in] obs Observation data for this satellite
366 * @return Frequency pair bitmask (0=L1 only, 1=L2, 2=L5, 3=L2+L5)
367 */
368int clas_osr_selfreqpair(int sat, const prcopt_t* opt, const obsd_t* obs) {
369 int optf, sys;
370
371 sys = satsys(sat, NULL);
372
373 /* GPS: posopt[10], QZS: posopt[12], default: 0 (→ L1+L2) */
374 optf = (sys == SYS_GPS) ? opt->posopt[10] : (sys == SYS_QZS) ? opt->posopt[12] : 0;
375
376 /* For GAL: prefer L5 if available */
377 if (sys & SYS_GAL) {
378 if (obs->L[2] != 0.0 || obs->P[2] != 0.0) {
379 return 2;
380 }
381 return 0;
382 }
383
384 if (NFREQ == 1 || optf == POSL1) {
385 return 0;
386 }
387 if (optf == POSL1L2L5) {
388 return 1 + 2;
389 }
390 if (optf == POSL1L5) {
391 return 2;
392 }
393 if (optf == POSL1L5_L2 && obs->L[2] != 0.0 && obs->P[2] != 0.0) {
394 return 2;
395 }
396 if (optf == POSL1L2) {
397 if (obs->L[1] != 0.0 || obs->P[1] != 0.0) {
398 return 1;
399 }
400 return 0;
401 }
402 /* default: L1+L2 */
403 return 1;
404}
405
406/*============================================================================
407 * clas_osr_compensatedisp -- Compensate Time Variation of Signal Bias + Iono
408 *===========================================================================*/
409
410/**
411 * @brief Compensate time variation of signal bias and ionosphere delay.
412 *
413 * Ported from upstream claslib compensatedisp(). All static variables
414 * replaced by clas_osr_ctx_t members (comp_*).
415 *
416 * @param[in] corr Merged correction snapshot
417 * @param[in] index Grid point indices
418 * @param[in] obs Observation for this satellite
419 * @param[in] sat Satellite number
420 * @param[in] iono Ionosphere delay (m)
421 * @param[in] pb Phase biases per frequency
422 * @param[out] compL Compensation output per frequency
423 * @param[in,out] pbreset Phase bias reset flags
424 * @param[in] opt Processing options
425 * @param[in] ssat Satellite status
426 * @param[in] ch SSR channel (0 in MRTKLIB)
427 * @param[in,out] osr_ctx OSR context for persistent state
428 * @param[in] nav Navigation data (for wavelengths)
429 */
430void clas_osr_compensatedisp(clas_corr_t* corr, const int* index, const obsd_t* obs, int sat, double iono,
431 const double* pb, double* compL, int* pbreset, const prcopt_t* opt, ssat_t ssat, int ch,
432 clas_osr_ctx_t* osr_ctx, const nav_t* nav) {
433 gtime_t time = obs->time;
434 int i, k, qi, qj, isat = sat - 1, oft = isat * NFREQ, oft_b;
435 double lam[NFREQ + NEXOBS];
436 double disp0, dispm, dt, dgf, fi;
437 int nf = opt->nf, flag, fqi, fqj;
438
439 /* compute wavelengths */
440 sat_wavelengths(sat, obs, nav, lam);
441
442 /* non-VRS path: offset is isat*NFREQ */
443 oft_b = isat * NFREQ;
444
445 /* long interval: check if STEC data has a newer time than our stored t0 */
446 for (k = flag = 0; k < corr->stec[index[0]].n; k++) {
447 if (sat == corr->stec[index[0]].data[k].sat) {
448 flag = 1;
449 break;
450 }
451 }
452
453 if (flag == 1 && timediff(corr->stec[index[0]].data[k].time, osr_ctx->comp_t0[ch][isat]) > 0.0) {
454 if (opt->posopt[5] == 1) {
455 osr_ctx->comp_tm[ch][isat] = osr_ctx->comp_t0[ch][isat];
456 osr_ctx->comp_t0[ch][isat] = corr->stec[index[0]].data[k].time;
457 dt = timediff(osr_ctx->comp_t0[ch][isat], osr_ctx->comp_tm[ch][isat]);
458 if (dt <= 0.0) {
459 return;
460 }
461
462 for (i = 0; i < nf; i++) {
463 if (osr_ctx->comp_b0[ch][oft_b + i] == 0.0 || osr_ctx->comp_iono0[ch][isat] == 0.0) {
464 osr_ctx->comp_b0[ch][oft_b + i] = pb[i];
465 osr_ctx->comp_iono0[ch][isat] = iono;
466 return;
467 }
468 }
469
470 /* signal bias */
471 for (i = 0; i < nf; i++) {
472 osr_ctx->comp_bm[ch][oft_b + i] = osr_ctx->comp_b0[ch][oft_b + i];
473 osr_ctx->comp_b0[ch][oft_b + i] = pb[i];
474 }
475 /* ionosphere */
476 osr_ctx->comp_ionom[ch][isat] = osr_ctx->comp_iono0[ch][isat];
477 osr_ctx->comp_iono0[ch][isat] = iono;
478
479 for (i = 1; i < nf; i++) {
480 qi = 0;
481 qj = i;
482 /* map signal mode to frequency band index */
483 fqi = obsfreqs[corr->smode[isat][qi]] - 1;
484 fqj = obsfreqs[corr->smode[isat][qj]] - 1;
485 if (fqi < 0 || fqj < 0) {
486 continue;
487 }
488 fi = (lam[fqi] > 0.0 && lam[fqj] > 0.0) ? lam[fqj] / lam[fqi] : 0.0;
489
490 if (pb[qi] == CLAS_CSSRINVALID || pb[qj] == CLAS_CSSRINVALID || iono == 0.0) {
491 continue;
492 }
493 dispm = -FREQ2 / FREQ1 * (1.0 - fi * fi) * osr_ctx->comp_ionom[ch][isat] +
494 osr_ctx->comp_bm[ch][oft_b + qi] - osr_ctx->comp_bm[ch][oft_b + qj];
495 disp0 = -FREQ2 / FREQ1 * (1.0 - fi * fi) * osr_ctx->comp_iono0[ch][isat] +
496 osr_ctx->comp_b0[ch][oft_b + qi] - osr_ctx->comp_b0[ch][oft_b + qj];
497 osr_ctx->comp_coef[ch][isat + (i - 1) * MAXSAT] = (disp0 - dispm) / dt;
498 }
499 } else {
500 for (i = 0; i < nf; i++) {
501 osr_ctx->comp_b0[ch][oft_b + i] = obs->L[i] * lam[i];
502 osr_ctx->comp_slip[ch][oft + i] = 0;
503 }
504 osr_ctx->comp_tm[ch][isat] = osr_ctx->comp_t0[ch][isat];
505 osr_ctx->comp_t0[ch][isat] = corr->stec[index[0]].data[k].time;
506 }
507 }
508
509 dt = timediff(time, osr_ctx->comp_t0[ch][isat]);
510
511 if (opt->posopt[5] == 1) {
512 for (i = 1; i < nf; i++) {
513 qi = 0;
514 qj = i;
515 fqi = obsfreqs[corr->smode[isat][qi]] - 1;
516 fqj = obsfreqs[corr->smode[isat][qj]] - 1;
517 if (fqi < 0 || fqj < 0) {
518 continue;
519 }
520 fi = (lam[fqi] > 0.0 && lam[fqj] > 0.0) ? lam[fqj] / lam[fqi] : 0.0;
521
522 if (fabs(osr_ctx->comp_coef[ch][isat + (i - 1) * MAXSAT] / (FREQ2 / FREQ1 * (1.0 - fi * fi))) > 0.008) {
523 continue;
524 }
525 if (pbreset[qi] || pbreset[qj]) {
526 osr_ctx->comp_coef[ch][isat] = 0.0;
527 return;
528 }
529 compL[qi] = (compL[qi] == 0.0)
530 ? (1.0 / (1.0 - SQR(fi)) * osr_ctx->comp_coef[ch][isat + (i - 1) * MAXSAT] * dt)
531 : compL[qi];
532 compL[qj] = SQR(fi) / (1.0 - SQR(fi)) * osr_ctx->comp_coef[ch][isat + (i - 1) * MAXSAT] * dt;
533 }
534 } else {
535 for (i = 0; i < nf; i++) {
536 if (ssat.slip[i] > 0) {
537 osr_ctx->comp_slip[ch][oft + i] = 1;
538 }
539 }
540 for (i = 1; i < nf; i++) {
541 qi = 0;
542 qj = i;
543 fi = (lam[qi] > 0.0 && lam[qj] > 0.0) ? lam[qj] / lam[qi] : 0.0;
544 if (osr_ctx->comp_slip[ch][oft + qi] || osr_ctx->comp_slip[ch][oft + qj] || obs->L[qi] * lam[qi] == 0.0 ||
545 obs->L[qj] * lam[qj] == 0.0 || pbreset[qi] || pbreset[qj]) {
546 continue;
547 }
548 dgf = obs->L[qi] * lam[qi] - obs->L[qj] * lam[qj] -
549 (osr_ctx->comp_b0[ch][oft_b + qi] - osr_ctx->comp_b0[ch][oft_b + qj]);
550 compL[qi] = (compL[qi] == 0.0) ? (1.0 / (1.0 - SQR(fi)) * dgf) : compL[qi];
551 compL[qj] = SQR(fi) / (1.0 - SQR(fi)) * dgf;
552 }
553 }
554}
555
556/*============================================================================
557 * clas_osr_corrmeas -- Ionosphere/Bias/Antenna Corrected Measurements
558 *===========================================================================*/
559
560/**
561 * @brief Assemble per-satellite ionosphere/bias/antenna corrections.
562 *
563 * Ported from upstream claslib corrmeas(). Decodes phase and code biases
564 * from SSR corrections, applies STEC grid interpolation for ionosphere,
565 * performs consistency checks, and computes receiver antenna correction.
566 *
567 * @param[in] obs Observation for this satellite
568 * @param[in,out] nav Navigation data
569 * @param[in] pos Receiver position {lat,lon,hgt} (rad,m)
570 * @param[in] azel Azimuth/elevation for this satellite
571 * @param[in] opt Processing options
572 * @param[in] grid Grid interpolation state
573 * @param[in] corr Merged correction snapshot
574 * @param[in] ssat Satellite status
575 * @param[out] brk Slip break flag
576 * @param[out] osr OSR output for this satellite
577 * @param[in,out] pbreset Phase bias reset flags
578 * @param[in] ch SSR channel (0 in MRTKLIB)
579 * @param[in,out] osr_ctx OSR context
580 * @return 1=ok, 0=error (stale bias, missing iono)
581 */
582int clas_osr_corrmeas(const obsd_t* obs, nav_t* nav, const double* pos, const double* azel, const prcopt_t* opt,
583 clas_grid_t* grid, clas_corr_t* corr, ssat_t ssat, int* brk, clas_osrd_t* osr, int* pbreset,
584 int ch, clas_osr_ctx_t* osr_ctx) {
585 double lam[NFREQ + NEXOBS];
586 double vari, dant[NFREQPCV] = {0}, compL[NFREQ + NEXOBS] = {0};
587 double stec = 0.0, rate, t5, t6;
588 double pbias[NFREQ + NEXOBS] = {0}, cbias[NFREQ + NEXOBS] = {0};
589 int i, j, sat, smode, nsig, nf = opt->nf;
590 int flag;
591 double dt;
592
593 trace(NULL, 3, "clas_osr_corrmeas: time=%s, sat=%2d\n", time_str(obs->time, 0), obs->sat);
594
595 sat = obs->sat;
596 sat_wavelengths(sat, obs, nav, lam);
597
598 for (i = 0; i < nf; i++) {
599 pbias[i] = cbias[i] = CLAS_CSSRINVALID;
600 }
601
602 /* decode phase and code bias -- check ages */
603 t5 = timediff(obs->time, nav->ssr_ch[ch][sat - 1].t0[4]); /* cbias */
604 t6 = timediff(obs->time, nav->ssr_ch[ch][sat - 1].t0[5]); /* pbias */
605
606 if (fabs(t5) > CLAS_MAXAGESSR_BIAS || fabs(t6) > CLAS_MAXAGESSR_BIAS) {
607 trace(NULL, 2, "age of ssr bias error: time=%s sat=%2d tc,tp=%.0f,%.0f\n", time_str(obs->time, 0), sat, t5, t6);
608 memset(&nav->ssr_ch[ch][sat - 1].t0[4], 0x00, sizeof(nav->ssr_ch[ch][sat - 1].t0[4]));
609 memset(&nav->ssr_ch[ch][sat - 1].t0[5], 0x00, sizeof(nav->ssr_ch[ch][sat - 1].t0[5]));
610 return 0;
611 }
612
613 /* decode phase and code bias (non-VRS / CSSR path) */
614 nsig = count_nsig(corr->smode[sat - 1]);
615 {
616 int sys = satsys(sat, NULL);
617 for (i = 0; i < nf; i++) {
618 /* pass 1: exact code match (preferred) */
619 for (j = 0; j < nsig && j < MAXCODE; j++) {
620 smode = corr->smode[sat - 1][j];
621 if (smode == 0) continue;
622 if (obs->code[i] == smode) {
623 pbias[i] = nav->ssr_ch[ch][sat - 1].pbias[smode - 1];
624 cbias[i] = nav->ssr_ch[ch][sat - 1].cbias[smode - 1];
625 break;
626 }
627 }
628 if (pbias[i] != CLAS_CSSRINVALID) continue;
629 /* pass 2: same-frequency fallback (e.g. L2X↔L2W, L1X↔L1C) */
630 for (j = 0; j < nsig && j < MAXCODE; j++) {
631 smode = corr->smode[sat - 1][j];
632 if (smode == 0) continue;
633 if (code_on_same_freq(sys, obs->code[i], smode)) {
634 pbias[i] = nav->ssr_ch[ch][sat - 1].pbias[smode - 1];
635 cbias[i] = nav->ssr_ch[ch][sat - 1].cbias[smode - 1];
636 break;
637 }
638 }
639 }
640 }
641
642 /* ionosphere correction via STEC grid interpolation */
643 if (!clas_stec_grid_data(corr, grid->index, obs->time, sat, grid->num, grid->weight, grid->Gmat, grid->Emat, &stec,
644 &rate, &vari, brk)) {
645 trace(NULL, 2, "clas_osr_corrmeas: iono correction error: time=%s sat=%2d\n", time_str(obs->time, 2), obs->sat);
646 return 0;
647 }
648
649 /* inconsistency check: STEC time vs pbias time */
650 for (i = flag = 0; i < corr->stec[grid->index[0]].n; i++) {
651 if (sat == corr->stec[grid->index[0]].data[i].sat) {
652 flag = 1;
653 break;
654 }
655 }
656 if (flag == 1) {
657 dt = timediff(corr->stec[grid->index[0]].data[i].time, nav->ssr_ch[ch][sat - 1].t0[5]);
658 /* store STEC time in t0[8] -- but ssr_t only has t0[6], skip */
659 if (dt < 0.0 || dt >= 30.0) {
660 trace(NULL, 2,
661 "inconsist ssr correction (iono-pbias): time=%s sat=%2d "
662 "dt=%.2f\n",
663 time_str(obs->time, 0), sat, dt);
664 return 0;
665 }
666 dt = timediff(corr->stec[grid->index[0]].data[i].time, nav->ssr_ch[ch][sat - 1].t0[0]);
667 if (dt < -30.0 || dt > 30.0) {
668 trace(NULL, 2,
669 "inconsist ssr correction (iono-orbit): time=%s sat=%2d "
670 "dt=%.2f\n",
671 time_str(obs->time, 0), sat, dt);
672 return 0;
673 }
674 }
675
676 /* pbias-cbias consistency */
677 dt = timediff(nav->ssr_ch[ch][sat - 1].t0[5], nav->ssr_ch[ch][sat - 1].t0[4]);
678 trace(NULL, 5,
679 "clas_osr_corrmeas: pbias_tow=%.1f, cbias_tow=%.1f, "
680 "dt=%.1f\n",
681 time2gpst(nav->ssr_ch[ch][sat - 1].t0[5], NULL), time2gpst(nav->ssr_ch[ch][sat - 1].t0[4], NULL), dt);
682 /* cbias may be newer than pbias by up to 30 s: the bank substitutes the
683 * next cbias when the one matching pbias was lost (#406) */
684 if (dt > 0.0 || dt < -30.0) {
685 trace(NULL, 2, "inconsist correction (pbias-cbias): time=%s sat=%2d dt=%.2f\n", time_str(obs->time, 0), sat,
686 dt);
687 return 0;
688 }
689
690 /* compensate time variation of signal bias and ionosphere delay */
691 if (opt->posopt[5] > 0) {
692 clas_osr_compensatedisp(corr, grid->index, obs, sat, stec, pbias, compL, pbreset, opt, ssat, ch, osr_ctx, nav);
693 }
694
695 /* receiver antenna phase center correction */
696 antmodel(&opt->pcvr[0], opt->antdel[0], azel, opt->posopt[1], dant);
697
698 /* ionosphere and windup corrected phase and code */
699 for (i = 0; i < nf; i++) {
700 osr->cbias[i] = cbias[i];
701 osr->pbias[i] = pbias[i];
702 osr->compL[i] = compL[i];
703 }
704
705 for (i = 0; i < nf; i++) {
706 int ant_idx = claslib_pcv_slot(&opt->pcvr[0], i);
707 osr->wupL[i] = lam[i] * ssat.phw;
708 osr->antr[i] = (0 <= ant_idx && ant_idx < NFREQPCV) ? dant[ant_idx] : 0.0;
709 }
710 osr->iono = stec;
711
712 return 1;
713}
714
715/*============================================================================
716 * ocean_tide_clasgrid -- Grid-interpolated Ocean Tide Loading
717 *===========================================================================*/
718
719/**
720 * @brief Compute ocean tide loading displacement using CLAS grid interpolation.
721 *
722 * Evaluates tide_oload() at each surrounding grid point, then interpolates
723 * using bilinear (Gmat/Emat) or weighted average.
724 * Ported from upstream claslib ppp.c:ocean_tide_clasgrid().
725 *
726 * @param[in] tut Time in UT
727 * @param[in] oload Ocean loading data for the selected network
728 * @param[in] index Grid point indices (from clas_grid_t)
729 * @param[in] nn Number of grid points (1-4)
730 * @param[in] E ENU rotation matrix (3x3)
731 * @param[in] Emat Interpolation basis vector (4x1, can be NULL)
732 * @param[in] Gmat Interpolation matrix (4x4, can be NULL)
733 * @param[in] weight Interpolation weights (4x1)
734 * @param[out] dr Displacement (ECEF, m) — accumulated
735 */
736static void ocean_tide_clasgrid(gtime_t tut, const clas_oload_t* oload, const int* index, int nn, const double* E,
737 const double* Emat, const double* Gmat, const double* weight, double* dr) {
738 int i, j;
739 double drt[3], val[4], val_[4], denu_grid[4][3], denu[3] = {0};
740
741 /* compute OTL displacement at each surrounding grid point */
742 for (i = 0; i < nn && i < 4; i++) {
743 tide_oload(tut, oload->odisp[index[i]], denu_grid[i]);
744 }
745
746 if (nn == 4 && Gmat && Emat) {
747 /* bilinear interpolation using Gmat/Emat matrices */
748 for (i = 0; i < 3; i++) {
749 for (j = 0; j < nn; j++) {
750 val[j] = denu_grid[j][i];
751 }
752 matmul("NN", nn, 1, nn, 1.0, Gmat, val, 0.0, val_);
753 denu[i] = dot(Emat, val_, 4);
754 }
755 } else {
756 /* weighted average fallback */
757 for (i = 0; i < nn; i++) {
758 for (j = 0; j < 3; j++) {
759 denu[j] += denu_grid[i][j] * weight[i];
760 }
761 }
762 }
763
764 matmul("TN", 3, 1, 3, 1.0, E, denu, 0.0, drt);
765 for (i = 0; i < 3; i++) {
766 dr[i] += drt[i];
767 }
768
769 trace(NULL, 5, "ocean_tide_clasgrid: denu=%.4f %.4f %.4f\n", denu[0], denu[1], denu[2]);
770}
771
772/*============================================================================
773 * SIS/IODE Adjustment Functions
774 *
775 * Ported from upstream claslib ephemeris.c: satpos_ssr_sis(), adjust_cpc(),
776 * adjust_prc(), adjust_r_dts(). Removes SIS discontinuity at SSR correction
777 * boundaries to prevent phase/code jumps that cause FLOAT epochs.
778 *===========================================================================*/
779
780/* SSR correction age/magnitude limits (matching mrtk_eph.c) */
781#define MAXAGESSR_SIS 60.0 /* max age of ssr orbit/clock (s) */
782#define MAXECORSSR_SIS 10.0 /* max orbit correction (m) */
783#define MAXCCORSSR_SIS (1E-6 * CLIGHT) /* max clock correction (m) */
784
785/**
786 * @brief Update per-satellite SIS correction state for IODE adjustment.
787 *
788 * Tracks orbit/clock corrections and detects 5-second/25-second SSR boundary
789 * transitions. Computes currsis = SIS discontinuity for adjust_cpc/adjust_prc.
790 *
791 * Ported from upstream satpos_ssr_sis() in ephemeris.c.
792 */
793int clas_osr_satcorr_update(gtime_t time, gtime_t teph, int sat, const prcopt_t* opt, const nav_t* nav, int ch,
794 clas_osr_ctx_t* ctx) {
795 const ssr_t* ssr;
796 double t1, t2, t3, deph[3], dclk, dorb[3], orbit;
797 double rs[6], dts[2], var, sig[3], nsig[3];
798 double er[3], ea[3], ec[3], rc[3];
799 double tow = time2gpst(teph, NULL);
800 int i, svh;
801 clas_satcorr_t* sc;
802
803 double orb_tow;
804 ssr = nav->ssr_ch[ch] + sat - 1;
805 sc = &ctx->satcorr[ch][sat - 1];
806
807 if (!ssr->t0[0].time) {
808 return 0; /* no orbit data -- silent */
809 }
810 if (!ssr->t0[1].time) {
811 return 0; /* no clock data -- silent */
812 }
813
814 t1 = timediff(time, ssr->t0[0]);
815 t2 = timediff(time, ssr->t0[1]);
816 t3 = timediff(time, ssr->t0[2]);
817
818 if (fabs(t1) > MAXAGESSR_SIS || fabs(t2) > MAXAGESSR_SIS) {
819 return 0;
820 }
821
822 if (ssr->udi[0] >= 1.0) {
823 t1 -= ssr->udi[0] / 2.0;
824 }
825 if (ssr->udi[1] >= 1.0) {
826 t2 -= ssr->udi[1] / 2.0;
827 }
828
829 for (i = 0; i < 3; i++) {
830 deph[i] = ssr->deph[i] + ssr->ddeph[i] * t1;
831 }
832 dclk = ssr->dclk[0] + ssr->dclk[1] * t2 + ssr->dclk[2] * t2 * t2;
833
834 if (ssr->iod[0] == ssr->iod[2] && ssr->t0[2].time && fabs(t3) < 10.0) {
835 dclk += ssr->hrclk;
836 }
837 if (norm(deph, 3) > MAXECORSSR_SIS || fabs(dclk) > MAXCCORSSR_SIS) {
838 return 0;
839 }
840
841 /* broadcast ephemeris position */
842 if (!ephpos(time, teph, sat, nav, ssr->iode, rs, dts, &var, &svh)) {
843 return 0;
844 }
845
846 /* radial-along-cross directions in ECEF */
847 if (!normv3(rs + 3, ea)) {
848 return 0;
849 }
850 cross3(rs, rs + 3, rc);
851 if (!normv3(rc, ec)) {
852 return 0;
853 }
854 cross3(ea, ec, er);
855
856 /* orbit correction in radial-along-cross frame */
857 for (i = 0; i < 3; i++) {
858 dorb[i] = -(er[i] * deph[0] + ea[i] * deph[1] + ec[i] * deph[2]);
859 }
860
861 /* project orbit correction along line-of-sight */
862 if (norm(ctx->saved_rr, 3) <= 0.0) {
863 return 0;
864 }
865 for (i = 0; i < 3; i++) {
866 sig[i] = rs[i] - ctx->saved_rr[i];
867 }
868 if (!normv3(sig, nsig)) {
869 return 0;
870 }
871 orbit = dot(nsig, dorb, 3);
872
873 sc->orb = orbit;
874 sc->clk = dclk;
875 sc->tow = tow;
876
877 trace(NULL, 4,
878 "satcorr_update: tow=%.1f sat=%2d clk_tow=%.1f clk=%.6f "
879 "orb_tow=%.1f orb=%.6f prev_tow=%.1f\n",
880 tow, sat, time2gpst(ssr->t0[1], NULL), dclk, time2gpst(ssr->t0[0], NULL), orbit, sc->prevtow);
881
882 /* skip SIS tracking if posopt[9] (don't adjust phase bias) is set */
883 if (opt->posopt[9]) {
884 return 1;
885 }
886
887 orb_tow = time2gpst(ssr->t0[0], NULL);
888
889 /* detect 25s boundary: clk_tow - orb_tow == 25 (orbit is 25s older than clock) */
890 if (sc->prevtow != time2gpst(ssr->t0[1], NULL) && timediff(ssr->t0[0], ssr->t0[1]) < 0.0 &&
891 timediff(ssr->t0[1], ssr->t0[0]) == 25.0) {
892 sc->prevtow = time2gpst(ssr->t0[1], NULL);
893 sc->prevsis = -dclk + orbit;
894 sc->previode = ssr->iode;
895 trace(NULL, 4,
896 "prevsis: tow=%.1f clk_tow=%.1f orb_tow=%.1f sat=%2d "
897 "iode=%3d prevsis=%f clk=%f orb=%f\n",
898 tow, sc->prevtow, orb_tow, sat, sc->previode, sc->prevsis, -dclk, orbit);
899 }
900
901 if (sc->prevtow != -1 && timediff(ssr->t0[0], ssr->t0[1]) == 0.0 && sc->currtow != time2gpst(ssr->t0[1], NULL)) {
902 if (time2gpst(ssr->t0[1], NULL) < sc->prevtow &&
903 (time2gpst(ssr->t0[1], NULL) + 3600 * 24 * 7) - sc->prevtow != 5.0) {
904 sc->prev_orb_tow = orb_tow;
905 return 1;
906 }
907 if (time2gpst(ssr->t0[1], NULL) - sc->prevtow != 5.0) {
908 sc->prev_orb_tow = orb_tow;
909 return 1;
910 }
911 sc->currsis = (-dclk + orbit) - sc->prevsis;
912 sc->currtow = time2gpst(ssr->t0[1], NULL);
913 trace(NULL, 4,
914 "currsis: tow=%.1f clk_tow=%.1f orb_tow=%.1f sat=%2d "
915 "currsis=%f clk=%f orb=%f\n",
916 tow, sc->currtow, orb_tow, sat, sc->currsis, -dclk, orbit);
917 }
918 sc->prev_orb_tow = orb_tow;
919 return 1;
920}
921
922/**
923 * @brief Adjust carrier-phase correction for SIS discontinuity.
924 *
925 * When phase bias TOW differs from current SIS TOW by exactly 30s,
926 * subtracts the SIS discontinuity from CPC to maintain phase continuity.
927 */
928static double clas_osr_adjust_cpc(gtime_t teph, int sat, const ssr_t* ssr, int sig, double cpc, double* sis, int* flag,
929 int ch, clas_osr_ctx_t* ctx) {
930 double before = cpc;
931 clas_satcorr_t* sc = &ctx->satcorr[ch][sat - 1];
932 *flag = 0;
933
934 if (time2gpst(ssr->t0[5], NULL) != sc->currtow) {
935 /* handle week rollover for 30s check */
936 if (sc->currtow < time2gpst(ssr->t0[5], NULL) &&
937 (sc->currtow + 3600 * 24 * 7) - time2gpst(ssr->t0[5], NULL) != 30.0) {
938 return cpc;
939 }
940 if (sc->currtow - time2gpst(ssr->t0[5], NULL) != 30.0) {
941 return cpc;
942 }
943 if (ssr->iode != sc->previode) {
944 trace(NULL, 4,
945 "adjust_cpc: IODE diff tow=%.1f sat=%2d orb_tow=%.1f "
946 "%3d sis_tow=%.1f %3d\n",
947 time2gpst(teph, NULL), sat, time2gpst(ssr->t0[0], NULL), ssr->iode, sc->prevtow, sc->previode);
948 *flag = 1;
949 }
950 *sis = sc->currsis;
951 cpc -= *sis;
952 trace(NULL, 4,
953 "adjust_cpc: tow=%.1f sat=%2d pb_tow=%.1f sis_tow=%.1f "
954 "sig=%d sis=%f cpc=%.6f-->%.6f\n",
955 time2gpst(teph, NULL), sat, time2gpst(ssr->t0[5], NULL), sc->currtow, sig, *sis, before, cpc);
956 }
957 return cpc;
958}
959
960/**
961 * @brief Adjust pseudorange correction for SIS discontinuity.
962 *
963 * Same logic as adjust_cpc but applied to PRC.
964 */
965static double clas_osr_adjust_prc(gtime_t teph, int sat, const ssr_t* ssr, int sig, double prc, double* sis, int* flag,
966 int ch, clas_osr_ctx_t* ctx) {
967 double before = prc;
968 clas_satcorr_t* sc = &ctx->satcorr[ch][sat - 1];
969 *flag = 0;
970
971 if (time2gpst(ssr->t0[5], NULL) != sc->currtow) {
972 if (sc->currtow < time2gpst(ssr->t0[5], NULL) &&
973 (sc->currtow + 3600 * 24 * 7) - time2gpst(ssr->t0[5], NULL) != 30.0) {
974 return prc;
975 }
976 if (sc->currtow - time2gpst(ssr->t0[5], NULL) != 30.0) {
977 return prc;
978 }
979 if (ssr->iode != sc->previode) {
980 *flag = 1;
981 }
982 *sis = sc->currsis;
983 prc -= *sis;
984 trace(NULL, 4,
985 "adjust_prc: tow=%.1f sat=%2d pb_tow=%.1f sis_tow=%.1f "
986 "sig=%d sis=%f prc=%.6f-->%.6f\n",
987 time2gpst(teph, NULL), sat, time2gpst(ssr->t0[5], NULL), sc->currtow, sig, *sis, before, prc);
988 }
989 return prc;
990}
991
992/**
993 * @brief Recompute geometric range and clock on IODE change.
994 *
995 * When IODE changes at an SSR boundary, the broadcast ephemeris has changed.
996 * This recomputes the geometric range using the previous IODE's ephemeris
997 * to maintain consistency.
998 */
999static void clas_osr_adjust_r_dts(double* retr, double* retdts, gtime_t teph, int sat, nav_t* nav, const double* rr,
1000 gtime_t dts_time, int ch, clas_osr_ctx_t* ctx) {
1001 double rs[6] = {0}, dts[2] = {0}, e[3], var;
1002 double er[3], ea[3], ec[3], rc[3];
1003 int sys = satsys(sat, NULL), svh0, i;
1004 const ssr_t* ssr = nav->ssr_ch[ch] + sat - 1;
1005 clas_satcorr_t* sc = &ctx->satcorr[ch][sat - 1];
1006 eph_t* eph;
1007
1008 if (sys == SYS_GPS || sys == SYS_GAL || sys == SYS_QZS || sys == SYS_CMP) {
1009 eph = seleph(dts_time, sat, sc->previode, nav);
1010 if (eph) {
1011 double dt = eph2clk(dts_time, eph);
1012 dts_time = timeadd(dts_time, -dt);
1013 }
1014 }
1015
1016 if (!ephpos(dts_time, teph, sat, nav, sc->previode, rs, dts, &var, &svh0)) {
1017 trace(NULL, 2, "adjust_r_dts: old ephemeris unavailable tow=%.1f sat=%d iode=%d\n", time2gpst(teph, NULL), sat,
1018 sc->previode);
1019 return;
1020 }
1021 if (!normv3(rs + 3, ea)) {
1022 return;
1023 }
1024 cross3(rs, rs + 3, rc);
1025 if (!normv3(rc, ec)) {
1026 return;
1027 }
1028 cross3(ea, ec, er);
1029
1030 /* satellite antenna offset correction */
1031 for (i = 0; i < 3; i++) {
1032 rs[i] += -(er[i] * ssr->deph[0] + ea[i] * ssr->deph[1] + ec[i] * ssr->deph[2]);
1033 }
1034 *retr = geodist(rs, rr, e);
1035 *retdts = dts[0] + ssr->dclk[0] / CLIGHT;
1036 trace(NULL, 2, "adjust_r_dts: tow=%.1f sat=%d r=%.8f dts=%.8f\n", time2gpst(teph, NULL), sat, *retr, *retdts);
1037}
1038
1039/*============================================================================
1040 * clas_osr_zdres -- Zero-Differenced Phase/Code Residuals
1041 *===========================================================================*/
1042
1043/**
1044 * @brief Compute zero-differenced phase/code residuals with CLAS corrections.
1045 *
1046 * Ported from upstream claslib zdres(). This is the ENA_PPP_RTK path
1047 * (always enabled). CSSR2OSR_VRS path is removed.
1048 *
1049 * Key differences from upstream:
1050 * - obs is const; a local copy is allocated.
1051 * - cpc/pt0 come from osr_ctx->cpctmp/pt0tmp.
1052 * - pbias_ofst comes from osr_ctx->pbias_ofst.
1053 * - Uses nav->ssr_ch[0][sat-1] (channel 0 by default).
1054 * - nav->lam[sat-1] -> local lam[] via sat_wavelengths().
1055 * - Stubbed functions: ISB, L2C.
1056 *
1057 * @param[in] obs Observation data for epoch
1058 * @param[in] n Number of observations
1059 * @param[in] rs Satellite positions+velocities (6*n)
1060 * @param[in] dts Satellite clock corrections (2*n)
1061 * @param[in] vare Satellite position variances (n)
1062 * @param[in] svh Satellite health flags (n)
1063 * @param[in,out] nav Navigation data
1064 * @param[in,out] x State vector (position extracted from [0:2])
1065 * @param[out] y Residuals (n*nf*2), can be NULL
1066 * @param[out] e Line-of-sight vectors (3*n)
1067 * @param[out] azel Azimuth/elevation (2*n)
1068 * @param[in] rtk RTK control struct
1069 * @param[in] osrlog Enable OSR logging
1070 * @param[in,out] osr_ctx OSR context for persistent state
1071 * @param[in] grid Grid interpolation state
1072 * @param[in] corr Merged correction snapshot
1073 * @param[in,out] ssat Satellite status array
1074 * @param[in] opt Processing options
1075 * @param[in,out] sol Solution struct
1076 * @param[out] osr OSR output per satellite
1077 * @param[in] ch SSR channel (0 in MRTKLIB)
1078 * @return Number of valid residual measurements
1079 */
1080int clas_osr_zdres(const obsd_t* obs, int n, const double* rs, const double* dts, const double* vare, const int* svh,
1081 nav_t* nav, double* x, double* y, double* e, double* azel, rtk_t* rtk, int osrlog,
1082 clas_osr_ctx_t* osr_ctx, clas_grid_t* grid, clas_corr_t* corr, ssat_t* ssat, prcopt_t* opt,
1083 sol_t* sol, clas_osrd_t* osr, int ch) {
1084 double r, rr[3], disp[3], pos[3], meas[NFREQ * 2], zwd, ztd;
1085 double lam[NFREQ + NEXOBS], fi;
1086 int i, j, f, qj, sat, sys, brk, nf = opt->nf, nftmp;
1087 int tbrk = 0;
1088 double modl[NFREQ * 2];
1089 obsd_t* obs_copy = NULL; /* local mutable copy of obs */
1090 int nv = 0;
1091 clas_osrd_t osrtmp[MAXOBS];
1092 double dcpc;
1093 double tow;
1094 double tmp_r, tmp_dts;
1095 int iodeflag;
1096 int prn;
1097
1098 /* use local osr array only when y is provided (PPP-RTK residual mode);
1099 * when y == NULL (VRS/ssr2osr mode), fill caller's osr directly */
1100 if (y) {
1101 memset(osrtmp, 0, sizeof(osrtmp));
1102 osr = osrtmp;
1103 }
1104
1105 if (n <= 0) {
1106 return 0;
1107 }
1108
1109 /* zero residuals if y is provided */
1110 if (y) {
1111 for (i = 0; i < n * nf * 2; i++) {
1112 y[i] = 0.0;
1113 }
1114 }
1115
1116 trace(NULL, 3, "clas_osr_zdres: n=%d\n", n);
1117
1118 for (i = 0; i < 3; i++) {
1119 rr[i] = x[i];
1120 }
1121
1122 if (norm(rr, 3) <= 0.0) {
1123 return 0;
1124 }
1125
1126 /* allocate mutable copy of observations */
1127 if (!(obs_copy = (obsd_t*)malloc(sizeof(obsd_t) * n))) {
1128 return 0;
1129 }
1130 for (i = 0; i < n && i < MAXOBS; i++) {
1131 obs_copy[i] = obs[i];
1132 }
1133
1134 ecef2pos(rr, pos);
1135
1136 /* troposphere interpolation from CLAS grid */
1137 if (!clas_trop_grid_data(corr, grid->index, obs_copy[0].time, grid->num, grid->weight, grid->Gmat, grid->Emat, &zwd,
1138 &ztd, &tbrk)) {
1139 trace(NULL, 2, "trop correction error: time=%s\n", time_str(obs_copy[0].time, 2));
1140 free(obs_copy);
1141 return 0;
1142 }
1143 /* earth tides correction
1144 * upstream claslib uses switch/case semantics for tidecorr:
1145 * 0: off
1146 * 1: solid only
1147 * 2: solid + station OTL + pole
1148 * 3: solid + grid OTL + pole (CLAS grid interpolation)
1149 * MRTKLIB's tidedisp() uses bitmask: 1=solid, 2=OTL, 4=pole
1150 * We translate here to match upstream behavior. */
1151 if (opt->tidecorr) {
1152 if (opt->tidecorr == 3) {
1153 /* solid + grid OTL + pole */
1154 tidedisp(gpst2utc(obs_copy[0].time), rr, 5, &nav->erp, NULL, disp); /* bitmask 5 = solid(1) + pole(4) */
1155
1156 /* grid-interpolated ocean tide loading */
1157 clas_ctx_t* ctx = (clas_ctx_t*)nav->clas_ctx;
1158 if (ctx && grid->network > 0 && ctx->oload[grid->network - 1].gridnum > 0) {
1159 gtime_t tut;
1160 double erpv[5] = {0}, pos_[2], E_[9];
1161 if (nav->erp.n > 0) {
1162 geterp(&nav->erp, utc2gpst(gpst2utc(obs_copy[0].time)), erpv);
1163 }
1164 tut = timeadd(gpst2utc(obs_copy[0].time), erpv[2]);
1165 pos_[0] = asin(rr[2] / norm(rr, 3));
1166 pos_[1] = atan2(rr[1], rr[0]);
1167 xyz2enu(pos_, E_);
1168 ocean_tide_clasgrid(tut, &ctx->oload[grid->network - 1], grid->index, grid->num, E_, grid->Emat,
1169 grid->Gmat, grid->weight, disp);
1170 }
1171 } else if (opt->tidecorr == 2) {
1172 /* solid + station OTL + pole */
1173 tidedisp(gpst2utc(obs_copy[0].time), rr, 7, &nav->erp, opt->odisp[0],
1174 disp); /* bitmask 7 = solid+OTL+pole */
1175 } else {
1176 /* tidecorr == 1: solid only */
1177 tidedisp(gpst2utc(obs_copy[0].time), rr, 1, &nav->erp, NULL, disp);
1178 }
1179 for (i = 0; i < 3; i++) {
1180 rr[i] += disp[i];
1181 }
1182 ecef2pos(rr, pos);
1183 }
1184
1185 for (i = 0; i < n && i < MAXOBS; i++) {
1186 sat = obs_copy[i].sat;
1187 sys = satsys(sat, &prn);
1188 osr[i].sat = sat;
1189
1190 /* VRS mode (y==NULL): set obs codes from CLAS smode so the per-signal
1191 * slot ordering matches the VRS fill in clas_ssr2osr(), which emits
1192 * obs[].code[j] = smode[j] and reads osr[].c[j] by the same index.
1193 *
1194 * This MUST be unconditional. VRS/OSR callers (e.g. cssr2rtcm3) reuse
1195 * one input obs buffer across epochs and that buffer is copied into
1196 * obs_copy at the top of this function, so obs_copy[].code[j] can carry
1197 * a stale non-zero code from a previous epoch (or a different
1198 * satellite). A `code[j]==0` guard then leaves that stale code in
1199 * place, desyncing the slot order from smode:
1200 * zdres computes osr[].c[] for one signal while the fill reads it as
1201 * another, leaking a stale carrier value (observed as ~1153 m E5a
1202 * glitches on E33 when the receiver-tracked signal set differs from the
1203 * CLAS-corrected set). PPP-RTK mode (y!=NULL) skips this block entirely,
1204 * so real receiver codes are preserved there. */
1205 if (y == NULL && sat > 0 && sat <= MAXSAT) {
1206 for (j = 0; j < nf; j++) {
1207 obs_copy[i].code[j] = corr->smode[sat - 1][j];
1208 }
1209 /* Clear the unused (>= nf) code slots too: the reused input buffer
1210 * can leave stale non-zero codes there, and downstream readers
1211 * (sat_wavelengths over all slots, the GAL L2-slot / L2C checks)
1212 * would otherwise pick them up when nf < NFREQ. */
1213 for (j = nf; j < NFREQ + NEXOBS; j++) {
1214 obs_copy[i].code[j] = 0;
1215 }
1216 }
1217
1218 /* compute wavelengths for this satellite */
1219 sat_wavelengths(sat, obs_copy + i, nav, lam);
1220
1221 for (j = 0; j < nf * 2; j++) {
1222 meas[j] = modl[j] = 0.0;
1223 }
1224
1225 /* geometric distance / azimuth / elevation angle */
1226 if ((r = geodist(rs + i * 6, rr, e + i * 3)) <= 0.0) {
1227 continue;
1228 }
1229 if (satazel(pos, e + i * 3, azel + i * 2) < opt->elmin) {
1230 trace(NULL, 3, "elevation mask rejection: sat=%2d, el=%4.1f\n", sat, azel[i * 2 + 1] * R2D);
1231 continue;
1232 }
1233
1234 /* excluded satellite check (basic health check) */
1235 if (svh[i] == -1) {
1236 continue;
1237 }
1238
1239 /* shapiro time delay correction. Assigned unconditionally: PRC/CPC below read
1240 * .relatv every epoch, but the caller's osr[] is reused across epochs (see the
1241 * partial-clear note in clas_ssr2osr), which would let a stale term survive here. */
1242 osr[i].relatv = opt->posopt[7] ? shapiro(rs + i * 6, rr) : 0.0;
1243
1244 /* tropospheric delay correction */
1245 osr[i].trop = clas_osr_prectrop(obs_copy[i].time, pos, azel + i * 2, zwd, ztd);
1246
1247 /* phase windup correction */
1248 windupcorr(sol->time, rs + i * 6, rr, &ssat[sat - 1].phw);
1249
1250 /* ionosphere, satellite code/phase bias and phase windup corrected */
1251 if (!clas_osr_corrmeas(obs_copy + i, nav, pos, azel + i * 2, opt, grid, corr, ssat[sat - 1], &brk, osr + i,
1252 ssat[sat - 1].pbreset, ch, osr_ctx)) {
1253 continue;
1254 }
1255
1256 /* frequency pair selection */
1257 qj = clas_osr_selfreqpair(sat, opt, obs_copy + i);
1258
1259 /* If selfreqpair returns 0 (L1 only) but CLAS has secondary
1260 * frequency corrections, use them. This handles Galileo E5a
1261 * when obsdef places it at index 1 instead of index 2 (e.g.
1262 * signals=["E1C","E5Q"] without E7Q).
1263 *
1264 * Restricted to Galileo so user-selected single-frequency modes
1265 * (e.g. POSL1) on GPS / QZSS / GLONASS / BDS are preserved. */
1266 if (qj == 0 && satsys(sat, NULL) == SYS_GAL) {
1267 for (j = 1; j < nf; j++) {
1268 if (corr->smode[sat - 1][j] != 0) {
1269 qj = j;
1270 break;
1271 }
1272 }
1273 }
1274 nftmp = nf;
1275 tmp_r = -1.0;
1276 tmp_dts = 0.0;
1277
1278 for (j = 0; j < nf; j++) {
1279 f = j;
1280
1281 fi = (lam[0] > 0.0) ? lam[f] / lam[0] : 0.0;
1282
1283 if (osr[i].pbias[j] == CLAS_CSSRINVALID || osr[i].cbias[j] == CLAS_CSSRINVALID) {
1284 /* ENA_PPP_RTK path: skip invalid biases */
1285 continue;
1286 }
1287
1288 /* pseudorange correction */
1289 osr[i].PRC[j] =
1290 osr[i].trop + osr[i].relatv + osr[i].antr[f] + fi * fi * FREQ2 / FREQ1 * osr[i].iono + osr[i].cbias[j];
1291
1292 /* carrier-phase correction */
1293 osr[i].CPC[j] = osr[i].trop + osr[i].relatv + osr[i].antr[f] - fi * fi * FREQ2 / FREQ1 * osr[i].iono +
1294 osr[i].pbias[j] + osr[i].wupL[f] + osr[i].compL[j];
1295
1296 /* SIS/IODE adjustment */
1297 iodeflag = 0;
1298 if (!opt->posopt[9]) {
1299 osr[i].CPC[j] = clas_osr_adjust_cpc(obs_copy[i].time, sat, &nav->ssr_ch[ch][sat - 1], f, osr[i].CPC[j],
1300 &osr[i].sis, &iodeflag, ch, osr_ctx);
1301 osr[i].PRC[j] = clas_osr_adjust_prc(obs_copy[i].time, sat, &nav->ssr_ch[ch][sat - 1], f, osr[i].PRC[j],
1302 &osr[i].sis, &iodeflag, ch, osr_ctx);
1303 }
1304
1305 /* compute model values, adjusting r/dts on IODE change */
1306 if (iodeflag) {
1307 if (tmp_r < 0.0) {
1308 gtime_t dtsat = timeadd(obs_copy[i].time, -obs_copy[i].P[0] / CLIGHT);
1309 tmp_r = r;
1310 tmp_dts = dts[i * 2];
1311 clas_osr_adjust_r_dts(&tmp_r, &tmp_dts, obs_copy[i].time, sat, nav, rr, dtsat, ch, osr_ctx);
1312 }
1313 modl[j] = tmp_r - CLIGHT * tmp_dts + osr[i].CPC[j];
1314 modl[nftmp + j] = tmp_r - CLIGHT * tmp_dts + osr[i].PRC[j];
1315 osr[i].CPC[j] += tmp_r - CLIGHT * tmp_dts - (r - CLIGHT * dts[i * 2]);
1316 osr[i].PRC[j] += tmp_r - CLIGHT * tmp_dts - (r - CLIGHT * dts[i * 2]);
1317 } else {
1318 modl[j] = r - CLIGHT * dts[i * 2] + osr[i].CPC[j];
1319 modl[nftmp + j] = r - CLIGHT * dts[i * 2] + osr[i].PRC[j];
1320 }
1321
1322 osr[i].p[j] = modl[nftmp + j];
1323 osr[i].c[j] = modl[j];
1324
1325 /* repair cycle slip of pbias */
1326 tow = time2gpst(obs_copy[i].time, NULL);
1327 /* getorbitclock from satcorr state */
1328 if (tow == osr_ctx->satcorr[ch][sat - 1].tow) {
1329 osr[i].orb = osr_ctx->satcorr[ch][sat - 1].orb;
1330 osr[i].clk = osr_ctx->satcorr[ch][sat - 1].clk;
1331 } else {
1332 osr[i].orb = 0.0;
1333 osr[i].clk = 0.0;
1334 }
1335
1336 /* cycle slip detection on phase bias update */
1337 if (!nav->filreset && fabs(timediff(nav->ssr_ch[ch][sat - 1].t0[5], osr_ctx->pt0tmp[sat - 1])) > 0.0 &&
1338 fabs(timediff(nav->ssr_ch[ch][sat - 1].t0[5], osr_ctx->pt0tmp[sat - 1])) < 120.0) {
1339 dcpc = osr[i].orb - osr[i].clk + osr[i].CPC[j] - osr_ctx->cpctmp[j * MAXSAT + sat - 1];
1340 if (dcpc >= 95.0 * lam[f] && dcpc < 105.0 * lam[f]) {
1341 osr_ctx->pbias_ofst[j * MAXSAT + sat - 1] -= 100.0;
1342 /* PPP-RTK (y != NULL): correct the ambiguity filter state.
1343 * VRS/OSR (y == NULL): rtk->x has no ambiguity slots, so
1344 * the offset is instead applied to the emitted carrier
1345 * phase in clas_ssr2osr() — see upstream cssr2osr.c L342. */
1346 if (y) {
1347 x[IB_RTK(sat, f, opt)] -= 100.0;
1348 }
1349 trace(NULL, 2,
1350 "pbias slip detected t=%s sat=%2d f=%1d "
1351 "dcpc[cycle]=%.1f\n",
1352 time_str(obs_copy[i].time, 0), sat, f, lam[f] > 0.0 ? dcpc / lam[f] : 0.0);
1353 } else if (dcpc <= -95.0 * lam[f] && dcpc > -105.0 * lam[f]) {
1354 osr_ctx->pbias_ofst[j * MAXSAT + sat - 1] += 100.0;
1355 if (y) {
1356 x[IB_RTK(sat, f, opt)] += 100.0;
1357 }
1358 trace(NULL, 2,
1359 "pbias slip detected t=%s sat=%2d f=%1d "
1360 "dcpc[cycle]=%.1f\n",
1361 time_str(obs_copy[i].time, 0), sat, f, lam[f] > 0.0 ? dcpc / lam[f] : 0.0);
1362 }
1363 } else {
1364 osr_ctx->pbias_ofst[j * MAXSAT + sat - 1] = 0.0;
1365 }
1366 osr_ctx->cpctmp[j * MAXSAT + sat - 1] = osr[i].orb - osr[i].clk + osr[i].CPC[j];
1367 }
1368
1369 /* ISB correction */
1370 if (opt->isb == ISBOPT_TABLE) {
1371 double isb_val[NFREQ][2] = {{0}};
1372 chk_isb(satsys(sat, NULL), opt, &nav->stas[0], isb_val);
1373 for (f = 0; f < nf; f++) {
1374 meas[f] -= isb_val[f][0]; /* phase */
1375 }
1376 for (f = 0; f < nf; f++) {
1377 meas[f + nf] -= isb_val[f][1]; /* code */
1378 }
1379 }
1380 /* L2C 1/4 cycle phase shift correction */
1381 if (opt->phasshft == ISBOPT_TABLE && isL2C(obs_copy[i].code[1])) {
1382 double ydif = 0.0;
1383 int jj;
1384 for (jj = 0; jj < nav->sfts.n; jj++) {
1385 if (strcmp(nav->sfts.rectyp[jj], opt->rectype[0]) == 0) {
1386 ydif = nav->sfts.bias[jj];
1387 break;
1388 }
1389 }
1390 meas[1] += ydif * lam[1];
1391 }
1392
1393 /* measurement phase and code (ENA_PPP_RTK path) */
1394 for (f = 0; f < nf; f++) {
1395 if (lam[f] == 0.0 || obs_copy[i].L[f] == 0.0 || obs_copy[i].P[f] == 0.0) {
1396 continue;
1397 }
1398 if (obs_copy[i].SNR[f] > 0.0 && testsnr(0, f, azel[i * 2 + 1], obs_copy[i].SNR[f] * 0.25, &opt->snrmask)) {
1399 trace(NULL, 2, "snr error: sat=%2d f=%d el=%.2f SNR=%.2f\n", sat, f, azel[i * 2 + 1] * R2D,
1400 obs_copy[i].SNR[f] * 0.25);
1401 meas[f] = meas[f + nf] = 0.0;
1402 continue;
1403 }
1404 meas[f] += obs_copy[i].L[f] * lam[f];
1405 meas[f + nf] += obs_copy[i].P[f];
1406 }
1407
1408 if (y) {
1409 for (j = 0; j < 2; j++) { /* phase and code */
1410 for (f = 0; f < nf; f++) {
1411 if (meas[nf * j + f] == 0.0) {
1412 continue;
1413 }
1414 if (j == 0) {
1415 ssat[sat - 1].code[f] = obs_copy[i].code[f];
1416 }
1417 if (f > 0 && !(f & qj)) {
1418 continue;
1419 }
1420 /* Keep CLAS PPP-RTK Galileo slots aligned with upstream: slot 1 is not used. */
1421 if (f == 1 && (satsys(sat, NULL) == SYS_GAL)) {
1422 continue;
1423 }
1424 if (j == 0 && (osr[i].pbias[f] == CLAS_CSSRINVALID)) {
1425 trace(NULL, 2, "invalid pbias sat=%2d f=%1d\n", sat, f);
1426 continue;
1427 }
1428 if (j == 1 && (osr[i].cbias[f] == CLAS_CSSRINVALID)) {
1429 trace(NULL, 2, "invalid cbias sat=%2d f=%1d\n", sat, f);
1430 continue;
1431 }
1432 y[nf * i * 2 + nf * j + f] = meas[nf * j + f] - modl[nf * j + f];
1433 }
1434 nv += nf;
1435 }
1436 } else {
1437 nv += nf * 2;
1438 }
1439
1440 /* store pbias time */
1441 osr_ctx->pt0tmp[sat - 1] = nav->ssr_ch[ch][sat - 1].t0[5];
1442
1443 /* GAL: clear L2 slots only if slot 1 is actually L2 band */
1444 if (sys == SYS_GAL && code2freq_num(obs_copy[i].code[1]) == 2) {
1445 osr[i].antr[1] = 0.0;
1446 osr[i].wupL[1] = 0.0;
1447 osr[i].CPC[1] = 0.0;
1448 osr[i].PRC[1] = 0.0;
1449 }
1450
1451 if (osrlog) {
1452 tow = time2gpst(obs_copy[i].time, NULL);
1453 trace(NULL, 3,
1454 "OSRRES(ch%d),%.1f,%d,%d,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,"
1455 "%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,"
1456 "%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,"
1457 "%.3f,%.9f,%.9f,%.3f\n",
1458 ch, tow, sys, prn, osr[i].pbias[0], osr[i].pbias[1], osr[i].pbias[2], osr[i].cbias[0],
1459 osr[i].cbias[1], osr[i].cbias[2], osr[i].trop, osr[i].iono, osr[i].antr[0], osr[i].antr[1],
1460 osr[i].antr[2], osr[i].relatv, osr[i].wupL[0], osr[i].wupL[1], osr[i].wupL[2], osr[i].compL[0],
1461 osr[i].compL[1], osr[i].compL[2], osr[i].sis, osr[i].CPC[0], osr[i].CPC[1], osr[i].CPC[2],
1462 osr[i].PRC[0], osr[i].PRC[1], osr[i].PRC[2], osr[i].orb, osr[i].clk, pos[0] * R2D, pos[1] * R2D,
1463 pos[2]);
1464 }
1465 }
1466
1467 free(obs_copy);
1468 return nv;
1469}
1470
1471/*============================================================================
1472 * clas_ssr2osr -- SSR-to-OSR Conversion Wrapper
1473 *
1474 * Verbatim port of upstream claslib cssr2osr.c ssr2osr() (lines 855-979).
1475 * Replaces upstream's global CSSR state with MRTKLIB's clas_ctx_t context.
1476 *===========================================================================*/
1477
1478/**
1479 * @brief Convert compact SSR corrections to observation-space representations.
1480 *
1481 * This is the top-level wrapper that sets up satellite state, grid/CSSR
1482 * corrections, then calls clas_osr_zdres() to compute per-satellite residuals.
1483 *
1484 * @param[in,out] rtk RTK control structure
1485 * @param[in,out] obs Observation data array (may be modified)
1486 * @param[in] n Number of observations
1487 * @param[in,out] nav Navigation data (SSR corrections applied here)
1488 * @param[out] osr OSR output per satellite
1489 * @param[in] mode 0=normal, unused (upstream compat)
1490 * @param[in] clas CLAS decoder context (replaces upstream globals)
1491 * @return Number of valid measurements, 0 on failure
1492 */
1493int clas_ssr2osr(rtk_t* rtk, obsd_t* obs, int n, nav_t* nav, clas_osrd_t* osr, int mode, clas_ctx_t* clas) {
1494 double *rs, *dts, *var, *e, *azel;
1495 int i, j, k = 0, nf = rtk->opt.nf, sati;
1496 prcopt_t* opt = &rtk->opt;
1497 int svh[MAXOBS];
1498 clas_grid_t* grid;
1499 clas_corr_t* corr;
1500 double pos[3];
1501 int ch, nn;
1502 static clas_osr_ctx_t osr_ctx;
1503 static int osr_ctx_init = 0;
1504
1505 if (!osr_ctx_init) {
1507 osr_ctx_init = 1;
1508 }
1509
1510 /* Clear only the output fields of the per-satellite OSR buffer.
1511 * All callers (cssr2rtcm3, ssr2obs, rtkpos) pass a MAXOBS-sized buffer
1512 * that is reused across epochs. When a satellite is skipped mid-epoch
1513 * (geodist failure, corrmeas failure, trop grid failure, etc.) the
1514 * caller's buffer keeps the previous epoch's p/c values and the VRS
1515 * output path would emit them as a valid MSM cell (see lessons.md L-036).
1516 *
1517 * We clear only .sat, .p[], .c[] here. The other fields (pbias, cbias,
1518 * iono, antr, wupL, compL, ...) are part of the cross-epoch state that
1519 * clas_osr_corrmeas() relies on — a full memset would break correction
1520 * propagation and drop the epoch output rate dramatically (lessons.md
1521 * L-036 has the numbers from the debugging session). */
1522 {
1523 int di, dj;
1524 for (di = 0; di < MAXOBS; di++) {
1525 osr[di].sat = 0;
1526 for (dj = 0; dj < NFREQ + NEXOBS; dj++) {
1527 osr[di].p[dj] = 0.0;
1528 osr[di].c[dj] = 0.0;
1529 }
1530 }
1531 }
1532
1533 rs = mat(6, n);
1534 dts = mat(2, n);
1535 var = mat(1, n);
1536 azel = zeros(2, n);
1537 e = mat(n, 3);
1538
1539 for (i = 0; i < MAXSAT; i++) {
1540 rtk->ssat[i].sys = satsys(i + 1, NULL);
1541 for (j = 0; j < NFREQ; j++) {
1542 rtk->ssat[i].vsat[j] = rtk->ssat[i].snr[j] = 0;
1543 }
1544 }
1545 for (i = 0; i < MAXSAT; i++) {
1546 rtk->ssat[i].fix[0] = 0;
1547 }
1548
1549 /* temporal update of states */
1550 if (opt->mode == PMODE_SSR2OSR_FIXED) {
1551 for (i = 0; i < 3; i++) {
1552 rtk->x[i] = opt->ru[i];
1553 }
1554 } else {
1555 if (norm(rtk->sol.rr, 3) > 0.0) {
1556 for (i = 0; i < 3; i++) {
1557 rtk->x[i] = rtk->sol.rr[i];
1558 }
1559 }
1560 }
1561
1562 /* grid index and CSSR lookup */
1563 ch = 0;
1564 grid = &clas->grid[ch];
1565 corr = &clas->current[ch];
1566 ecef2pos(rtk->x, pos);
1567
1568 if ((nn = clas_get_grid_index(clas, pos, grid, 0, obs[0].time)) <= 0) {
1569 trace(NULL, 2, "clas_ssr2osr: no valid grid\n");
1570 free(rs);
1571 free(dts);
1572 free(var);
1573 free(e);
1574 free(azel);
1575 rtk->sol.stat = SOLQ_SINGLE;
1576 return 0;
1577 }
1578
1579 /* re-fetch corrections closest to observation time each epoch,
1580 * falling back to L6 buffer time if obs lookup fails. current[ch] is a
1581 * bank snapshot, not a live decode target, so a fetch gated on network
1582 * change would keep serving the first correction set until it ages out. */
1583 if (grid->network > 0) {
1584 if (clas_bank_get_close(clas, obs[0].time, grid->network, ch, corr) != 0 &&
1585 clas_bank_get_close(clas, clas->l6buf[ch].time, grid->network, ch, corr) != 0) {
1586 trace(NULL, 2, "clas_ssr2osr: bank lookup failed\n");
1587 free(rs);
1588 free(dts);
1589 free(var);
1590 free(e);
1591 free(azel);
1592 rtk->sol.stat = SOLQ_SINGLE;
1593 return 0;
1594 }
1595 clas_check_grid_status(clas, corr, ch);
1596 }
1597 clas_update_global(nav, corr, ch);
1598 clas_update_local(nav, corr, ch);
1599
1600 /* SSR age */
1601 rtk->sol.age = 1e4;
1602 for (i = 0; i < n && i < MAXOBS; i++) {
1603 double age_d;
1604 sati = obs[i].sat;
1605 age_d = timediff(obs[i].time, nav->ssr_ch[ch][sati - 1].t0[1]);
1606 if (rtk->sol.age > age_d) {
1607 rtk->sol.age = age_d;
1608 }
1609 }
1610
1611 /* satellite positions and clocks */
1612 set_ssr_ch_idx(ch);
1613 satposs(obs[0].time, obs, n, nav, EPHOPT_SSRAPC, rs, dts, var, svh);
1614
1615 /* zero-difference residuals */
1616 k = clas_osr_zdres(obs, n, rs, dts, var, svh, nav, rtk->x, NULL, e, azel, rtk, 1, &osr_ctx, grid, corr, rtk->ssat,
1617 opt, &rtk->sol, osr, ch);
1618 if (!k) {
1619 trace(NULL, 2, "clas_ssr2osr: zdres failed\n");
1620 }
1621
1622 /* fill VRS pseudo-observations from OSR corrections (CSSR2OSR_VRS path) */
1623 {
1624 int ko = 0;
1625 for (i = 0; i < n && i < MAXOBS; i++) {
1626 double lam_v[NFREQ + NEXOBS];
1627 int sys_v, f;
1628 sati = obs[i].sat;
1629 if (sati <= 0) {
1630 continue;
1631 }
1632 if (osr[i].sat <= 0) {
1633 continue;
1634 }
1635
1636 /* Skip satellites with no usable observation on any signal. When
1637 * CLAS provides no valid code/phase bias for a satellite (or it is
1638 * stale), every signal comes out P = L = 0. The MSM encoder then
1639 * writes such a satellite with rough range 255 (invalid): a dead
1640 * entry that occupies the satellite mask but carries no usable
1641 * range or carrier. The rover cannot position with it and may let
1642 * it disturb satellite selection, so do not advertise it at all. */
1643 {
1644 int any_valid = 0;
1645 for (j = 0; j < nf; j++) {
1646 if (osr[i].p[j] != 0.0 || osr[i].c[j] != 0.0) {
1647 any_valid = 1;
1648 break;
1649 }
1650 }
1651 if (!any_valid) {
1652 continue;
1653 }
1654 }
1655 sys_v = satsys(sati, NULL);
1656
1657 obs[ko].time = obs[i].time;
1658 obs[ko].sat = sati;
1659 obs[ko].rcv = 1;
1660
1661 /* assign signal codes from corr's smode (CLAS decoder) */
1662 for (j = 0; j < nf; j++) {
1663 obs[ko].code[j] = corr->smode[sati - 1][j];
1664 }
1665 /* compute wavelengths from assigned codes */
1666 sat_wavelengths(sati, &obs[ko], nav, lam_v);
1667
1668 for (j = 0; j < nf; j++) {
1669 f = j;
1670 if (osr[i].p[j] == 0.0 && osr[i].c[j] == 0.0) {
1671 obs[ko].P[j] = 0.0;
1672 obs[ko].L[j] = 0.0;
1673 obs[ko].LLI[j] = 0;
1674 obs[ko].SNR[j] = 0;
1675 continue;
1676 }
1677 obs[ko].P[j] = osr[i].p[j];
1678 /* Apply the ±100-cycle phase-bias-wrap repair to the emitted
1679 * carrier phase. clas_osr_zdres() detects the wrap and stores
1680 * the cumulative offset in osr_ctx->pbias_ofst (indexed by
1681 * signal j), but in VRS/OSR mode it has no ambiguity filter
1682 * state to correct. Without this term the CLAS phase-bias wrap
1683 * leaks ~100 cycles (~19 m at L1) into the VRS phase, forcing a
1684 * cycle slip on the rover. Mirrors upstream cssr2osr.c L342
1685 * (obs[k].L[j] += pbias_ofst[...]). */
1686 obs[ko].L[j] =
1687 (lam_v[f] > 0.0) ? osr[i].c[j] / lam_v[f] + osr_ctx.pbias_ofst[j * MAXSAT + sati - 1] : 0.0;
1688 obs[ko].LLI[j] = (rtk->ssat[sati - 1].slip[j] & 1) ? 1 : 0;
1689 /* SNR model: elevation-dependent or fixed (via posopt[11]).
1690 * cssr2rtcm3 carries snr_fixed in this slot so it does not
1691 * clash with posopt[10], the GPS frequency option read by
1692 * clas_osr_selfreqpair(). */
1693 {
1694 double el = azel[i * 2 + 1]; /* elevation (rad), from zdres */
1695 double snr_db = (opt->posopt[11] > 0.0) ? opt->posopt[11] : 25.0 + 20.0 * sin(el);
1696 obs[ko].SNR[j] = (uint16_t)(snr_db / SNR_UNIT);
1697 }
1698 }
1699 /* clear remaining freq slots */
1700 for (j = nf; j < NFREQ + NEXOBS; j++) {
1701 obs[ko].P[j] = 0.0;
1702 obs[ko].L[j] = 0.0;
1703 obs[ko].LLI[j] = 0;
1704 obs[ko].code[j] = 0;
1705 obs[ko].SNR[j] = 0;
1706 }
1707 ko++;
1708 }
1709 k = ko;
1710 }
1711
1712 /* save observation state (upstream compat) */
1713 for (i = 0; i < k; i++) {
1714 for (j = 0; j < nf; j++) {
1715 if (obs[i].L[j] == 0.0) {
1716 continue;
1717 }
1718 rtk->ssat[obs[i].sat - 1].pt[obs[i].rcv - 1][j] = obs[i].time;
1719 rtk->ssat[obs[i].sat - 1].ph[obs[i].rcv - 1][j] = obs[i].L[j];
1720 }
1721 }
1722 for (i = 0; i < k; i++) {
1723 for (j = 0; j < nf; j++) {
1724 rtk->ssat[obs[i].sat - 1].snr[j] = obs[i].SNR[j];
1725 }
1726 }
1727 for (i = 0; i < MAXSAT; i++) {
1728 for (j = 0; j < nf; j++) {
1729 if (rtk->ssat[i].fix[j] == 2) {
1730 rtk->ssat[i].fix[j] = 1;
1731 }
1732 if (rtk->ssat[i].slip[j] & 1) {
1733 rtk->ssat[i].slipc[j] = 1;
1734 }
1735 }
1736 }
1737
1738 free(rs);
1739 free(dts);
1740 free(var);
1741 free(e);
1742 free(azel);
1743 return k;
1744}
void antmodel(const pcv_t *pcv, const double *del, const double *azel, int opt, double *dant)
Compute receiver antenna offset by antenna phase center parameters.
MRTKLIB Antenna Module — Antenna phase center model and parameter I/O.
void sunmoonpos(gtime_t tutc, const double *erpv, double *rsun, double *rmoon, double *gmst)
Compute sun and moon position in ECEF.
Definition mrtk_astro.c:135
MRTKLIB Astronomy Module — Sun and moon position computation.
double tropmapf(gtime_t time, const double pos[], const double azel[], double *mapfw)
Definition mrtk_atmos.c:256
MRTKLIB Atmosphere Module — Ionospheric and tropospheric models.
void clas_check_grid_status(clas_ctx_t *ctx, const clas_corr_t *corr, int ch)
Check and update grid status flags based on correction availability.
Definition mrtk_clas.c:1320
int clas_bank_get_close(const clas_ctx_t *ctx, gtime_t time, int network, int ch, clas_corr_t *corr)
Merge closest corrections from all banks into a clas_corr_t snapshot.
Definition mrtk_clas.c:1121
void clas_update_global(nav_t *nav, const clas_corr_t *corr, int ch)
Apply global corrections (orbit/clock/bias) to nav_t.ssr[].
Definition mrtk_clas.c:1430
void clas_update_local(nav_t *nav, const clas_corr_t *corr, int ch)
Apply local corrections (trop/STEC) to nav_t.
Definition mrtk_clas.c:1493
CLAS (QZSS L6D) CSSR decoder and grid correction type definitions.
#define CLAS_CSSRINVALID
Definition mrtk_clas.h:956
#define CLAS_MAXAGESSR_BIAS
Definition mrtk_clas.h:959
#define CLAS_CH_NUM
Definition mrtk_clas.h:185
int clas_trop_grid_data(clas_corr_t *corr, const int *index, gtime_t time, int n, const double *weight, const double *Gmat, const double *Emat, double *zwd, double *ztd, int *tbrk)
Interpolate troposphere delay from grid points.
int clas_get_grid_index(clas_ctx_t *ctx, const double *pos, clas_grid_t *grid, int gridsel, gtime_t obstime)
Select surrounding grid points and compute interpolation weights.
int clas_stec_grid_data(clas_corr_t *corr, const int *index, gtime_t time, int sat, int n, const double *weight, const double *Gmat, const double *Emat, double *iono, double *rate, double *var, int *brk)
Interpolate STEC (ionosphere delay) from grid points.
int get_stTv(gtime_t t, const double lat, const double hs, const double hg, double *tdv, double *twv)
void chk_isb(int sysno, const prcopt_t *opt, const sta_t *sta, double y[5][2])
Get ISB correction values for a satellite system.
int isL2C(int code)
Check if observation code is L2C.
#define POSL1L2L5
#define POSL1
static double clas_osr_adjust_prc(gtime_t teph, int sat, const ssr_t *ssr, int sig, double prc, double *sis, int *flag, int ch, clas_osr_ctx_t *ctx)
Adjust pseudorange correction for SIS discontinuity.
int clas_osr_selfreqpair(int sat, const prcopt_t *opt, const obsd_t *obs)
Select frequency pair for multi-frequency processing.
static double clas_osr_adjust_cpc(gtime_t teph, int sat, const ssr_t *ssr, int sig, double cpc, double *sis, int *flag, int ch, clas_osr_ctx_t *ctx)
Adjust carrier-phase correction for SIS discontinuity.
int clas_osr_corrmeas(const obsd_t *obs, nav_t *nav, const double *pos, const double *azel, const prcopt_t *opt, clas_grid_t *grid, clas_corr_t *corr, ssat_t ssat, int *brk, clas_osrd_t *osr, int *pbreset, int ch, clas_osr_ctx_t *osr_ctx)
Assemble per-satellite ionosphere/bias/antenna corrections.
#define MAXECORSSR_SIS
static int pcv_slot_has_data(const pcv_t *pcv, int slot)
static int claslib_pcv_slot(const pcv_t *pcv, int f)
#define POSL1L2
#define MAXCCORSSR_SIS
#define POSL1L5_L2
int clas_osr_zdres(const obsd_t *obs, int n, const double *rs, const double *dts, const double *vare, const int *svh, nav_t *nav, double *x, double *y, double *e, double *azel, rtk_t *rtk, int osrlog, clas_osr_ctx_t *osr_ctx, clas_grid_t *grid, clas_corr_t *corr, ssat_t *ssat, prcopt_t *opt, sol_t *sol, clas_osrd_t *osr, int ch)
Compute zero-differenced phase/code residuals with CLAS corrections.
static const unsigned char obsfreqs[]
#define MAXAGESSR_SIS
int clas_osr_satcorr_update(gtime_t time, gtime_t teph, int sat, const prcopt_t *opt, const nav_t *nav, int ch, clas_osr_ctx_t *ctx)
Update per-satellite SIS correction state for IODE adjustment.
static void sat_wavelengths(int sat, const obsd_t *obs, const nav_t *nav, double *lam)
Fill wavelength array for a satellite from observation codes.
static void clas_osr_adjust_r_dts(double *retr, double *retdts, gtime_t teph, int sat, nav_t *nav, const double *rr, gtime_t dts_time, int ch, clas_osr_ctx_t *ctx)
Recompute geometric range and clock on IODE change.
int clas_ssr2osr(rtk_t *rtk, obsd_t *obs, int n, nav_t *nav, clas_osrd_t *osr, int mode, clas_ctx_t *clas)
Convert compact SSR corrections to observation-space representations.
static int code_on_same_freq(int sys, uint8_t code1, uint8_t code2)
Check if two obs codes are on the same frequency band.
#define SQR(x)
#define GME
#define POSL1L5
void clas_osr_ctx_init(clas_osr_ctx_t *ctx)
Initialize OSR conversion context (zero all persistent state).
static int count_nsig(const int smode[MAXCODE])
Count non-zero entries in a signal mode array (equivalent to nsig).
void clas_osr_compensatedisp(clas_corr_t *corr, const int *index, const obsd_t *obs, int sat, double iono, const double *pb, double *compL, int *pbreset, const prcopt_t *opt, ssat_t ssat, int ch, clas_osr_ctx_t *osr_ctx, const nav_t *nav)
Compensate time variation of signal bias and ionosphere delay.
static void ocean_tide_clasgrid(gtime_t tut, const clas_oload_t *oload, const int *index, int nn, const double *E, const double *Emat, const double *Gmat, const double *weight, double *dr)
Compute ocean tide loading displacement using CLAS grid interpolation.
#define IB_RTK(s, f, opt)
static double shapiro(const double *rsat, const double *rrcv)
Compute Shapiro (relativistic) time delay correction.
static void windupcorr(gtime_t time, const double *rs, const double *rr, double *phw)
Compute phase windup correction.
double sat2freq(int sat, uint8_t code, const nav_t *nav)
Definition mrtk_obs.c:244
double clas_osr_prectrop(gtime_t time, const double *pos, const double *azel, double zwd, double ztd)
Precise troposphere model for CLAS (MOPS standard + mapping function).
MRTKLIB Constants — Physical, navigation, and system constants.
double geodist(const double *rs, const double *rr, double *e)
Compute geometric distance and receiver-to-satellite unit vector.
double satazel(const double *pos, const double *e, double *azel)
Compute satellite azimuth/elevation angle.
#define CLIGHT
Definition mrtk_coords.c:41
#define PI
Definition mrtk_coords.c:35
void xyz2enu(const double *pos, double *E)
Compute ECEF to local coordinate transformation matrix.
#define MAXSAT
Definition mrtk_coords.c:45
#define OMGE
Definition mrtk_coords.c:40
void ecef2pos(const double *r, double *pos)
Transform ECEF position to geodetic position.
MRTKLIB Coordinates Module — Coordinate transformations and geometry.
int satsys(int sat, int *prn)
Definition mrtk_nav.c:117
void set_ssr_ch_idx(int ch)
Set SSR channel index for satpos_ssr() (multi-L6E).
Definition mrtk_eph.c:761
#define SYS_GPS
Definition mrtk_eph.c:65
int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav, int iode, double *rs, double *dts, double *var, int *svh)
Satellite position and clock by broadcast ephemeris.
Definition mrtk_eph.c:673
#define EPHOPT_SSRAPC
Definition mrtk_eph.c:86
int satpos(gtime_t time, gtime_t teph, int sat, int ephopt, const nav_t *nav, double *rs, double *dts, double *var, int *svh)
Compute satellite position, velocity and clock.
Definition mrtk_eph.c:891
#define SYS_QZS
Definition mrtk_eph.c:69
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)
Compute satellite positions, velocities and clocks.
Definition mrtk_eph.c:940
#define SYS_CMP
Definition mrtk_eph.c:70
#define SYS_GAL
Definition mrtk_eph.c:68
double eph2clk(gtime_t time, const eph_t *eph)
Compute satellite clock bias with broadcast ephemeris (GPS/GAL/QZS).
Definition mrtk_eph.c:232
eph_t * seleph(gtime_t time, int sat, int iode, const nav_t *nav)
Select broadcast ephemeris by satellite and IODE.
Definition mrtk_eph.c:465
MRTKLIB Broadcast Ephemeris Module — Satellite orbit/clock types and Kepler/GLONASS/SBAS orbit comput...
struct nav_s nav_t
Definition mrtk_eph.h:40
#define SNR_UNIT
#define MAXOBS
#define MAXCODE
#define NFREQPCV
double geoidh(const double *pos)
Get geoid height from geoid model.
Definition mrtk_geoid.c:268
MRTKLIB Geoid Module — Geoid height models.
double * mat(int n, int m)
Allocate memory for a double matrix.
Definition mrtk_mat.c:76
double * zeros(int n, int m)
Allocate a new zero matrix.
Definition mrtk_mat.c:108
void cross3(const double *a, const double *b, double *c)
Cross product of 3D vectors.
Definition mrtk_mat.c:172
double norm(const double *a, int n)
Euclidean norm of vector.
Definition mrtk_mat.c:165
int normv3(const double *a, double *b)
Normalize 3D vector.
Definition mrtk_mat.c:183
void matmul(const char *tr, int n, int k, int m, double alpha, const double *A, const double *B, double beta, double *C)
Multiply matrix by matrix (C = alpha*A*B + beta*C).
Definition mrtk_mat.c:310
double dot(const double *a, const double *b, int n)
Inner product of vectors.
Definition mrtk_mat.c:151
MRTKLIB Matrix Module — Matrix/vector operations and estimation.
MRTKLIB Navigation Module — Navigation data types and satellite/nav utility functions.
#define ISBOPT_TABLE
Definition mrtk_nav.h:349
#define FREQ2
Definition mrtk_obs.c:68
#define R2D
Definition mrtk_obs.c:51
int code2freq_num(uint8_t code)
Extract frequency number from obs code.
Definition mrtk_obs.c:201
#define FREQ1
Definition mrtk_obs.c:67
int code2freq_idx(int sys, uint8_t code)
Convert system and obs code to frequency index (obsdef-based).
Definition mrtk_obs.c:225
int testsnr(int base, int idx, double el, double snr, const snrmask_t *mask)
Test SNR mask.
Definition mrtk_obs.c:132
MRTKLIB Observation Module — Observation data types and utility functions.
MRTKLIB Options Module — Processing and solution option types.
#define SOLQ_SINGLE
Definition mrtk_opt.h:75
#define TROPOPT_EST
Definition mrtk_opt.h:105
#define PMODE_SSR2OSR_FIXED
Definition mrtk_opt.h:56
#define TROPOPT_ESTG
Definition mrtk_opt.h:106
int geterp(const erp_t *erp, gtime_t time, double *erpv)
Get earth rotation parameter values at specified time.
Definition mrtk_peph.c:146
double cpc[SSR_CH_NUM][MAXSAT *5]
clas_osr_ctx_t osr_ctx
#define NT_RTK(opt)
PPP-RTK positioning engine declarations.
MRTKLIB RTK Positioning Module — relative and precise positioning.
MRTKLIB Solution Module — Solution data types and I/O functions.
void tidedisp(gtime_t tutc, const double *rr, int opt, const erp_t *erp, const double *odisp, double *dr)
Compute displacements by earth tides.
Definition mrtk_tides.c:261
void tide_oload(gtime_t tut, const double *odisp, double *denu)
Compute displacement by ocean tide loading (11 constituents).
Definition mrtk_tides.c:146
MRTKLIB Tides Module — Tidal displacement corrections.
gtime_t gpst2utc(gtime_t t)
Convert GPS time to UTC considering leap seconds.
Definition mrtk_time.c:422
double timediff(gtime_t t1, gtime_t t2)
Compute time difference (t1 - t2).
Definition mrtk_time.c:279
double time2gpst(gtime_t t, int *week)
Convert gtime_t to GPS week/tow.
Definition mrtk_time.c:181
gtime_t timeadd(gtime_t t, double sec)
Add seconds to gtime_t.
Definition mrtk_time.c:265
char * time_str(gtime_t t, int n)
Get time string (not reentrant).
Definition mrtk_time.c:530
gtime_t utc2gpst(gtime_t t)
Convert UTC to GPS time considering leap seconds.
Definition mrtk_time.c:440
MRTKLIB Time Module — GNSS time types and conversion functions.
void trace(mrtk_ctx_t *ctx, int level, const char *format,...)
Output trace message.
Definition mrtk_trace.c:78
MRTKLIB Trace Module — debug trace and logging functions.
stec_t stec[80]
Definition mrtk_clas.h:333
int smode[MAXSAT][MAXCODE]
Definition mrtk_clas.h:342
clas_grid_t grid[2]
Definition mrtk_clas.h:592
clas_corr_t current[2]
Definition mrtk_clas.h:588
clas_l6buf_t l6buf[2]
Definition mrtk_clas.h:608
clas_oload_t oload[13]
Definition mrtk_clas.h:602
double weight[4]
Definition mrtk_clas.h:464
double Gmat[4 *4]
Definition mrtk_clas.h:463
double Emat[4]
Definition mrtk_clas.h:465
int index[4]
Definition mrtk_clas.h:466
gtime_t time
Definition mrtk_clas.h:565
double odisp[80][6 *11]
Definition mrtk_clas.h:244
clas_satcorr_t satcorr[2][MAXSAT]
Definition mrtk_clas.h:939
double saved_rr[3]
Definition mrtk_clas.h:940
double c[5+5]
Definition mrtk_clas.h:502
double antr[5+5]
Definition mrtk_clas.h:495
double orb
Definition mrtk_clas.h:483
double compL[5+5]
Definition mrtk_clas.h:497
double wupL[5+5]
Definition mrtk_clas.h:496
double CPC[5+5]
Definition mrtk_clas.h:498
unsigned char sat
Definition mrtk_clas.h:479
double pbias[5+5]
Definition mrtk_clas.h:488
double trop
Definition mrtk_clas.h:484
double p[5+5]
Definition mrtk_clas.h:503
double relatv
Definition mrtk_clas.h:493
double sis
Definition mrtk_clas.h:510
double PRC[5+5]
Definition mrtk_clas.h:499
double clk
Definition mrtk_clas.h:482
double cbias[5+5]
Definition mrtk_clas.h:487
double iono
Definition mrtk_clas.h:485
double prev_orb_tow
Definition mrtk_clas.h:915
GPS/GAL/QZS/BDS/IRN broadcast ephemeris type.
Definition mrtk_eph.h:77
int n
Definition mrtk_peph.h:60
GNSS time representation.
Definition mrtk_time.h:49
time_t time
Definition mrtk_time.h:50
int filreset
Definition mrtk_nav.h:436
sta_t stas[MAXRCV]
Definition mrtk_nav.h:451
sft_t sfts
Definition mrtk_nav.h:450
erp_t erp
Definition mrtk_nav.h:413
void * clas_ctx
Definition mrtk_nav.h:439
ssr_t ssr_ch[SSR_CH_NUM][MAXSAT]
Definition mrtk_nav.h:434
Observation data record.
Definition mrtk_obs.h:44
gtime_t time
Definition mrtk_obs.h:45
uint8_t sat
Definition mrtk_obs.h:46
uint8_t LLI[5+5]
Definition mrtk_obs.h:48
double L[5+5]
Definition mrtk_obs.h:50
uint8_t rcv
Definition mrtk_obs.h:46
double P[5+5]
Definition mrtk_obs.h:51
uint16_t SNR[5+5]
Definition mrtk_obs.h:47
uint8_t code[5+5]
Definition mrtk_obs.h:49
double var[NFREQPCV][19]
Definition mrtk_nav.h:51
double off[NFREQPCV][3]
Definition mrtk_nav.h:50
int isb
Definition mrtk_opt.h:260
snrmask_t snrmask
Definition mrtk_opt.h:177
double elmin
Definition mrtk_opt.h:176
int phasshft
Definition mrtk_opt.h:259
pcv_t pcvr[2]
Definition mrtk_opt.h:224
int tropopt
Definition mrtk_opt.h:188
char rectype[2][MAXANT]
Definition mrtk_opt.h:274
double odisp[2][6 *11]
Definition mrtk_opt.h:239
int posopt[13]
Definition mrtk_opt.h:237
int mode
Definition mrtk_opt.h:172
int tidecorr
Definition mrtk_opt.h:190
double antdel[2][3]
Definition mrtk_opt.h:223
double ru[3]
Definition mrtk_opt.h:220
double * x
Definition mrtk_rtkpos.h:54
prcopt_t opt
Definition mrtk_rtkpos.h:63
ssat_t ssat[MAXSAT]
Definition mrtk_rtkpos.h:60
sol_t sol
Definition mrtk_rtkpos.h:50
char rectyp[100][MAXANT]
Definition mrtk_nav.h:361
int n
Definition mrtk_nav.h:360
double bias[100]
Definition mrtk_nav.h:362
double rr[6]
Definition mrtk_sol.h:46
uint8_t stat
Definition mrtk_sol.h:54
float age
Definition mrtk_sol.h:56
gtime_t time
Definition mrtk_sol.h:45
gtime_t pt[2][5]
Definition mrtk_sol.h:116
uint8_t vsat[5]
Definition mrtk_sol.h:104
uint32_t slipc[5]
Definition mrtk_sol.h:111
double ph[2][5]
Definition mrtk_sol.h:117
double phw
Definition mrtk_sol.h:115
uint8_t slip[5]
Definition mrtk_sol.h:107
uint16_t snr[5]
Definition mrtk_sol.h:105
uint8_t sys
Definition mrtk_sol.h:99
uint8_t fix[5]
Definition mrtk_sol.h:106
uint8_t code[5]
Definition mrtk_sol.h:124
double dclk[3]
Definition mrtk_nav.h:185
double ddeph[3]
Definition mrtk_nav.h:184
double hrclk
Definition mrtk_nav.h:186
gtime_t t0[6]
Definition mrtk_nav.h:176
int iod[6]
Definition mrtk_nav.h:178
int iode
Definition mrtk_nav.h:179
double deph[3]
Definition mrtk_nav.h:183
double pbias[MAXCODE]
Definition mrtk_nav.h:188
float cbias[MAXCODE]
Definition mrtk_nav.h:187
double udi[6]
Definition mrtk_nav.h:177
int n
Definition mrtk_clas.h:214
stecd_t * data
Definition mrtk_clas.h:215
gtime_t time
Definition mrtk_clas.h:202
unsigned char sat
Definition mrtk_clas.h:203
#define malloc(x)
Definition toml.c:52
#define free(x)
Definition toml.c:53