/*!
* \file rtklib_ionex.cc
* \brief ionex functions
* \authors
* - 2007-2013, T. Takasu
*
- 2017, Javier Arribas
*
- 2017, Carles Fernandez
*
*
* This is a derived work from RTKLIB http://www.rtklib.com/
* The original source code at https://github.com/tomojitakasu/RTKLIB is
* released under the BSD 2-clause license with an additional exclusive clause
* that does not apply here. This additional clause is reproduced below:
*
* " The software package includes some companion executive binaries or shared
* libraries necessary to execute APs on Windows. These licenses succeed to the
* original ones of these software. "
*
* Neither the executive binaries nor the shared libraries are required by, used
* or included in GNSS-SDR.
*
* -----------------------------------------------------------------------------
* Copyright (C) 2007-2013, T. Takasu
* Copyright (C) 2017, Javier Arribas
* Copyright (C) 2017, Carles Fernandez
* All rights reserved.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* References:
* [1] S.Schear, W.Gurtner and J.Feltens, IONEX: The IONosphere Map EXchange
* Format Version 1, February 25, 1998
* [2] S.Schaer, R.Markus, B.Gerhard and A.S.Timon, Daily Global Ionosphere
* Maps based on GPS Carrier Phase Data Routinely produced by CODE
* Analysis Center, Proceeding of the IGS Analysis Center Workshop, 1996
*
*----------------------------------------------------------------------------*/
#include "rtklib_ionex.h"
#include "rtklib_rtkcmn.h"
#include
#include
/* get index -----------------------------------------------------------------*/
int getindex(double value, const double *range)
{
if (range[2] == 0.0)
{
return 0;
}
if (range[1] > 0.0 && (value < range[0] || range[1] < value))
{
return -1;
}
if (range[1] < 0.0 && (value < range[1] || range[0] < value))
{
return -1;
}
return static_cast(floor((value - range[0]) / range[2] + 0.5));
}
/* get number of items -------------------------------------------------------*/
int nitem(const double *range)
{
return getindex(range[1], range) + 1;
}
/* data index (i:lat,j:lon,k:hgt) --------------------------------------------*/
int dataindex(int i, int j, int k, const int *ndata)
{
if (i < 0 || ndata[0] <= i || j < 0 || ndata[1] <= j || k < 0 || ndata[2] <= k)
{
return -1;
}
return i + ndata[0] * (j + ndata[1] * k);
}
/* add tec data to navigation data -------------------------------------------*/
tec_t *addtec(const double *lats, const double *lons, const double *hgts,
double rb, nav_t *nav)
{
tec_t *p;
tec_t *nav_tec;
gtime_t time0 = {0, 0};
int i;
int n;
int ndata[3];
trace(3, "addtec :\n");
ndata[0] = nitem(lats);
ndata[1] = nitem(lons);
ndata[2] = nitem(hgts);
if (ndata[0] <= 1 || ndata[1] <= 1 || ndata[2] <= 0)
{
return nullptr;
}
if (nav->nt >= nav->ntmax)
{
nav->ntmax += 256;
if (!(nav_tec = static_cast(realloc(nav->tec, sizeof(tec_t) * nav->ntmax))))
{
trace(1, "readionex malloc error ntmax=%d\n", nav->ntmax);
free(nav->tec);
nav->tec = nullptr;
nav->nt = nav->ntmax = 0;
return nullptr;
}
nav->tec = nav_tec;
}
p = nav->tec + nav->nt;
p->time = time0;
p->rb = rb;
for (i = 0; i < 3; i++)
{
p->ndata[i] = ndata[i];
p->lats[i] = lats[i];
p->lons[i] = lons[i];
p->hgts[i] = hgts[i];
}
n = ndata[0] * ndata[1] * ndata[2];
if (!(p->data = static_cast(malloc(sizeof(double) * n))) ||
!(p->rms = static_cast(malloc(sizeof(float) * n))))
{
return nullptr;
}
for (i = 0; i < n; i++)
{
p->data[i] = 0.0;
p->rms[i] = 0.0F;
}
nav->nt++;
return p;
}
/* read ionex dcb aux data ----------------------------------------------------*/
void readionexdcb(FILE *fp, double *dcb, double *rms)
{
int i;
int sat;
char buff[1024];
char id[32];
char *label;
trace(3, "readionexdcb:\n");
for (i = 0; i < MAXSAT; i++)
{
dcb[i] = rms[i] = 0.0;
}
while (fgets(buff, sizeof(buff), fp))
{
if (strlen(buff) < 60)
{
continue;
}
label = buff + 60;
if (strstr(label, "PRN / BIAS / RMS") == label)
{
int ret = std::snprintf(id, 3, "%s", buff + 3); // NOLINT(runtime/printf)
if (ret >= 0 && ret < 3)
{
if (!(sat = satid2no(id)))
{
trace(2, "ionex invalid satellite: %s\n", id);
continue;
}
dcb[sat - 1] = str2num(buff, 6, 10);
rms[sat - 1] = str2num(buff, 16, 10);
}
}
else if (strstr(label, "END OF AUX DATA") == label)
{
break;
}
}
}
/* read ionex header ---------------------------------------------------------*/
double readionexh(FILE *fp, double *lats, double *lons, double *hgts,
double *rb, double *nexp, double *dcb, double *rms)
{
double ver = 0.0;
char buff[1024];
char *label;
trace(3, "readionexh:\n");
while (fgets(buff, sizeof(buff), fp))
{
if (strlen(buff) < 60)
{
continue;
}
label = buff + 60;
if (strstr(label, "IONEX VERSION / TYPE") == label)
{
if (buff[20] == 'I')
{
ver = str2num(buff, 0, 8);
}
}
else if (strstr(label, "BASE RADIUS") == label)
{
*rb = str2num(buff, 0, 8);
}
else if (strstr(label, "HGT1 / HGT2 / DHGT") == label)
{
hgts[0] = str2num(buff, 2, 6);
hgts[1] = str2num(buff, 8, 6);
hgts[2] = str2num(buff, 14, 6);
}
else if (strstr(label, "LAT1 / LAT2 / DLAT") == label)
{
lats[0] = str2num(buff, 2, 6);
lats[1] = str2num(buff, 8, 6);
lats[2] = str2num(buff, 14, 6);
}
else if (strstr(label, "LON1 / LON2 / DLON") == label)
{
lons[0] = str2num(buff, 2, 6);
lons[1] = str2num(buff, 8, 6);
lons[2] = str2num(buff, 14, 6);
}
else if (strstr(label, "EXPONENT") == label)
{
*nexp = str2num(buff, 0, 6);
}
else if (strstr(label, "START OF AUX DATA") == label &&
strstr(buff, "DIFFERENTIAL CODE BIASES"))
{
readionexdcb(fp, dcb, rms);
}
else if (strstr(label, "END OF HEADER") == label)
{
return ver;
}
}
return 0.0;
}
/* read ionex body -----------------------------------------------------------*/
int readionexb(FILE *fp, const double *lats, const double *lons,
const double *hgts, double rb, double nexp, nav_t *nav)
{
tec_t *p = nullptr;
gtime_t time = {0, 0};
double lat;
double lon[3];
double hgt;
double x;
int i;
int j;
int k;
int n;
int m;
int index;
int type = 0;
char buff[1024];
char *label = buff + 60;
trace(3, "readionexb:\n");
while (fgets(buff, sizeof(buff), fp))
{
if (strlen(buff) < 60)
{
continue;
}
if (strstr(label, "START OF TEC MAP") == label)
{
if ((p = addtec(lats, lons, hgts, rb, nav)))
{
type = 1;
}
}
else if (strstr(label, "END OF TEC MAP") == label)
{
type = 0;
p = nullptr;
}
else if (strstr(label, "START OF RMS MAP") == label)
{
type = 2;
p = nullptr;
}
else if (strstr(label, "END OF RMS MAP") == label)
{
type = 0;
p = nullptr;
}
else if (strstr(label, "EPOCH OF CURRENT MAP") == label)
{
if (str2time(buff, 0, 36, &time))
{
trace(2, "ionex epoch invalid: %-36.36s\n", buff);
continue;
}
if (type == 2)
{
for (i = nav->nt - 1; i >= 0; i--)
{
if (fabs(timediff(time, nav->tec[i].time)) >= 1.0)
{
continue;
}
p = nav->tec + i;
break;
}
}
else if (p)
{
p->time = time;
}
}
else if (strstr(label, "LAT/LON1/LON2/DLON/H") == label && p)
{
lat = str2num(buff, 2, 6);
lon[0] = str2num(buff, 8, 6);
lon[1] = str2num(buff, 14, 6);
lon[2] = str2num(buff, 20, 6);
hgt = str2num(buff, 26, 6);
i = getindex(lat, p->lats);
k = getindex(hgt, p->hgts);
n = nitem(lon);
for (m = 0; m < n; m++)
{
if (m % 16 == 0 && !fgets(buff, sizeof(buff), fp))
{
break;
}
j = getindex(lon[0] + lon[2] * m, p->lons);
if ((index = dataindex(i, j, k, p->ndata)) < 0)
{
continue;
}
if ((x = str2num(buff, m % 16 * 5, 5)) == 9999.0)
{
continue;
}
if (type == 1)
{
p->data[index] = x * std::pow(10.0, nexp);
}
else
{
p->rms[index] = static_cast(x * std::pow(10.0, nexp));
}
}
}
}
return 1;
}
/* combine tec grid data -----------------------------------------------------*/
void combtec(nav_t *nav)
{
tec_t tmp;
int i;
int j;
int n = 0;
trace(3, "combtec : nav->nt=%d\n", nav->nt);
for (i = 0; i < nav->nt - 1; i++)
{
for (j = i + 1; j < nav->nt; j++)
{
if (timediff(nav->tec[j].time, nav->tec[i].time) < 0.0)
{
tmp = nav->tec[i];
nav->tec[i] = nav->tec[j];
nav->tec[j] = tmp;
}
}
}
for (i = 0; i < nav->nt; i++)
{
if (i > 0 && timediff(nav->tec[i].time, nav->tec[n - 1].time) == 0.0)
{
free(nav->tec[n - 1].data);
free(nav->tec[n - 1].rms);
nav->tec[n - 1] = nav->tec[i];
continue;
}
nav->tec[n++] = nav->tec[i];
}
nav->nt = n;
trace(4, "combtec : nav->nt=%d\n", nav->nt);
}
/* read ionex tec grid file ----------------------------------------------------
* read ionex ionospheric tec grid file
* args : char *file I ionex tec grid file
* (wind-card * is expanded)
* nav_t *nav IO navigation data
* nav->nt, nav->ntmax and nav->tec are modified
* int opt I read option (1: no clear of tec data,0:clear)
* return : none
* notes : see ref [1]
*-----------------------------------------------------------------------------*/
void readtec(const char *file, nav_t *nav, int opt)
{
FILE *fp;
double lats[3] = {0};
double lons[3] = {0};
double hgts[3] = {0};
double rb = 0.0;
double nexp = -1.0;
std::vector dcb(MAXSAT, 0);
std::vector rms(MAXSAT, 0);
int i;
int n;
char *efiles[MAXEXFILE];
trace(3, "readtec : file=%s\n", file);
/* clear of tec grid data option */
if (!opt)
{
free(nav->tec);
nav->tec = nullptr;
nav->nt = nav->ntmax = 0;
}
for (i = 0; i < MAXEXFILE; i++)
{
if (!(efiles[i] = static_cast(malloc(MAXSTRPATH + 255))))
{
for (i--; i >= 0; i--)
{
free(efiles[i]);
}
return;
}
}
/* expand wild card in file path */
n = expath(file, efiles, MAXEXFILE);
for (i = 0; i < n; i++)
{
if (!(fp = fopen(efiles[i], "re")))
{
trace(2, "ionex file open error %s\n", efiles[i]);
continue;
}
/* read ionex header */
if (readionexh(fp, lats, lons, hgts, &rb, &nexp, dcb.data(), rms.data()) <= 0.0)
{
trace(2, "ionex file format error %s\n", efiles[i]);
fclose(fp);
continue;
}
/* read ionex body */
readionexb(fp, lats, lons, hgts, rb, nexp, nav);
fclose(fp);
}
for (i = 0; i < MAXEXFILE; i++)
{
free(efiles[i]);
}
/* combine tec grid data */
if (nav->nt > 0)
{
combtec(nav);
}
/* P1-P2 dcb */
for (i = 0; i < MAXSAT; i++)
{
nav->cbias[i][0] = SPEED_OF_LIGHT_M_S * dcb[i] * 1e-9; /* ns->m */
}
}
/* interpolate tec grid data -------------------------------------------------*/
int interptec(const tec_t *tec, int k, const double *posp, double *value,
double *rms)
{
double dlat;
double dlon;
double a;
double b;
double d[4] = {0};
double r[4] = {0};
int i;
int j;
int n;
int index;
trace(3, "interptec: k=%d posp=%.2f %.2f\n", k, posp[0] * R2D, posp[1] * R2D);
*value = *rms = 0.0;
if (tec->lats[2] == 0.0 || tec->lons[2] == 0.0)
{
return 0;
}
dlat = posp[0] * R2D - tec->lats[0];
dlon = posp[1] * R2D - tec->lons[0];
if (tec->lons[2] > 0.0)
{
dlon -= floor(dlon / 360) * 360.0; /* 0 <= dlon<360 */
}
else
{
dlon += floor(-dlon / 360) * 360.0; /* -360lats[2];
b = dlon / tec->lons[2];
i = static_cast(floor(a));
a -= i;
j = static_cast(floor(b));
b -= j;
/* get gridded tec data */
for (n = 0; n < 4; n++)
{
if ((index = dataindex(i + (n % 2), j + (n < 2 ? 0 : 1), k, tec->ndata)) < 0)
{
continue;
}
d[n] = tec->data[index];
r[n] = tec->rms[index];
}
if (d[0] > 0.0 && d[1] > 0.0 && d[2] > 0.0 && d[3] > 0.0)
{
/* bilinear interpolation (inside of grid) */
*value = (1.0 - a) * (1.0 - b) * d[0] + a * (1.0 - b) * d[1] + (1.0 - a) * b * d[2] + a * b * d[3];
*rms = (1.0 - a) * (1.0 - b) * r[0] + a * (1.0 - b) * r[1] + (1.0 - a) * b * r[2] + a * b * r[3];
}
/* nearest-neighbour extrapolation (outside of grid) */
else if (a <= 0.5 && b <= 0.5 && d[0] > 0.0)
{
*value = d[0];
*rms = r[0];
}
else if (a > 0.5 && b <= 0.5 && d[1] > 0.0)
{
*value = d[1];
*rms = r[1];
}
else if (a <= 0.5 && b > 0.5 && d[2] > 0.0)
{
*value = d[2];
*rms = r[2];
}
else if (a > 0.5 && b > 0.5 && d[3] > 0.0)
{
*value = d[3];
*rms = r[3];
}
else
{
i = 0;
for (n = 0; n < 4; n++)
{
if (d[n] > 0.0)
{
i++;
*value += d[n];
*rms += r[n];
}
}
if (i == 0)
{
return 0;
}
*value /= i;
*rms /= i;
}
return 1;
}
/* ionosphere delay by tec grid data -----------------------------------------*/
int iondelay(gtime_t time, const tec_t *tec, const double *pos,
const double *azel, int opt, double *delay, double *var)
{
const double fact = 40.30E16 / FREQ1 / FREQ1; /* tecu->L1 iono (m) */
double fs;
double posp[3] = {0};
double vtec;
double rms;
double hion;
double rp;
int i;
trace(3, "iondelay: time=%s pos=%.1f %.1f azel=%.1f %.1f\n", time_str(time, 0),
pos[0] * R2D, pos[1] * R2D, azel[0] * R2D, azel[1] * R2D);
*delay = *var = 0.0;
for (i = 0; i < tec->ndata[2]; i++)
{ /* for a layer */
hion = tec->hgts[0] + tec->hgts[2] * i;
/* ionospheric pierce point position */
fs = ionppp(pos, azel, tec->rb, hion, posp);
if (opt & 2)
{
/* modified single layer mapping function (M-SLM) ref [2] */
rp = tec->rb / (tec->rb + hion) * sin(0.9782 * (GNSS_PI / 2.0 - azel[1]));
fs = 1.0 / sqrt(1.0 - rp * rp);
}
if (opt & 1)
{
/* earth rotation correction (sun-fixed coordinate) */
posp[1] += 2.0 * GNSS_PI * timediff(time, tec->time) / 86400.0;
}
/* interpolate tec grid data */
if (!interptec(tec, i, posp, &vtec, &rms))
{
return 0;
}
*delay += fact * fs * vtec;
*var += fact * fact * fs * fs * rms * rms;
}
trace(4, "iondelay: delay=%7.2f std=%6.2f\n", *delay, sqrt(*var));
return 1;
}
/* ionosphere model by tec grid data -------------------------------------------
* compute ionospheric delay by tec grid data
* args : gtime_t time I time (gpst)
* nav_t *nav I navigation data
* double *pos I receiver position {lat,lon,h} (rad,m)
* double *azel I azimuth/elevation angle {az,el} (rad)
* int opt I model option
* bit0: 0:earth-fixed,1:sun-fixed
* bit1: 0:single-layer,1:modified single-layer
* double *delay O ionospheric delay (L1) (m)
* double *var O ionospheric dealy (L1) variance (m^2)
* return : status (1:ok,0:error)
* notes : before calling the function, read tec grid data by calling readtec()
* return ok with delay=0 and var=VAR_NOTEC if elnt; i++)
{
if (timediff(nav->tec[i].time, time) > 0.0)
{
break;
}
}
if (i == 0 || i >= nav->nt)
{
trace(2, "%s: tec grid out of period\n", time_str(time, 0));
return 0;
}
if ((tt = timediff(nav->tec[i].time, nav->tec[i - 1].time)) == 0.0)
{
trace(2, "tec grid time interval error\n");
return 0;
}
/* ionospheric delay by tec grid data */
stat[0] = iondelay(time, nav->tec + i - 1, pos, azel, opt, dels, vars);
stat[1] = iondelay(time, nav->tec + i, pos, azel, opt, dels + 1, vars + 1);
if (!stat[0] && !stat[1])
{
trace(2, "%s: tec grid out of area pos=%6.2f %7.2f azel=%6.1f %5.1f\n",
time_str(time, 0), pos[0] * R2D, pos[1] * R2D, azel[0] * R2D, azel[1] * R2D);
return 0;
}
if (stat[0] && stat[1])
{ /* linear interpolation by time */
a = timediff(time, nav->tec[i - 1].time) / tt;
*delay = dels[0] * (1.0 - a) + dels[1] * a;
*var = vars[0] * (1.0 - a) + vars[1] * a;
}
else if (stat[0])
{ /* nearest-neighbour extrapolation by time */
*delay = dels[0];
*var = vars[0];
}
else
{
*delay = dels[1];
*var = vars[1];
}
trace(3, "iontec : delay=%5.2f std=%5.2f\n", *delay, sqrt(*var));
return 1;
}