MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
mrtk_clas_grid.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * mrtk_clas_grid.c : CLAS grid interpolation for troposphere and STEC
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_grid.c
18 * @brief CLAS grid interpolation for troposphere and STEC.
19 *
20 * References:
21 * [1] IS-QZSS-L6-006, QZSS L6 Signal CLAS IF Specification
22 *
23 * History:
24 * 2026/03/02 1.0 ported from claslib grid.c / stec.c / rtkcmn.c
25 * - singleton globals replaced by clas_ctx_t context
26 * - nav_t dependency replaced by clas_corr_t
27 * - MOPS standard atmosphere helpers embedded as static
28 */
29#include <math.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include "mrtklib/mrtk_clas.h"
34#include "mrtklib/mrtk_const.h"
35#include "mrtklib/mrtk_geoid.h"
36#include "mrtklib/mrtk_mat.h"
38#include "mrtklib/mrtk_time.h"
39#include "mrtklib/mrtk_trace.h"
40
41/*============================================================================
42 * Local Constants
43 *===========================================================================*/
44#define MAX_GRID_CACHE 20 /* candidate grids sorted by distance */
45#define MAX_DIST 120.0 /* max distance to grid (km) */
46#define MAXAGESSR_TROP 120.0 /* max age of trop correction (s) */
47#define MAXAGESSR_IONO 120.0 /* max age of iono correction (s) */
48
49#define SQR(x) ((x) * (x))
50#define MIN(x, y) ((x) < (y) ? (x) : (y))
51#define MAX(x, y) ((x) > (y) ? (x) : (y))
52
53/*============================================================================
54 * Local Types
55 *===========================================================================*/
56
57/** Grid candidate info for sorting by distance */
58typedef struct {
59 double pos[3]; /* grid position {lat,lon,hgt} (deg) */
60 double weight; /* distance metric (m) */
61 int network; /* network ID (1-based) */
62 int index; /* grid point index */
63} grid_info;
64
65/*============================================================================
66 * MOPS Standard Atmosphere Helpers (from rtkcmn.c)
67 *
68 * Used by trop_data() to normalize decoded ZTD/ZWD values against
69 * the MOPS standard atmosphere model.
70 *===========================================================================*/
71
72/* linear interpolation ------------------------------------------------------*/
73static double Interp1(const double* x, const double* y, int nx, int ny, double xi) {
74 int i;
75 double dx;
76
77 for (i = 0; i < nx - 1; i++) {
78 if (xi < x[i + 1]) {
79 break;
80 }
81 }
82 dx = (xi - x[i]) / (x[i + 1] - x[i]);
83
84 return y[i] * (1.0 - dx) + y[i + 1] * dx;
85}
86
87/* get MOPS meteorological parameters ----------------------------------------*/
88static int get_mops(const double lat, const double doy, double* pre, double* temp, double* wpre, double* ltemp,
89 double* wvap) {
90 int i;
91 double cos_, latdeg = lat * R2D; /* rad->deg */
92 const double dminN = 28; /* 28@north hemisphere */
93 const double interval[] = {15.0, 30.0, 45.0, 60.0, 75.0};
94
95 /* meteorological parameter average table */
96 const double pTave[] = {1013.25, 1017.25, 1015.75, 1011.75, 1013.00};
97 const double tTave[] = {299.65, 294.15, 283.15, 272.15, 263.65};
98 const double wTave[] = {26.31, 21.79, 11.66, 6.78, 4.11};
99 const double tlTave[] = {6.30e-3, 6.05e-3, 5.58e-3, 5.39e-3, 4.53e-3};
100 const double wlTave[] = {2.77, 3.15, 2.57, 1.81, 1.55};
101
102 /* meteorological parameter seasonal variation table */
103 const double pTableS[] = {0.00, -3.75, -2.25, -1.75, -0.50};
104 const double tTableS[] = {0.00, 7.00, 11.00, 15.00, 14.50};
105 const double wTableS[] = {0.00, 8.85, 7.24, 5.36, 3.39};
106 const double tlTableS[] = {0.00e-3, 0.25e-3, 0.32e-3, 0.81e-3, 0.62e-3};
107 const double wlTableS[] = {0.00, 0.33, 0.46, 0.74, 0.30};
108
109 double calcPsta[5], calcTsta[5], calcWsta[5], calcTLsta[5], calcWLsta[5];
110
111 for (i = 0; i < 5; i++) {
112 cos_ = cos(2.0 * PI * (doy - dminN) / 365.25);
113 calcPsta[i] = pTave[i] - pTableS[i] * cos_;
114 calcTsta[i] = tTave[i] - tTableS[i] * cos_;
115 calcWsta[i] = wTave[i] - wTableS[i] * cos_;
116 calcTLsta[i] = tlTave[i] - tlTableS[i] * cos_;
117 calcWLsta[i] = wlTave[i] - wlTableS[i] * cos_;
118 }
119
120 if (75.0 <= latdeg) {
121 *pre = calcPsta[4];
122 *temp = calcTsta[4];
123 *wpre = calcWsta[4];
124 *ltemp = calcTLsta[4];
125 *wvap = calcWLsta[4];
126 } else if (latdeg <= 15.0) {
127 *pre = calcPsta[0];
128 *temp = calcTsta[0];
129 *wpre = calcWsta[0];
130 *ltemp = calcTLsta[0];
131 *wvap = calcWLsta[0];
132 } else if (15.0 < latdeg && latdeg < 75.0) {
133 *pre = Interp1(interval, calcPsta, 5, 5, latdeg);
134 *temp = Interp1(interval, calcTsta, 5, 5, latdeg);
135 *wpre = Interp1(interval, calcWsta, 5, 5, latdeg);
136 *ltemp = Interp1(interval, calcTLsta, 5, 5, latdeg);
137 *wvap = Interp1(interval, calcWLsta, 5, 5, latdeg);
138 } else {
139 return 1;
140 }
141 return 0;
142}
143
144/* dry zenith delay scaling factor -------------------------------------------*/
145static double get_Tdv(const double lat, const double Hs, const double Ps) {
146 return 2.2768 / (1.0 - 0.00266 * cos(2.0 * lat) - (2.8e-7) * Hs) * Ps * 0.001;
147}
148
149/* wet zenith delay scaling factor -------------------------------------------*/
150static double get_Twv(const double Ts, const double es) { return 2.2768 * (1255.0 / Ts + 0.05) * es * 0.001; }
151
152/* standard atmosphere dry/wet zenith delay ----------------------------------
153 *
154 * args: gtime_t t I time (GPST)
155 * double lat I latitude (rad)
156 * double hs I ellipsoidal height (m)
157 * double hg I geoid height (m)
158 * double *tdv O dry zenith delay scaling factor
159 * double *twv O wet zenith delay scaling factor
160 * return: 0=ok, 1=error
161 *---------------------------------------------------------------------------*/
162/* non-static: called from mrtk_clas_osr.c via extern declaration */
163int get_stTv(gtime_t t, const double lat, const double hs, const double hg, double* tdv, double* twv) {
164 double P0, T0, e0, beta0, lambda0, Hs, Ts, Ps, es, t0, tk, tc, ET, doy;
165 const double g = 9.80665, Rd = 287.0537625; /* J/(kg.K) */
166
167 doy = time2doy(t);
168
169 if (get_mops(lat, doy, &P0, &T0, &e0, &beta0, &lambda0)) {
170 return 1;
171 }
172
173 Hs = hs - hg; /* elevation = ellipsoidal height - geoid height */
174
175 Ts = T0 - beta0 * Hs;
176 Ps = P0 * pow(1 - beta0 * Hs / T0, g / (Rd * beta0));
177 es = e0 * pow(1 - beta0 * Hs / T0, (lambda0 + 1.0) * g / (Rd * beta0));
178
179 t0 = 273.15;
180 tk = Ts;
181 tc = tk - t0;
182 ET = 6.11 * pow(tk / t0, -5.3) * exp(25.2 * tc / tk);
183
184 if (es > ET) {
185 es = ET;
186 }
187
188 *tdv = get_Tdv(lat, Hs + hg, Ps); /* dry */
189 *twv = get_Twv(Ts, es); /* wet */
190
191 return 0;
192}
193
194/*============================================================================
195 * Grid Candidate Search and Selection (from grid.c)
196 *===========================================================================*/
197
198/* pickup candidate grids within MAX_DIST of receiver position ---------------*/
199static int pickup_candidates_of_grid(const clas_ctx_t* ctx, grid_info* gridinfo, const int network, const double* pos,
200 int ch) {
201 int start = (network > 0 ? network : 1);
202 int end = (network > 0 ? network + 1 : CLAS_MAX_NETWORK);
203 int inet, i, j, k, n = 0;
204 double d, dd[2];
205
206 for (inet = start; inet < end; ++inet) {
207 for (i = 0; i < CLAS_MAX_GP; ++i) {
208 /* sentinel check: {-1,-1,-1} marks end of grid */
209 if (fabs(-1.0 - ctx->grid_pos[inet][i][0]) < 0.0001 && fabs(-1.0 - ctx->grid_pos[inet][i][1]) < 0.0001 &&
210 fabs(-1.0 - ctx->grid_pos[inet][i][2]) < 0.0001) {
211 break;
212 }
213 /* skip grids with non-zero height or invalid status */
214 if (fabs(ctx->grid_pos[inet][i][2]) > 0.0001 || ctx->grid_stat[ch][inet][i] == 0) {
215 continue;
216 }
217
218 /* distance to grid (m) */
219 dd[0] = RE_WGS84 * (pos[0] - ctx->grid_pos[inet][i][0] * D2R);
220 dd[1] = RE_WGS84 * (pos[1] - ctx->grid_pos[inet][i][1] * D2R) * cos(pos[0]);
221 if ((d = MAX(norm(dd, 2), 1.0)) > MAX_DIST * 1000.0) {
222 continue;
223 }
224
225 trace(NULL, 5,
226 "get_grid_index: inet=%2d, pos=%3.3f %3.3f, "
227 "dist=%6.3f[km]\n",
228 inet, ctx->grid_pos[inet][i][0], ctx->grid_pos[inet][i][1], d / 1000.0);
229
230 /* insertion sort into gridinfo[] by distance */
231 if (n <= 0) {
232 gridinfo[n].pos[0] = ctx->grid_pos[inet][i][0];
233 gridinfo[n].pos[1] = ctx->grid_pos[inet][i][1];
234 gridinfo[n].pos[2] = ctx->grid_pos[inet][i][2];
235 gridinfo[n].network = inet;
236 gridinfo[n].weight = d;
237 gridinfo[n].index = i;
238 ++n;
239 } else {
240 for (j = 0; j < n; j++) {
241 if (d < gridinfo[j].weight) {
242 break;
243 }
244 }
245 if (j >= MAX_GRID_CACHE) {
246 continue;
247 }
248 for (k = MIN(n, MAX_GRID_CACHE - 1); k > j; k--) {
249 gridinfo[k] = gridinfo[k - 1];
250 }
251 gridinfo[j].pos[0] = ctx->grid_pos[inet][i][0];
252 gridinfo[j].pos[1] = ctx->grid_pos[inet][i][1];
253 gridinfo[j].pos[2] = ctx->grid_pos[inet][i][2];
254 gridinfo[j].network = inet;
255 gridinfo[j].weight = d;
256 gridinfo[j].index = i;
257 if (n < MAX_GRID_CACHE) {
258 n++;
259 }
260 }
261 }
262 }
263 return n;
264}
265
266/* find 3 or 4 grid points surrounding receiver position ---------------------*/
267static int find_grid_surround_pos(grid_info** gridindex, int n, grid_info* gridinfo) {
268 int first_index;
269 double dd12[2], dd13[2], dd14[2];
270 grid_info *grid2 = NULL, *grid3 = NULL;
271 int flag, i;
272
273 first_index = (gridinfo[1].network == gridinfo[2].network && gridinfo[0].network != gridinfo[1].network) ? 1 : 0;
274
275 /* find 2nd grid in same network */
276 for (i = 1, gridindex[0] = &gridinfo[first_index], flag = 0; i < n; ++i) {
277 if (gridinfo[first_index].network == gridinfo[i].network && first_index != i) {
278 dd12[0] = gridinfo[i].pos[0] * D2R - gridinfo[first_index].pos[0] * D2R;
279 dd12[1] = gridinfo[i].pos[1] * D2R - gridinfo[first_index].pos[1] * D2R;
280 gridindex[1] = &gridinfo[i];
281 grid2 = &gridinfo[i];
282 flag = 1;
283 break;
284 }
285 }
286 if (!flag) {
287 trace(NULL, 2, "can't find second grid: network=%d\n", gridinfo[first_index].network);
288 return 1;
289 }
290
291 /* find 3rd grid: non-collinear with dd12 */
292 for (i = 1, flag = 0; i < n; i++) {
293 if (gridindex[1]->index == gridinfo[i].index || gridinfo[first_index].network != gridinfo[i].network ||
294 first_index == i) {
295 continue;
296 }
297 dd13[0] = gridinfo[i].pos[0] * D2R - gridinfo[first_index].pos[0] * D2R;
298 dd13[1] = gridinfo[i].pos[1] * D2R - gridinfo[first_index].pos[1] * D2R;
299 if (fabs(dot(dd12, dd13, 2) / (norm(dd12, 2) * norm(dd13, 2))) < 1.0) {
300 gridindex[2] = &gridinfo[i];
301 grid3 = &gridinfo[i];
302 flag = 1;
303 break;
304 }
305 }
306 if (!flag) {
307 trace(NULL, 2, "can't find third grid: network=%d\n", gridinfo[first_index].network);
308 return 1;
309 }
310
311 /* find 4th grid: non-collinear with both dd12 and dd13, rectangular */
312 for (i = 1, flag = 0; i < n; i++) {
313 if (gridindex[1]->index == gridinfo[i].index || gridindex[2]->index == gridinfo[i].index ||
314 gridinfo[first_index].network != gridinfo[i].network || first_index == i) {
315 continue;
316 }
317 dd14[0] = gridinfo[i].pos[0] * D2R - gridinfo[first_index].pos[0] * D2R;
318 dd14[1] = gridinfo[i].pos[1] * D2R - gridinfo[first_index].pos[1] * D2R;
319 if (fabs(dot(dd12, dd14, 2) / (norm(dd12, 2) * norm(dd14, 2))) < 1.0 &&
320 fabs(dot(dd13, dd14, 2) / (norm(dd13, 2) * norm(dd14, 2))) < 1.0) {
321 if ((fabs(grid2->pos[1] - gridinfo[i].pos[1]) < 0.04 && fabs(grid3->pos[0] - gridinfo[i].pos[0]) < 0.04) ||
322 (fabs(grid2->pos[0] - gridinfo[i].pos[0]) < 0.04 && fabs(grid3->pos[1] - gridinfo[i].pos[1]) < 0.04)) {
323 gridindex[3] = &gridinfo[i];
324 flag = 1;
325 break;
326 }
327 }
328 }
329
330 if (!flag) {
331 trace(NULL, 2, "can't find fourth grid: network=%d\n", gridinfo[first_index].network);
332 n = 3;
333 } else {
334 n = 4;
335 }
336 return n;
337}
338
339/* barycentric weights for 3-point interpolation -----------------------------*/
340static void calc_3points_weight(double* weight, const double* pos, grid_info** gridindex) {
341 double *U, *E, dd[2];
342 int i;
343
344 U = mat(2, 2);
345 E = mat(2, 1);
346 for (i = 1; i < 3; i++) {
347 U[(i - 1) * 2 + 0] = gridindex[i]->pos[0] - gridindex[0]->pos[0];
348 U[(i - 1) * 2 + 1] = gridindex[i]->pos[1] - gridindex[0]->pos[1];
349 }
350 trace(NULL, 5, "Umat=\n");
351 tracemat(NULL, 5, U, 2, 2, 10, 5);
352
353 if (matinv(U, 2)) {
354 trace(NULL, 2, "calculation error of G inverse\n");
355 }
356 trace(NULL, 5, "Umat=\n");
357 tracemat(NULL, 5, U, 2, 2, 10, 5);
358
359 E[0] = pos[0] * R2D - gridindex[0]->pos[0];
360 E[1] = pos[1] * R2D - gridindex[0]->pos[1];
361 matmul("NN", 2, 1, 2, 1.0, U, E, 0.0, dd);
362 free(U);
363 free(E);
364
365 weight[0] = 1.0 - dd[0] - dd[1];
366 weight[1] = dd[0];
367 weight[2] = dd[1];
368}
369
370/* check if receiver is inside grid polygon (stub) ---------------------------*/
371static int is_exists_inside(const double* pos, int n, grid_info** gridindex, grid_info* nearestgrid) {
372 int flag = 0xff; /* always true — upstream uses this as disabled stub */
373
374 if (flag == 0x00) {
375 if (nearestgrid->weight < gridindex[0]->weight) {
376 trace(NULL, 2,
377 "change to the nearest grid: inet=%d, index=%d, "
378 "pos=%6.3f %6.3f %6.3f, distance=%.2fKm\n",
379 nearestgrid->network, nearestgrid->index, nearestgrid->pos[0], nearestgrid->pos[1],
380 nearestgrid->pos[2], nearestgrid->weight / 1000.0);
381 gridindex[0] = nearestgrid;
382 return 1;
383 }
384 trace(NULL, 2,
385 "change to the nearest grid: inet=%d, index=%d, "
386 "pos=%6.3f %6.3f %6.3f, distance=%.2fKm\n",
387 gridindex[0]->network, gridindex[0]->index, gridindex[0]->pos[0], gridindex[0]->pos[1],
388 gridindex[0]->pos[2], gridindex[0]->weight / 1000.0);
389 n = 1;
390 }
391 return n;
392}
393
394/* compute grid interpolation weights ----------------------------------------*/
395static void calc_grid_weight(double* weight, const double* pos, int n, grid_info** gridindex) {
396 double sum;
397 int i;
398
399 if (n != 3) {
400 sum = 0.0;
401 for (i = 0; i < n; i++) {
402 sum += 1.0 / gridindex[i]->weight;
403 }
404 for (i = 0; i < n; i++) {
405 weight[i] = 1.0 / (gridindex[i]->weight * sum);
406 }
407 } else {
408 calc_3points_weight(weight, pos, gridindex);
409 }
410}
411
412/* log selected grid points (trace only on change) ---------------------------*/
413static void output_selected_grid(const double* pos, int n, grid_info** gridindex, double* weight) {
414 static int savenetwork[4];
415 static int saveindex[4];
416 static int savenum = -1;
417 int flag = 0;
418 int i;
419
420 for (i = 0; i < savenum; ++i) {
421 if (savenetwork[i] != gridindex[i]->network || saveindex[i] != gridindex[i]->index) {
422 flag = 1;
423 break;
424 }
425 }
426 if (savenum == n && !flag) {
427 return;
428 }
429
430 trace(NULL, 1, "pos est: pos=%6.3f %6.3f\n", pos[0] / D2R, pos[1] / D2R);
431 for (i = 0; i < n; i++) {
432 trace(NULL, 1,
433 "selected_grid%d: inet=%d, weight=%.2f, "
434 "pos=%6.3f %6.3f, dist=%.2fkm, index=%d\n",
435 i + 1, gridindex[i]->network, weight[i], gridindex[i]->pos[0], gridindex[i]->pos[1],
436 gridindex[i]->weight / 1000.0, gridindex[i]->index);
437 }
438
439 for (i = 0; i < n; ++i) {
440 savenetwork[i] = gridindex[i]->network;
441 saveindex[i] = gridindex[i]->index;
442 }
443 savenum = n;
444}
445
446/*============================================================================
447 * Public: Grid Selection
448 *===========================================================================*/
449
450/* select surrounding grid points and compute interpolation weights ----------
451 *
452 * args: clas_ctx_t *ctx I CLAS context (grid_pos, grid_stat)
453 * const double *pos I receiver position {lat,lon,hgt} (rad,m)
454 * clas_grid_t *grid O interpolation result
455 * int gridsel I grid selection threshold (m), 0=always multi
456 * gtime_t obstime I observation time (reserved)
457 * return: number of selected grids (0=error, 1/3/4=ok)
458 *---------------------------------------------------------------------------*/
459extern int clas_get_grid_index(clas_ctx_t* ctx, const double* pos, clas_grid_t* grid, int gridsel, gtime_t obstime) {
461 grid_info* gridindex[4];
462 double dlat, dlon;
463 int i, n = 0, ch, nch[CLAS_CH_NUM] = {0};
464
465 trace(NULL, 3, "clas_get_grid: pos=%.3f %.3f\n", pos[0] * R2D, pos[1] * R2D);
466
467 /* search both channels, select channel with more candidates */
468 for (ch = 0; ch < CLAS_CH_NUM; ch++) {
469 nch[ch] = pickup_candidates_of_grid(ctx, gridinfo[ch], grid->network, pos, ch);
470 }
471 ch = nch[0] >= nch[1] ? 0 : 1;
472 n = nch[ch];
473
474 if (n >= 3 && gridinfo[ch][0].network <= 12) {
475 if (gridsel == 0 || gridinfo[ch][0].weight > (double)gridsel) {
476 n = find_grid_surround_pos(&gridindex[0], n, gridinfo[ch]);
477 } else {
478 gridindex[0] = &gridinfo[ch][0];
479 n = 1;
480 }
481 } else if (n == 0) {
482 trace(NULL, 2, "can't find nearby grid: rover pos=%3.3f %3.3f\n", pos[0] * R2D, pos[1] * R2D);
483 return 0;
484 } else {
485 gridindex[0] = &gridinfo[ch][0];
486 n = 1;
487 }
488
489 n = is_exists_inside(pos, n, &gridindex[0], &gridinfo[ch][0]);
490 calc_grid_weight(grid->weight, pos, n, &gridindex[0]);
491 output_selected_grid(pos, n, gridindex, grid->weight);
492
493 /* build 4×4 interpolation matrix for bilinear case */
494 if (n == 4) {
495 double* U = mat(n, n);
496 for (i = 0; i < n; i++) {
497 dlat = gridindex[i]->pos[0] - gridindex[0]->pos[0];
498 dlon = gridindex[i]->pos[1] - gridindex[0]->pos[1];
499 U[0 * n + i] = dlat;
500 U[1 * n + i] = dlon;
501 U[2 * n + i] = dlat * dlon;
502 U[3 * n + i] = 1.0;
503 }
504 trace(NULL, 5, "Umat=\n");
505 tracemat(NULL, 5, U, 4, n, 10, 5);
506
507 if (matinv(U, n)) {
508 trace(NULL, 2, "calculation error of G inverse\n");
509 }
510 matcpy(grid->Gmat, U, n, n);
511 trace(NULL, 5, "Gmat=\n");
512 tracemat(NULL, 5, grid->Gmat, 4, n, 10, 5);
513 free(U);
514 }
515
516 /* basis vector for polynomial evaluation */
517 dlat = pos[0] * R2D - gridindex[0]->pos[0];
518 dlon = pos[1] * R2D - gridindex[0]->pos[1];
519 grid->Emat[0] = dlat;
520 grid->Emat[1] = dlon;
521 grid->Emat[2] = dlat * dlon;
522 grid->Emat[3] = 1.0;
523
524 grid->network = (grid->network == 0 ? gridindex[0]->network : grid->network);
525 for (i = 0; i < n; i++) {
526 grid->index[i] = gridindex[i]->index;
527 }
528 grid->num = n;
529 return n;
530}
531
532/*============================================================================
533 * STEC Data Retrieval and Interpolation (from stec.c / grid.c)
534 *===========================================================================*/
535
536/* search STEC data for a single satellite at one grid point -----------------*/
537static int stec_data(stec_t* stec, gtime_t time, int sat, double* iono, double* rate, double* rms, double* quality,
538 int* slip) {
539 int k, flag;
540 double tt;
541
542 if (stec->n <= 0) {
543 return 0;
544 }
545
546 /* search by satellite */
547 for (k = flag = 0; k < stec->n; k++) {
548 if (sat == stec->data[k].sat) {
549 flag = 1;
550 break;
551 }
552 }
553 if (flag == 0) {
554 trace(NULL, 3, "search_data: no iono (no sat data) %s sat=%2d\n", time_str(time, 0), sat);
555 return 0;
556 }
557
558 tt = timediff(time, stec->data[k].time);
559 if (fabs(tt) > MAXAGESSR_IONO) {
560 trace(NULL, 2, "age of ssr iono error %s sat=%2d tt=%.0f\n", time_str(time, 0), sat, tt);
561 return 0;
562 }
563 *iono = stec->data[k].iono + stec->data[k].rate * tt;
564 *rate = stec->data[k].rate;
565 *rms = stec->data[k].rms;
566 *slip = stec->data[k].slip;
567 *quality = stec->data[k].quality;
568
569 return 1;
570}
571
572/* interpolate STEC (ionosphere delay) from grid points ----------------------
573 *
574 * args: clas_corr_t *corr I merged correction snapshot
575 * int *index I selected grid point indices
576 * gtime_t time I time (GPST)
577 * int sat I satellite number
578 * int n I number of grids (1-4)
579 * double *weight I interpolation weights
580 * double *Gmat I interpolation matrix (4×4, can be NULL)
581 * double *Emat I basis vector (4×1, can be NULL)
582 * double *iono O interpolated L1 ionosphere delay (m)
583 * double *rate O interpolated ionosphere rate (m/s)
584 * double *var O interpolated variance (m^2)
585 * int *brk O slip break flag
586 * return: 1=ok, 0=error
587 *---------------------------------------------------------------------------*/
588extern int clas_stec_grid_data(clas_corr_t* corr, const int* index, gtime_t time, int sat, int n, const double* weight,
589 const double* Gmat, const double* Emat, double* iono, double* rate, double* var,
590 int* brk) {
591 int i, slip;
592 double *ionos, *rates, *rms, *quals;
593 double *ionos_, *rates_, *sqrms, *sqrms_, *quals_;
594
595 if (n <= 0) {
596 return 0;
597 }
598
599 ionos = mat(n, 1);
600 rates = mat(n, 1);
601 rms = mat(n, 1);
602 quals = mat(n, 1);
603 ionos_ = mat(n, 1);
604 rates_ = mat(n, 1);
605 sqrms = mat(n, 1);
606 sqrms_ = mat(n, 1);
607 quals_ = mat(n, 1);
608
609 if (n == 1) {
610 if (!stec_data(&corr->stec[index[0]], time, sat, iono, rate, rms, quals, &slip)) {
611 free(ionos);
612 free(rates);
613 free(rms);
614 free(quals);
615 free(ionos_);
616 free(rates_);
617 free(sqrms);
618 free(sqrms_);
619 free(quals_);
620 return 0;
621 }
622 if (slip) {
623 *brk = 1;
624 }
625 *var = SQR(rms[0]);
626 free(ionos);
627 free(rates);
628 free(rms);
629 free(quals);
630 free(ionos_);
631 free(rates_);
632 free(sqrms);
633 free(sqrms_);
634 free(quals_);
635 return 1;
636 }
637
638 /* fetch STEC at each grid point */
639 for (i = 0; i < n; i++) {
640 if (!stec_data(&corr->stec[index[i]], time, sat, ionos + i, rates + i, rms + i, quals + i, &slip)) {
641 free(ionos);
642 free(rates);
643 free(rms);
644 free(quals);
645 free(ionos_);
646 free(rates_);
647 free(sqrms);
648 free(sqrms_);
649 free(quals_);
650 return 0;
651 }
652 if (slip) {
653 *brk = 1;
654 }
655 }
656
657 *iono = *rate = *var = 0.0;
658
659 if (n == 4 && Gmat && Emat) {
660 /* bilinear polynomial interpolation */
661 for (i = 0; i < n; i++) {
662 sqrms[i] = SQR(rms[i]);
663 }
664 matmul("NN", n, 1, n, 1.0, Gmat, ionos, 0.0, ionos_);
665 matmul("NN", n, 1, n, 1.0, Gmat, rates, 0.0, rates_);
666 matmul("NN", n, 1, n, 1.0, Gmat, sqrms, 0.0, sqrms_);
667 matmul("NN", n, 1, n, 1.0, Gmat, quals, 0.0, quals_);
668 *iono = dot(Emat, ionos_, 4);
669 *rate = dot(Emat, rates_, 4);
670 *var = dot(Emat, sqrms_, 4);
671 } else {
672 /* weighted average */
673 for (i = 0; i < n; i++) {
674 *iono += ionos[i] * weight[i];
675 *rate += rates[i] * weight[i];
676 *var += SQR(rms[i]) * weight[i];
677 }
678 }
679
680 free(ionos);
681 free(rates);
682 free(rms);
683 free(quals);
684 free(ionos_);
685 free(rates_);
686 free(sqrms);
687 free(sqrms_);
688 free(quals_);
689
690 return 1;
691}
692
693/*============================================================================
694 * Troposphere Data Retrieval and Interpolation (from grid.c)
695 *===========================================================================*/
696
697/* fetch troposphere delay at a single grid point ----------------------------*/
698static int trop_data(zwd_t* z, gtime_t time, double* ztd, double* zwd, double* quality, int idx, int* valid) {
699 double tt;
700 int k = 0;
701 double tdvd, twvd, gh, pos[2];
702
703 trace(NULL, 4, "trop_data: %s\n", time_str(time, 0));
704
705 if (z->n <= 0) {
706 return 0;
707 }
708
709 tt = timediff(time, z->data[k].time);
710 if (fabs(tt) > MAXAGESSR_TROP) {
711 trace(NULL, 2, "age of ssr trop error %s tt=%.0f idx=%d\n", time_str(time, 0), tt, idx);
712 return 0;
713 }
714
715 pos[0] = z->pos[0] * D2R;
716 pos[1] = z->pos[1] * D2R;
717 gh = geoidh(pos);
718 if (get_stTv(time, pos[0], 0.0, gh, &tdvd, &twvd)) {
719 return 0;
720 }
721
722 if (z->data[k].ztd == CSSR_INVALID_VALUE || z->data[k].zwd == CSSR_INVALID_VALUE) {
723 trace(NULL, 2, "invalid trop correction %s ztd=%.2f zwd=%.2f\n", time_str(time, 0), z->data[k].ztd,
724 z->data[k].zwd);
725 return 0;
726 }
727 *ztd = (z->data[k].ztd - z->data[k].zwd) / tdvd;
728 *zwd = z->data[k].zwd / twvd;
729 *quality = z->data[k].quality;
730 *valid = z->data[k].valid;
731
732 return 1;
733}
734
735/* interpolate troposphere delay from grid points ----------------------------
736 *
737 * args: clas_corr_t *corr I merged correction snapshot
738 * int *index I selected grid point indices
739 * gtime_t time I time (GPST)
740 * int n I number of grids (1-4)
741 * double *weight I interpolation weights
742 * double *Gmat I interpolation matrix (4×4, can be NULL)
743 * double *Emat I basis vector (4×1, can be NULL)
744 * double *zwd O interpolated zenith wet delay
745 * double *ztd O interpolated zenith total delay
746 * int *tbrk O validity flag (0=all valid, 1=partial)
747 * return: 1=ok, 0=error
748 *---------------------------------------------------------------------------*/
749extern int clas_trop_grid_data(clas_corr_t* corr, const int* index, gtime_t time, int n, const double* weight,
750 const double* Gmat, const double* Emat, double* zwd, double* ztd, int* tbrk) {
751 int i, *valid, valid_ = 0, ret = 0;
752 double *zwds, *ztds, *zwds_, *ztds_, *quals, *quals_;
753
754 if (n <= 0) {
755 return 0;
756 }
757
758 zwds = mat(n, 1);
759 ztds = mat(n, 1);
760 quals = mat(n, 1);
761 valid = imat(n, 1);
762
763 if (n == 1) {
764 ret = trop_data(&corr->zwd[index[0]], time, ztd, zwd, quals, index[0], valid);
765 free(zwds);
766 free(ztds);
767 free(quals);
768 free(valid);
769 return ret;
770 }
771
772 /* fetch trop at each grid point */
773 for (i = 0; i < n; i++) {
774 if (!trop_data(&corr->zwd[index[i]], time, ztds + i, zwds + i, quals + i, index[i], valid + i)) {
775 free(zwds);
776 free(ztds);
777 free(quals);
778 free(valid);
779 return 0;
780 }
781 }
782
783 *zwd = *ztd = 0.0;
784
785 if (n == 4 && Gmat && Emat) {
786 /* bilinear polynomial interpolation */
787 zwds_ = mat(n, 1);
788 ztds_ = mat(n, 1);
789 quals_ = mat(n, 1);
790 matmul("NN", n, 1, n, 1.0, Gmat, ztds, 0.0, ztds_);
791 matmul("NN", n, 1, n, 1.0, Gmat, zwds, 0.0, zwds_);
792 matmul("NN", n, 1, n, 1.0, Gmat, quals, 0.0, quals_);
793 *ztd = dot(Emat, ztds_, 4);
794 *zwd = dot(Emat, zwds_, 4);
795 free(zwds_);
796 free(ztds_);
797 free(quals_);
798 } else {
799 /* weighted average */
800 for (i = 0; i < n; i++) {
801 *zwd += zwds[i] * weight[i];
802 *ztd += ztds[i] * weight[i];
803 }
804 }
805
806 for (i = 0; i < n; i++) {
807 valid_ += valid[i];
808 }
809 *tbrk = valid_ == n ? 0 : 1;
810
811 free(zwds);
812 free(ztds);
813 free(quals);
814 free(valid);
815
816 return 1;
817}
818
819/*============================================================================
820 * Ocean Loading Initialization
821 *===========================================================================*/
822
823/* initialize ocean loading data in context ----------------------------------*/
824extern void clas_init_oload(clas_ctx_t* ctx) {
825 int i;
826 for (i = 0; i < CLAS_MAX_NETWORK; i++) {
827 ctx->oload[i].gridnum = 0;
828 }
829}
830/* read blq ocean tide loading parameters for CLAS grid ----------------------
831 * read blq ocean tide loading parameters for each grid point
832 * args : char *file I BLQ ocean tide loading parameter file
833 * clas_ctx_t *ctx IO CLAS context (oload[] populated)
834 * return : status (1:ok, 0:file open error)
835 * notes : file format: "net-gridno" header, 3 comment lines (last has lon/lat),
836 * then 6 lines x 11 values (standard BLQ record)
837 * ported from upstream claslib rtkcmn.c:readblqgrid()
838 *----------------------------------------------------------------------------*/
839extern int readblqgrid(const char* file, clas_ctx_t* ctx) {
840 FILE* fp;
841 char buff[256], *str, *str2;
842 int net, gridno, i;
843
844 trace(NULL, 3, "readblqgrid: file=%s\n", file);
845
846 if (!(fp = fopen(file, "r"))) {
847 trace(NULL, 2, "blq grid file open error: file=%s\n", file);
848 return 0;
849 }
850 while (fgets(buff, sizeof(buff), fp)) {
851 if (!strncmp(buff, "$$", 2) || strlen(buff) < 2) {
852 continue;
853 }
854
855 /* parse "net-gridno" format (e.g., "1-1", "10-23") */
856 str = strtok(buff, "-");
857 if (!str) {
858 continue;
859 }
860 net = atoi(str);
861 str = strtok(NULL, "-");
862 if (!str) {
863 continue;
864 }
865 gridno = atoi(str);
866
867 if (net < 1 || net > CLAS_MAX_NETWORK || gridno < 1 || gridno > CLAS_MAX_GP) {
868 trace(NULL, 1, "readblqgrid: net/grid overflow [%d-%d]\n", net, gridno);
869 continue;
870 }
871
872 /* skip 3 comment/header lines; the 3rd contains lon/lat */
873 for (i = 0; i < 3; i++) {
874 if (!fgets(buff, sizeof(buff), fp)) {
875 break;
876 }
877 }
878 if (i < 3) {
879 break;
880 }
881
882 /* parse lon/lat from last comment line:
883 * "$$ 1-1, ... lon/lat: 125.3700 24.7500 0.000" */
884 str = strtok(buff, ":");
885 str = strtok(NULL, ":");
886 if (str) {
887 str2 = strtok(str, " ");
888 if (str2) {
889 ctx->oload[net - 1].pos[gridno - 1][0] = atof(str2);
890 }
891 str2 = strtok(NULL, " ");
892 if (str2) {
893 ctx->oload[net - 1].pos[gridno - 1][1] = atof(str2);
894 }
895 }
896
897 /* read 6-line BLQ record (11 constituents x 6 components) */
898 if (!readblqrecord(fp, ctx->oload[net - 1].odisp[gridno - 1])) {
899 trace(NULL, 1, "readblqgrid: format error at net=%d grid=%d\n", net, gridno);
900 fclose(fp);
901 return 0;
902 }
903 ctx->oload[net - 1].gridnum++;
904 }
905 fclose(fp);
906
907 trace(NULL, 3, "readblqgrid: loaded grid OTL data\n");
908 return 1;
909}
CLAS (QZSS L6D) CSSR decoder and grid correction type definitions.
#define CLAS_CH_NUM
Definition mrtk_clas.h:185
#define CLAS_MAX_NETWORK
Definition mrtk_clas.h:186
#define CSSR_INVALID_VALUE
Definition mrtk_clas.h:144
#define CLAS_MAX_GP
Definition mrtk_clas.h:188
static void calc_3points_weight(double *weight, const double *pos, grid_info **gridindex)
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.
static int trop_data(zwd_t *z, gtime_t time, double *ztd, double *zwd, double *quality, int idx, int *valid)
void clas_init_oload(clas_ctx_t *ctx)
Initialize ocean loading data in context.
static int find_grid_surround_pos(grid_info **gridindex, int n, grid_info *gridinfo)
static int pickup_candidates_of_grid(const clas_ctx_t *ctx, grid_info *gridinfo, const int network, const double *pos, int ch)
#define MIN(x, y)
static double get_Twv(const double Ts, const double es)
#define MAXAGESSR_TROP
static void output_selected_grid(const double *pos, int n, grid_info **gridindex, double *weight)
static double get_Tdv(const double lat, const double Hs, const double Ps)
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.
#define SQR(x)
static int stec_data(stec_t *stec, gtime_t time, int sat, double *iono, double *rate, double *rms, double *quality, int *slip)
int readblqgrid(const char *file, clas_ctx_t *ctx)
Read BLQ ocean tide loading parameters for CLAS grid points.
#define MAX(x, y)
#define MAX_GRID_CACHE
static int get_mops(const double lat, const double doy, double *pre, double *temp, double *wpre, double *ltemp, double *wvap)
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.
#define MAX_DIST
static double Interp1(const double *x, const double *y, int nx, int ny, double xi)
static void calc_grid_weight(double *weight, const double *pos, int n, grid_info **gridindex)
static int is_exists_inside(const double *pos, int n, grid_info **gridindex, grid_info *nearestgrid)
int get_stTv(gtime_t t, const double lat, const double hs, const double hg, double *tdv, double *twv)
#define MAXAGESSR_IONO
MRTKLIB Constants — Physical, navigation, and system constants.
#define D2R
Definition mrtk_coords.c:36
#define PI
Definition mrtk_coords.c:35
#define RE_WGS84
Definition mrtk_coords.c:38
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
int matinv(double *A, int n)
Inverse of matrix (A = A^-1).
Definition mrtk_mat.c:421
void matcpy(double *A, const double *B, int n, int m)
Copy matrix.
Definition mrtk_mat.c:205
int * imat(int n, int m)
Allocate memory for an integer matrix.
Definition mrtk_mat.c:92
double norm(const double *a, int n)
Euclidean norm of vector.
Definition mrtk_mat.c:165
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.
#define R2D
Definition mrtk_obs.c:51
int readblqrecord(FILE *fp, double *odisp)
Read a single BLQ record (6 lines x 11 values).
MRTKLIB Station Module — Site environment parameter I/O.
double timediff(gtime_t t1, gtime_t t2)
Compute time difference (t1 - t2).
Definition mrtk_time.c:279
double time2doy(gtime_t t)
Convert time to day of year.
Definition mrtk_time.c:540
char * time_str(gtime_t t, int n)
Get time string (not reentrant).
Definition mrtk_time.c:530
MRTKLIB Time Module — GNSS time types and conversion functions.
void tracemat(mrtk_ctx_t *ctx, int level, const double *A, int n, int m, int p, int q)
Output trace matrix.
Definition mrtk_trace.c:106
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
zwd_t zwd[80]
Definition mrtk_clas.h:337
double grid_pos[13][80][3]
Definition mrtk_clas.h:596
clas_oload_t oload[13]
Definition mrtk_clas.h:602
int grid_stat[2][13][80]
Definition mrtk_clas.h:599
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
double odisp[80][6 *11]
Definition mrtk_clas.h:244
double pos[80][2]
Definition mrtk_clas.h:243
double pos[3]
double weight
GNSS time representation.
Definition mrtk_time.h:49
int n
Definition mrtk_clas.h:214
stecd_t * data
Definition mrtk_clas.h:215
float iono
Definition mrtk_clas.h:205
float rate
Definition mrtk_clas.h:206
float quality
Definition mrtk_clas.h:207
float rms
Definition mrtk_clas.h:208
unsigned char slip
Definition mrtk_clas.h:204
gtime_t time
Definition mrtk_clas.h:202
unsigned char sat
Definition mrtk_clas.h:203
float pos[3]
Definition mrtk_clas.h:232
zwdd_t * data
Definition mrtk_clas.h:234
int n
Definition mrtk_clas.h:233
float quality
Definition mrtk_clas.h:228
gtime_t time
Definition mrtk_clas.h:223
unsigned char valid
Definition mrtk_clas.h:224
float ztd
Definition mrtk_clas.h:226
float zwd
Definition mrtk_clas.h:225
#define free(x)
Definition toml.c:53