MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
mrtk_atmos.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * mrtk_atmos.c : atmosphere model functions
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_atmos.c
18 * @brief MRTKLIB Atmosphere Module — Ionospheric and tropospheric models.
19 *
20 * This file contains all broadcast ionosphere and troposphere model functions
21 * extracted from rtkcmn.c with zero algorithmic changes (pure cut-and-paste).
22 */
23#include "mrtklib/mrtk_atmos.h"
24
25#include <math.h>
26
27#include "mrtklib/mrtk_ionex.h"
28#include "mrtklib/mrtk_sbas.h"
29#include "mrtklib/mrtk_trace.h"
30
31/*============================================================================
32 * Constants (duplicated from rtklib.h to avoid header dependency)
33 *===========================================================================*/
34
35#define PI 3.1415926535897932 /* pi */
36#define D2R (PI / 180.0) /* deg to rad */
37#define R2D (180.0 / PI) /* rad to deg */
38#define CLIGHT 299792458.0 /* speed of light (m/s) */
39#define RE_WGS84 6378137.0 /* earth semimajor axis (WGS84) (m) */
40#define HION 350000.0 /* ionosphere height (m) */
41
42/*============================================================================
43 * Forward declarations for legacy functions (resolved at link time)
44 *===========================================================================*/
45
46#ifdef IERS_MODEL
47extern int gmf_(double* mjd, double* lat, double* lon, double* hgt, double* zd, double* gmfh, double* gmfw);
48extern double geoidh(const double* pos);
49#endif
50
51/*============================================================================
52 * Ionosphere Models
53 *===========================================================================*/
54
55/* ionosphere model ------------------------------------------------------------
56 * compute ionospheric delay by broadcast ionosphere model (klobuchar model)
57 * args : gtime_t t I time (gpst)
58 * double *ion I iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3}
59 * double *pos I receiver position {lat,lon,h} (rad,m)
60 * double *azel I azimuth/elevation angle {az,el} (rad)
61 * return : ionospheric delay (L1) (m)
62 *-----------------------------------------------------------------------------*/
63extern double ionmodel(gtime_t t, const double* ion, const double* pos, const double* azel) {
64 const double ion_default[] = {
65 /* 2004/1/1 */
66 0.1118E-07, -0.7451E-08, -0.5961E-07, 0.1192E-06, 0.1167E+06, -0.2294E+06, -0.1311E+06, 0.1049E+07};
67 double tt, f, psi, phi, lam, amp, per, x;
68 int week;
69
70 if (pos[2] < -1E3 || azel[1] <= 0) {
71 return 0.0;
72 }
73 if (norm(ion, 8) <= 0.0) {
74 ion = ion_default;
75 }
76
77 /* earth centered angle (semi-circle) */
78 psi = 0.0137 / (azel[1] / PI + 0.11) - 0.022;
79
80 /* subionospheric latitude/longitude (semi-circle) */
81 phi = pos[0] / PI + psi * cos(azel[0]);
82 if (phi > 0.416) {
83 phi = 0.416;
84 } else if (phi < -0.416) {
85 phi = -0.416;
86 }
87 lam = pos[1] / PI + psi * sin(azel[0]) / cos(phi * PI);
88
89 /* geomagnetic latitude (semi-circle) */
90 phi += 0.064 * cos((lam - 1.617) * PI);
91
92 /* local time (s) */
93 tt = 43200.0 * lam + time2gpst(t, &week);
94 tt -= floor(tt / 86400.0) * 86400.0; /* 0<=tt<86400 */
95
96 /* slant factor */
97 f = 1.0 + 16.0 * pow(0.53 - azel[1] / PI, 3.0);
98
99 /* ionospheric delay */
100 amp = ion[0] + phi * (ion[1] + phi * (ion[2] + phi * ion[3]));
101 per = ion[4] + phi * (ion[5] + phi * (ion[6] + phi * ion[7]));
102 amp = amp < 0.0 ? 0.0 : amp;
103 per = per < 72000.0 ? 72000.0 : per;
104 x = 2.0 * PI * (tt - 50400.0) / per;
105
106 return CLIGHT * f * (fabs(x) < 1.57 ? 5E-9 + amp * (1.0 + x * x * (-0.5 + x * x / 24.0)) : 5E-9);
107}
108/* ionosphere mapping function -------------------------------------------------
109 * compute ionospheric delay mapping function by single layer model
110 * args : double *pos I receiver position {lat,lon,h} (rad,m)
111 * double *azel I azimuth/elevation angle {az,el} (rad)
112 * return : ionospheric mapping function
113 *-----------------------------------------------------------------------------*/
114extern double ionmapf(const double* pos, const double* azel) {
115 if (pos[2] >= HION) {
116 return 1.0;
117 }
118 return 1.0 / cos(asin((RE_WGS84 + pos[2]) / (RE_WGS84 + HION) * sin(PI / 2.0 - azel[1])));
119}
120/* ionospheric pierce point position -------------------------------------------
121 * compute ionospheric pierce point (ipp) position and slant factor
122 * args : double *pos I receiver position {lat,lon,h} (rad,m)
123 * double *azel I azimuth/elevation angle {az,el} (rad)
124 * double re I earth radius (km)
125 * double hion I altitude of ionosphere (km)
126 * double *posp O pierce point position {lat,lon,h} (rad,m)
127 * return : slant factor
128 * notes : see ref [2], only valid on the earth surface
129 * fixing bug on ref [2] A.4.4.10.1 A-22,23
130 *-----------------------------------------------------------------------------*/
131extern double ionppp(const double* pos, const double* azel, double re, double hion, double* posp) {
132 double cosaz, rp, ap, sinap, tanap;
133
134 rp = re / (re + hion) * cos(azel[1]);
135 ap = PI / 2.0 - azel[1] - asin(rp);
136 sinap = sin(ap);
137 tanap = tan(ap);
138 cosaz = cos(azel[0]);
139 posp[0] = asin(sin(pos[0]) * cos(ap) + cos(pos[0]) * sinap * cosaz);
140
141 if ((pos[0] > 70.0 * D2R && tanap * cosaz > tan(PI / 2.0 - pos[0])) ||
142 (pos[0] < -70.0 * D2R && -tanap * cosaz > tan(PI / 2.0 + pos[0]))) {
143 posp[1] = pos[1] + PI - asin(sinap * sin(azel[0]) / cos(posp[0]));
144 } else {
145 posp[1] = pos[1] + asin(sinap * sin(azel[0]) / cos(posp[0]));
146 }
147 return 1.0 / sqrt(1.0 - rp * rp);
148}
149
150/*============================================================================
151 * Troposphere Models
152 *===========================================================================*/
153
154/* troposphere model -----------------------------------------------------------
155 * compute tropospheric delay by standard atmosphere and saastamoinen model
156 * args : gtime_t time I time
157 * double *pos I receiver position {lat,lon,h} (rad,m)
158 * double *azel I azimuth/elevation angle {az,el} (rad)
159 * double humi I relative humidity
160 * return : tropospheric delay (m)
161 *-----------------------------------------------------------------------------*/
162extern double tropmodel(gtime_t time, const double* pos, const double* azel, double humi) {
163 const double temp0 = 15.0; /* temparature at sea level */
164 double hgt, pres, temp, e, z, trph, trpw;
165
166 if (pos[2] < -100.0 || 1E4 < pos[2] || azel[1] <= 0) {
167 return 0.0;
168 }
169
170 /* standard atmosphere */
171 hgt = pos[2] < 0.0 ? 0.0 : pos[2];
172
173 pres = 1013.25 * pow(1.0 - 2.2557E-5 * hgt, 5.2568);
174 temp = temp0 - 6.5E-3 * hgt + 273.16;
175 e = 6.108 * humi * exp((17.15 * temp - 4684.0) / (temp - 38.45));
176
177 /* saastamoninen model */
178 z = PI / 2.0 - azel[1];
179 trph = 0.0022768 * pres / (1.0 - 0.00266 * cos(2.0 * pos[0]) - 0.00028 * hgt / 1E3) / cos(z);
180 trpw = 0.002277 * (1255.0 / temp + 0.05) * e / cos(z);
181 return trph + trpw;
182}
183#ifndef IERS_MODEL
184
185static double interpc(const double coef[], double lat) {
186 int i = (int)(lat / 15.0);
187 if (i < 1) {
188 return coef[0];
189 } else if (i > 4) {
190 return coef[4];
191 }
192 return coef[i - 1] * (1.0 - lat / 15.0 + i) + coef[i] * (lat / 15.0 - i);
193}
194static double mapf(double el, double a, double b, double c) {
195 double sinel = sin(el);
196 return (1.0 + a / (1.0 + b / (1.0 + c))) / (sinel + (a / (sinel + b / (sinel + c))));
197}
198static double nmf(gtime_t time, const double pos[], const double azel[], double* mapfw) {
199 /* ref [5] table 3 */
200 /* hydro-ave-a,b,c, hydro-amp-a,b,c, wet-a,b,c at latitude 15,30,45,60,75 */
201 const double coef[][5] = {{1.2769934E-3, 1.2683230E-3, 1.2465397E-3, 1.2196049E-3, 1.2045996E-3},
202 {2.9153695E-3, 2.9152299E-3, 2.9288445E-3, 2.9022565E-3, 2.9024912E-3},
203 {62.610505E-3, 62.837393E-3, 63.721774E-3, 63.824265E-3, 64.258455E-3},
204
205 {0.0000000E-0, 1.2709626E-5, 2.6523662E-5, 3.4000452E-5, 4.1202191E-5},
206 {0.0000000E-0, 2.1414979E-5, 3.0160779E-5, 7.2562722E-5, 11.723375E-5},
207 {0.0000000E-0, 9.0128400E-5, 4.3497037E-5, 84.795348E-5, 170.37206E-5},
208
209 {5.8021897E-4, 5.6794847E-4, 5.8118019E-4, 5.9727542E-4, 6.1641693E-4},
210 {1.4275268E-3, 1.5138625E-3, 1.4572752E-3, 1.5007428E-3, 1.7599082E-3},
211 {4.3472961E-2, 4.6729510E-2, 4.3908931E-2, 4.4626982E-2, 5.4736038E-2}};
212 const double aht[] = {2.53E-5, 5.49E-3, 1.14E-3}; /* height correction */
213
214 double y, cosy, ah[3], aw[3], dm, el = azel[1], lat = pos[0] * R2D, hgt = pos[2];
215 int i;
216
217 if (el <= 0.0) {
218 if (mapfw) {
219 *mapfw = 0.0;
220 }
221 return 0.0;
222 }
223 /* year from doy 28, added half a year for southern latitudes */
224 y = (time2doy(time) - 28.0) / 365.25 + (lat < 0.0 ? 0.5 : 0.0);
225
226 cosy = cos(2.0 * PI * y);
227 lat = fabs(lat);
228
229 for (i = 0; i < 3; i++) {
230 ah[i] = interpc(coef[i], lat) - interpc(coef[i + 3], lat) * cosy;
231 aw[i] = interpc(coef[i + 6], lat);
232 }
233 /* ellipsoidal height is used instead of height above sea level */
234 dm = (1.0 / sin(el) - mapf(el, aht[0], aht[1], aht[2])) * hgt / 1E3;
235
236 if (mapfw) {
237 *mapfw = mapf(el, aw[0], aw[1], aw[2]);
238 }
239
240 return mapf(el, ah[0], ah[1], ah[2]) + dm;
241}
242#endif /* !IERS_MODEL */
243
244/* troposphere mapping function ------------------------------------------------
245 * compute tropospheric mapping function by NMF
246 * args : gtime_t t I time
247 * double *pos I receiver position {lat,lon,h} (rad,m)
248 * double *azel I azimuth/elevation angle {az,el} (rad)
249 * double *mapfw IO wet mapping function (NULL: not output)
250 * return : dry mapping function
251 * note : see ref [5] (NMF) and [9] (GMF)
252 * original JGR paper of [5] has bugs in eq.(4) and (5). the corrected
253 * paper is obtained from:
254 * ftp://web.haystack.edu/pub/aen/nmf/NMF_JGR.pdf
255 *-----------------------------------------------------------------------------*/
256extern double tropmapf(gtime_t time, const double pos[], const double azel[], double* mapfw) {
257#ifdef IERS_MODEL
258 const double ep[] = {2000, 1, 1, 12, 0, 0};
259 double mjd, lat, lon, hgt, zd, gmfh, gmfw;
260#endif
261 trace(NULL, 4, "tropmapf: pos=%10.6f %11.6f %6.1f azel=%5.1f %4.1f\n", pos[0] * R2D, pos[1] * R2D, pos[2],
262 azel[0] * R2D, azel[1] * R2D);
263
264 if (pos[2] < -1000.0 || pos[2] > 20000.0) {
265 if (mapfw) {
266 *mapfw = 0.0;
267 }
268 return 0.0;
269 }
270#ifdef IERS_MODEL
271 mjd = 51544.5 + (timediff(time, epoch2time(ep))) / 86400.0;
272 lat = pos[0];
273 lon = pos[1];
274 hgt = pos[2] - geoidh(pos); /* height in m (mean sea level) */
275 zd = PI / 2.0 - azel[1];
276
277 /* call GMF */
278 gmf_(&mjd, &lat, &lon, &hgt, &zd, &gmfh, &gmfw);
279
280 if (mapfw) *mapfw = gmfw;
281 return gmfh;
282#else
283 return nmf(time, pos, azel, mapfw); /* NMF */
284#endif
285}
286
287/*============================================================================
288 * Ionosphere / Troposphere Correction (multi-model dispatch)
289 *===========================================================================*/
290
291#define SQR(x) ((x) * (x))
292#define ERR_ION 5.0 /* ionospheric delay Std (m) */
293#define ERR_TROP 3.0 /* tropspheric delay Std (m) */
294#define ERR_SAAS 0.3 /* Saastamoinen model error Std (m) */
295#define ERR_BRDCI 0.5 /* broadcast ionosphere model error factor */
296#define REL_HUMI 0.7 /* relative humidity for Saastamoinen model */
297
298/* ionospheric correction ------------------------------------------------------
299 * compute ionospheric correction
300 * args : gtime_t time I time
301 * nav_t *nav I navigation data
302 * int sat I satellite number
303 * double *pos I receiver position {lat,lon,h} (rad|m)
304 * double *azel I azimuth/elevation angle {az,el} (rad)
305 * int ionoopt I ionospheric correction option (IONOOPT_???)
306 * double *ion O ionospheric delay (L1) (m)
307 * double *var O ionospheric delay (L1) variance (m^2)
308 * return : status(1:ok,0:error)
309 *-----------------------------------------------------------------------------*/
310int ionocorr(gtime_t time, const nav_t* nav, int sat, const double* pos, const double* azel, int ionoopt, double* ion,
311 double* var) {
312 trace(NULL, 4, "ionocorr: time=%s opt=%d sat=%2d pos=%.3f %.3f azel=%.3f %.3f\n", time_str(time, 3), ionoopt, sat,
313 pos[0] * R2D, pos[1] * R2D, azel[0] * R2D, azel[1] * R2D);
314
315 /* GPS broadcast ionosphere model */
316 if (ionoopt == IONOOPT_BRDC) {
317 *ion = ionmodel(time, nav->ion_gps, pos, azel);
318 *var = SQR(*ion * ERR_BRDCI);
319 return 1;
320 }
321 /* SBAS ionosphere model */
322 if (ionoopt == IONOOPT_SBAS) {
323 return sbsioncorr(time, nav, pos, azel, ion, var);
324 }
325 /* IONEX TEC model */
326 if (ionoopt == IONOOPT_TEC) {
327 return iontec(time, nav, pos, azel, 1, ion, var);
328 }
329 /* QZSS broadcast ionosphere model */
330 if (ionoopt == IONOOPT_QZS && norm(nav->ion_qzs, 8) > 0.0) {
331 *ion = ionmodel(time, nav->ion_qzs, pos, azel);
332 *var = SQR(*ion * ERR_BRDCI);
333 return 1;
334 }
335 *ion = 0.0;
336 *var = ionoopt == IONOOPT_OFF ? SQR(ERR_ION) : 0.0;
337 return 1;
338}
339/* tropospheric correction -----------------------------------------------------
340 * compute tropospheric correction
341 * args : gtime_t time I time
342 * nav_t *nav I navigation data
343 * double *pos I receiver position {lat,lon,h} (rad|m)
344 * double *azel I azimuth/elevation angle {az,el} (rad)
345 * int tropopt I tropospheric correction option (TROPOPT_???)
346 * double *trp O tropospheric delay (m)
347 * double *var O tropospheric delay variance (m^2)
348 * return : status(1:ok,0:error)
349 *-----------------------------------------------------------------------------*/
350int tropcorr(gtime_t time, const nav_t* nav, const double* pos, const double* azel, int tropopt, double* trp,
351 double* var) {
352 trace(NULL, 4, "tropcorr: time=%s opt=%d pos=%.3f %.3f azel=%.3f %.3f\n", time_str(time, 3), tropopt, pos[0] * R2D,
353 pos[1] * R2D, azel[0] * R2D, azel[1] * R2D);
354
355 /* Saastamoinen model */
356 if (tropopt == TROPOPT_SAAS || tropopt == TROPOPT_EST || tropopt == TROPOPT_ESTG) {
357 *trp = tropmodel(time, pos, azel, REL_HUMI);
358 *var = SQR(ERR_SAAS / (sin(azel[1]) + 0.1));
359 return 1;
360 }
361 /* SBAS (MOPS) troposphere model */
362 if (tropopt == TROPOPT_SBAS) {
363 *trp = sbstropcorr(time, pos, azel, var);
364 return 1;
365 }
366 /* no correction */
367 *trp = 0.0;
368 *var = tropopt == TROPOPT_OFF ? SQR(ERR_TROP) : 0.0;
369 return 1;
370}
double tropmodel(gtime_t time, const double *pos, const double *azel, double humi)
Compute tropospheric delay by standard atmosphere and Saastamoinen.
Definition mrtk_atmos.c:162
#define ERR_ION
Definition mrtk_atmos.c:292
int ionocorr(gtime_t time, const nav_t *nav, int sat, const double *pos, const double *azel, int ionoopt, double *ion, double *var)
Compute ionospheric correction.
Definition mrtk_atmos.c:310
#define ERR_SAAS
Definition mrtk_atmos.c:294
#define REL_HUMI
Definition mrtk_atmos.c:296
static double mapf(double el, double a, double b, double c)
Definition mrtk_atmos.c:194
double ionmodel(gtime_t t, const double *ion, const double *pos, const double *azel)
Compute ionospheric delay by broadcast ionosphere model (Klobuchar).
Definition mrtk_atmos.c:63
#define SQR(x)
Definition mrtk_atmos.c:291
static double interpc(const double coef[], double lat)
Definition mrtk_atmos.c:185
static double nmf(gtime_t time, const double pos[], const double azel[], double *mapfw)
Definition mrtk_atmos.c:198
double tropmapf(gtime_t time, const double pos[], const double azel[], double *mapfw)
Definition mrtk_atmos.c:256
#define ERR_BRDCI
Definition mrtk_atmos.c:295
#define HION
Definition mrtk_atmos.c:40
double ionppp(const double *pos, const double *azel, double re, double hion, double *posp)
Compute ionospheric pierce point (IPP) position and slant factor.
Definition mrtk_atmos.c:131
double ionmapf(const double *pos, const double *azel)
Compute ionospheric delay mapping function by single layer model.
Definition mrtk_atmos.c:114
#define ERR_TROP
Definition mrtk_atmos.c:293
int tropcorr(gtime_t time, const nav_t *nav, const double *pos, const double *azel, int tropopt, double *trp, double *var)
Compute tropospheric correction.
Definition mrtk_atmos.c:350
MRTKLIB Atmosphere Module — Ionospheric and tropospheric models.
#define D2R
Definition mrtk_coords.c:36
#define CLIGHT
Definition mrtk_coords.c:41
#define PI
Definition mrtk_coords.c:35
#define RE_WGS84
Definition mrtk_coords.c:38
struct nav_s nav_t
Definition mrtk_eph.h:40
double geoidh(const double *pos)
Get geoid height from geoid model.
Definition mrtk_geoid.c:268
int iontec(gtime_t time, const nav_t *nav, const double *pos, const double *azel, int opt, double *delay, double *var)
Compute ionospheric delay by TEC grid data.
Definition mrtk_ionex.c:481
MRTKLIB IONEX Module — IONEX ionospheric TEC map I/O and interpolation.
double norm(const double *a, int n)
Euclidean norm of vector.
Definition mrtk_mat.c:165
#define R2D
Definition mrtk_obs.c:51
#define TROPOPT_SBAS
Definition mrtk_opt.h:104
#define TROPOPT_SAAS
Definition mrtk_opt.h:103
#define TROPOPT_OFF
Definition mrtk_opt.h:102
#define IONOOPT_OFF
Definition mrtk_opt.h:92
#define TROPOPT_EST
Definition mrtk_opt.h:105
#define IONOOPT_SBAS
Definition mrtk_opt.h:94
#define IONOOPT_QZS
Definition mrtk_opt.h:98
#define TROPOPT_ESTG
Definition mrtk_opt.h:106
#define IONOOPT_BRDC
Definition mrtk_opt.h:93
#define IONOOPT_TEC
Definition mrtk_opt.h:97
int sbsioncorr(gtime_t time, const nav_t *nav, const double *pos, const double *azel, double *delay, double *var)
SBAS ionospheric delay correction.
Definition mrtk_sbas.c:823
double sbstropcorr(gtime_t time, const double *pos, const double *azel, double *var)
SBAS tropospheric delay correction.
Definition mrtk_sbas.c:930
MRTKLIB SBAS Module — SBAS message decoding and corrections.
gtime_t epoch2time(const double *ep)
Convert calendar day/time to gtime_t struct.
Definition mrtk_time.c:113
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
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
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.
GNSS time representation.
Definition mrtk_time.h:49
double ion_gps[8]
Definition mrtk_nav.h:421
double ion_qzs[8]
Definition mrtk_nav.h:423