/*! * \file sbas_ionospheric_correction.cc * \brief Implementation of the SBAS ionosphere correction set based on SBAS RTKLIB functions * \author Daniel Fehr 2013. daniel.co(at)bluewin.ch * * ------------------------------------------------------------------------- * * Copyright (C) 2010-2015 (see AUTHORS file for a list of contributors) * * GNSS-SDR is a software defined Global Navigation * Satellite Systems receiver * * This file is part of GNSS-SDR. * * GNSS-SDR is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GNSS-SDR is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNSS-SDR. If not, see . * * ------------------------------------------------------------------------- */ #include "sbas_ionospheric_correction.h" #include #include #include #include #include #include #include enum V_Log_Level {EVENT = 2, // logs important events which don't occur every update() call FLOW = 3, // logs the function calls of block processing functions MORE = 4}; // very detailed stuff void Sbas_Ionosphere_Correction::print(std::ostream &out) { for(std::vector::const_iterator it_band = d_bands.begin(); it_band != d_bands.end(); ++it_band) { int band = it_band - d_bands.begin(); out << "<> Band" << band << ":" << std::endl; for(std::vector::const_iterator it_igp = it_band->d_igps.begin(); it_igp != it_band->d_igps.end(); ++it_igp) { int igp = it_igp-it_band->d_igps.begin(); out << "<> -IGP" << igp << ":"; //std::cout << " valid=" << it_igp->d_valid; out << " t0=" << it_igp->t0; out << " lat=" << it_igp->d_latitude; out << " lon=" << it_igp->d_longitude; out << " give=" << it_igp->d_give; out << " delay=" << it_igp->d_delay; out << std::endl; } } } /* Applies SBAS ionosphric delay correction * \param[out] delay Slant ionospheric delay (L1) (m) * \param[out] var Variance of ionospheric delay (m^2) * \param[in] sample_stamp Sample stamp of observable on which the correction will be applied * \param[in] longitude_d Receiver's longitude in terms of WGS84 (degree) * \param[in] latitude_d Receiver's latitude in terms of WGS84 (degree) * \param[in] azimuth_d Satellite azimuth/elavation angle (rad). Azimuth is the angle of * the satellite from the userŐs location measured clockwise from north * \param[in] elevation_d Elevation is the angle of the satellite from the user's location measured * with respect to the local-tangent-plane */ bool Sbas_Ionosphere_Correction::apply(double sample_stamp, double latitude_d, double longitude_d, double azimut_d, double elevation_d, double &delay, double &var) { const double GPS_PI = 3.1415926535898; //!< Pi as defined in IS-GPS-200E int result; double pos[3]; double azel[2]; // convert receiver position from degrees to rad pos[0] = latitude_d * GPS_PI / 180.0; pos[1] = longitude_d * GPS_PI / 180.0; pos[2] = 0; // is not used by sbsioncorr, for ionocorrection is a fixed earth radius assumed // convert satellite azimut and elevation from degrees to rad , use topocent to obtain it in pvt block azel[0] = azimut_d * GPS_PI / 180.0; azel[1] = elevation_d * GPS_PI / 180.0; result = sbsioncorr(sample_stamp, pos, azel, &delay, &var); return (bool)result; } /* geometric distance ---------------------------------------------------------- * compute geometric distance and receiver-to-satellite unit vector * args : double *rs I satellilte position (ecef at transmission) (m) * double *rr I receiver position (ecef at reception) (m) * double *e O line-of-sight vector (ecef) * return : geometric distance (m) (0>:error/no satellite position) * notes : distance includes sagnac effect correction *-----------------------------------------------------------------------------*/ //extern double geodist(const double *rs, const double *rr, double *e) //{ // double r; // int i; // // if (norm(rs,3)= 0) c += a[n]*b[n]; return c; } /* multiply matrix -----------------------------------------------------------*/ void Sbas_Ionosphere_Correction::matmul(const char *tr, int n, int k, int m, double alpha, const double *A, const double *B, double beta, double *C) { double d; int i, j, x, f = tr[0] == 'N' ? (tr[1] == 'N' ? 1 : 2) : (tr[1] == 'N' ? 3 : 4); for (i = 0; i < n; i++) for (j = 0; j < k; j++) { d = 0.0; switch (f) { case 1: for (x = 0; x < m; x++) d += A[i + x*n]*B[x + j*m]; break; case 2: for (x = 0; x < m; x++) d += A[i + x*n]*B[j + x*k]; break; case 3: for (x = 0; x < m; x++) d += A[x + i*m]*B[x + j*m]; break; case 4: for (x = 0; x < m; x++) d += A[x + i*m]*B[j + x*k]; break; } if (beta == 0.0) { C[i + j*n] = alpha*d; } else { C[i + j*n] = alpha*d + beta*C[i + j*n]; } } } /* ecef to local coordinate transfromation matrix ------------------------------ * compute ecef to local coordinate transfromation matrix * args : double *pos I geodetic position {lat,lon} (rad) * double *E O ecef to local coord transformation matrix (3x3) * return : none * notes : matrix stored by column-major order (fortran convention) *-----------------------------------------------------------------------------*/ void Sbas_Ionosphere_Correction::xyz2enu(const double *pos, double *E) { double sinp = sin(pos[0]), cosp = cos(pos[0]), sinl = sin(pos[1]), cosl = cos(pos[1]); E[0] = -sinl; E[3] = cosl; E[6] = 0.0; E[1] = -sinp*cosl; E[4] = -sinp*sinl; E[7] = cosp; E[2] = cosp*cosl; E[5] = cosp*sinl; E[8] = sinp; } /* transform ecef vector to local tangential coordinate ------------------------- * transform ecef vector to local tangential coordinate * args : double *pos I geodetic position {lat,lon} (rad) * double *r I vector in ecef coordinate {x,y,z} * double *e O vector in local tangental coordinate {e,n,u} * return : none *-----------------------------------------------------------------------------*/ void Sbas_Ionosphere_Correction::ecef2enu(const double *pos, const double *r, double *e) { double E[9]; xyz2enu(pos, E); matmul("NN", 3, 1, 3, 1.0, E, r, 0.0, e); } const double PI = 3.1415926535897932; /* pi */ /* satellite azimuth/elevation angle ------------------------------------------- * compute satellite azimuth/elevation angle * args : double *pos I geodetic position {lat,lon,h} (rad,m) * double *e I receiver-to-satellilte unit vevtor (ecef) * double *azel IO azimuth/elevation {az,el} (rad) (NULL: no output) * (0.0<=azel[0]<2*pi,-pi/2<=azel[1]<=pi/2) * return : elevation angle (rad) *-----------------------------------------------------------------------------*/ double Sbas_Ionosphere_Correction::satazel(const double *pos, const double *e, double *azel) { const double RE_WGS84 = 6378137.0; /* earth semimajor axis (WGS84) (m) */ double az = 0.0, el = PI/2.0, enu[3]; if (pos[2] > -RE_WGS84) { ecef2enu(pos, e, enu); az = dot(enu, enu, 2) < 1E-12 ? 0.0 : atan2(enu[0], enu[1]); if (az < 0.0) az += 2*PI; el = asin(enu[2]); } if (azel) { azel[0] = az; azel[1] = el; } return el; } /* debug trace function -----------------------------------------------------*/ void Sbas_Ionosphere_Correction::trace(int level, const char *format, ...) { va_list ap; char str[1000]; va_start(ap,format); vsprintf(str,format,ap); va_end(ap); VLOG(FLOW) << "<> " << std::string(str); } /* ionospheric pierce point position ------------------------------------------- * compute ionospheric pierce point (ipp) position and slant factor * args : double *pos I receiver position {lat,lon,h} (rad,m) * double *azel I azimuth/elevation angle {az,el} (rad) * double re I earth radius (km) * double hion I altitude of ionosphere (km) * double *posp O pierce point position {lat,lon,h} (rad,m) * return : slant factor * notes : see ref [2], only valid on the earth surface * fixing bug on ref [2] A.4.4.10.1 A-22,23 *-----------------------------------------------------------------------------*/ double Sbas_Ionosphere_Correction::ionppp(const double *pos, const double *azel, double re, double hion, double *posp) { double cosaz, rp, ap, sinap, tanap; const double D2R = (PI/180.0); /* deg to rad */ rp = re/(re + hion)*cos(azel[1]); ap = PI/2.0 - azel[1] - asin(rp); sinap = sin(ap); tanap = tan(ap); cosaz = cos(azel[0]); posp[0] = asin(sin(pos[0])*cos(ap) + cos(pos[0])*sinap*cosaz); if ((pos[0] > 70.0*D2R && tanap*cosaz > tan(PI/2.0 - pos[0])) || (pos[0] < -70.0*D2R && - tanap*cosaz > tan(PI/2.0 + pos[0]))) { posp[1] = pos[1] + PI - asin(sinap*sin(azel[0])/cos(posp[0])); } else { posp[1] = pos[1] + asin(sinap*sin(azel[0])/cos(posp[0])); } return 1.0 / sqrt(1.0 - rp*rp); } /* variance of ionosphere correction (give=GIVEI) --------------------------*/ double Sbas_Ionosphere_Correction::varicorr(int give) { const double var[15]={ 0.0084, 0.0333, 0.0749, 0.1331, 0.2079, 0.2994, 0.4075, 0.5322, 0.6735, 0.8315, 1.1974, 1.8709, 3.326, 20.787, 187.0826 }; return 0 <= give && give < 15 ? var[give]:0.0; } /* search igps ---------------------------------------------------------------*/ void Sbas_Ionosphere_Correction::searchigp(const double *pos, const Igp **igp, double *x, double *y) { int i; int latp[2]; int lonp[4]; const double R2D = (180.0/PI); /* rad to deg */ double lat = pos[0]*R2D; double lon = pos[1]*R2D; trace(4,"searchigp: pos=%.3f %.3f",pos[0]*R2D, pos[1]*R2D); // round the pierce point position to the next IGP grid point if (lon >= 180.0) lon -= 360.0; if (-55.0 <= lat && lat < 55.0) { latp[0] = (int)floor(lat/5.0)*5; latp[1] = latp[0] + 5; lonp[0] = lonp[1] = (int)floor(lon/5.0)*5; lonp[2] = lonp[3] = lonp[0] + 5; *x = (lon - lonp[0])/5.0; *y = (lat - latp[0])/5.0; } else { latp[0] = (int)floor((lat-5.0)/10.0)*10+5; latp[1] = latp[0] + 10; lonp[0] = lonp[1] = (int)floor(lon/10.0)*10; lonp[2] = lonp[3] = lonp[0] + 10; *x = (lon - lonp[0])/10.0; *y = (lat - latp[0])/10.0; if (75.0 <= lat && lat < 85.0) { lonp[1] = (int)floor(lon/90.0)*90; lonp[3] = lonp[1] + 90; } else if (-85.0 <= lat && lat < -75.0) { lonp[0] = (int)floor((lon - 50.0)/90.0)*90 + 40; lonp[2] = lonp[0] + 90; } else if (lat >= 85.0) { for (i = 0; i < 4; i++) lonp[i] = (int)floor(lon/90.0)*90; } else if (lat <- 85.0) { for (i = 0; i < 4; i++) lonp[i] = (int)floor((lon - 50.0)/90.0)*90 + 40; } } for (i = 0; i < 4; i++) if (lonp[i] == 180) lonp[i] = -180; // find the correction data for the grid points in latp[] and lonp[] // iterate over bands for (std::vector::const_iterator band_it = this->d_bands.begin(); band_it != d_bands.end(); ++band_it) { //VLOG(MORE) << "band=" << band_it-d_bands.begin() << std::endl; // iterate over IGPs in band_it for (std::vector::const_iterator igp_it = band_it->d_igps.begin(); igp_it != band_it->d_igps.end(); ++igp_it) { std::stringstream ss; int give = igp_it->d_give; ss << "IGP: give=" << give; if(give < 15) // test if valid correction data is sent for current IGP { int lat = igp_it->d_latitude; int lon = igp_it->d_longitude; ss << " lat=" << lat << " lon=" << lon; if (lat == latp[0] && lon == lonp[0]) igp[0] = igp_it.base(); else if (lat == latp[1] && lon == lonp[1]) igp[1] = igp_it.base(); else if (lat == latp[0] && lon == lonp[2]) igp[2] = igp_it.base(); else if (lat == latp[1] && lon == lonp[3]) igp[3] = igp_it.base(); } //VLOG(MORE) << ss.str(); } } //VLOG(MORE) << "igp[0:3]={" << igp[0] << "," << igp[1] << "," << igp[2] << "," << igp[3] << "}"; } /* sbas ionospheric delay correction ------------------------------------------- * compute sbas ionosphric delay correction * args : long sample_stamp I sample stamp of observable on which the correction will be applied * sbsion_t *ion I ionospheric correction data (implicit) * double *pos I receiver position {lat,lon,height} (rad/m) * double *azel I satellite azimuth/elavation angle (rad) * double *delay O slant ionospheric delay (L1) (m) * double *var O variance of ionospheric delay (m^2) * return : status (1:ok, 0:no correction) * notes : before calling the function, sbas ionosphere correction parameters * in navigation data (nav->sbsion) must be set by calling * sbsupdatecorr() *-----------------------------------------------------------------------------*/ int Sbas_Ionosphere_Correction::sbsioncorr(const double sample_stamp, const double *pos, const double *azel, double *delay, double *var) { const double re = 6378.1363; const double hion = 350.0; int err = 0; double fp; double posp[2]; double x = 0.0; double y = 0.0; double t; double w[4] = {0}; const Igp *igp[4] = {0}; /* {ws,wn,es,en} */ const double R2D = (180.0/PI); /* rad to deg */ trace(4, "sbsioncorr: pos=%.3f %.3f azel=%.3f %.3f", pos[0]*R2D, pos[1]*R2D, azel[0]*R2D, azel[1]*R2D); *delay = *var = 0.0; if (pos[2] < -100.0 || azel[1] <= 0) return 1; /* ipp (ionospheric pierce point) position */ fp = ionppp(pos, azel, re, hion, posp); /* search igps around ipp */ searchigp(posp, igp, &x, &y); VLOG(FLOW) << "<> SBAS iono correction:" << " igp[0]=" << igp[0] << " igp[1]=" << igp[1] << " igp[2]=" << igp[2] << " igp[3]=" << igp[3] << " x=" << x << " y=" << y; /* weight of igps */ if (igp[0] && igp[1] && igp[2] && igp[3]) { w[0] = (1.0 - x)*(1.0 - y); w[1] = (1.0 - x)*y; w[2] = x*(1.0 - y); w[3] = x*y; } else if (igp[0] && igp[1] && igp[2]) { w[1] = y; w[2] = x; if ((w[0] = 1.0 - w[1] - w[2]) < 0.0) err = 1; } else if (igp[0] && igp[2] && igp[3]) { w[0] = 1.0 - x; w[3] = y; if ((w[2] = 1.0 - w[0] -w[3]) < 0.0) err = 1; } else if (igp[0] && igp[1] && igp[3]) { w[0] = 1.0 - y; w[3] = x; if ((w[1] = 1.0 - w[0] - w[3]) < 0.0) err = 1; } else if (igp[1]&&igp[2]&&igp[3]) { w[1] = 1.0 - x; w[2] = 1.0 - y; if ((w[3] = 1.0 - w[1] - w[2]) < 0.0) err = 1; } else err = 1; if (err) { trace(2, "no sbas iono correction: lat=%3.0f lon=%4.0f", posp[0]*R2D, posp[1]*R2D); return 0; } for (int i = 0; i <4 ; i++) { if (!igp[i]) continue; t = (sample_stamp - igp[i]->t0); // time diff between now and reception of the igp data in seconds *delay += w[i]*igp[i]->d_delay; *var += w[i] * varicorr(igp[i]->d_give) * 9E-8 * fabs(t); } *delay *= fp; *var *= fp*fp; trace(5, "sbsioncorr: dion=%7.2f sig=%7.2f", *delay, sqrt(*var)); return 1; }