1 //============================================================================== 2 // 3 // This file is part of GPSTk, the GPS Toolkit. 4 // 5 // The GPSTk is free software; you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published 7 // by the Free Software Foundation; either version 3.0 of the License, or 8 // any later version. 9 // 10 // The GPSTk is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with GPSTk; if not, write to the Free Software Foundation, 17 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA 18 // 19 // This software was developed by Applied Research Laboratories at the 20 // University of Texas at Austin. 21 // Copyright 2004-2020, The Board of Regents of The University of Texas System 22 // 23 //============================================================================== 24 25 //============================================================================== 26 // 27 // This software was developed by Applied Research Laboratories at the 28 // University of Texas at Austin, under contract to an agency or agencies 29 // within the U.S. Department of Defense. The U.S. Government retains all 30 // rights to use, duplicate, distribute, disclose, or release this software. 31 // 32 // Pursuant to DoD Directive 523024 33 // 34 // DISTRIBUTION STATEMENT A: This software has been approved for public 35 // release, distribution is unlimited. 36 // 37 //============================================================================== 38 39 40 #include "GPSEllipsoid.hpp" 41 #include "GGHeightTropModel.hpp" 42 43 #define THROW_IF_INVALID_DETAILED() {if (!valid) { \ 44 InvalidTropModel e; \ 45 if(!validWeather) e.addText("Invalid trop model: weather"); \ 46 if(!validHeights) e.addText("Invalid trop model: validHeights"); \ 47 if(!validRxHeight) e.addText("Invalid trop model: validRxHeight"); \ 48 GPSTK_THROW(e);}} 49 50 namespace gpstk 51 { 52 // ------------------------------------------------------------------------ 53 // Tropospheric model with heights based on Goad and Goodman(1974), 54 // "A Modified Hopfield Tropospheric Refraction Correction Model," Paper 55 // presented at the Fall Annual Meeting of the American Geophysical Union, 56 // San Francisco, December 1974. 57 // (Not the same as GGTropModel because this has height dependence, 58 // and the computation of this model does not break cleanly into 59 // wet and dry components.) 60 GGHeightTropModel(void)61 GGHeightTropModel::GGHeightTropModel(void) 62 { 63 validWeather = false; //setWeather(20.0,980.0,50.0); 64 validHeights = false; //setHeights(0.0,0.0,0.0); 65 validRxHeight = false; 66 } 67 68 // Creates a trop model from a weather observation 69 // @param wx the weather to use for this correction. GGHeightTropModel(const WxObservation & wx)70 GGHeightTropModel::GGHeightTropModel(const WxObservation& wx) 71 { 72 valid = validRxHeight = validHeights = false; 73 setWeather(wx); 74 } 75 76 // Create a tropospheric model from explicit weather data 77 // @param T temperature in degrees Celsius 78 // @param P atmospheric pressure in millibars 79 // @param H relative humidity in percent GGHeightTropModel(const double & T,const double & P,const double & H)80 GGHeightTropModel::GGHeightTropModel(const double& T, 81 const double& P, 82 const double& H) 83 { 84 validRxHeight = validHeights = false; 85 setWeather(T,P,H); 86 } 87 88 // Create a valid model from explicit input. 89 // @param T temperature in degrees Celsius 90 // @param P atmospheric pressure in millibars 91 // @param H relative humidity in percent 92 // @param hT height at which temperature applies in meters. 93 // @param hP height at which atmospheric pressure applies in meters. 94 // @param hH height at which relative humidity applies in meters. GGHeightTropModel(const double & T,const double & P,const double & H,const double hT,const double hP,const double hH)95 GGHeightTropModel::GGHeightTropModel(const double& T, 96 const double& P, 97 const double& H, 98 const double hT, 99 const double hP, 100 const double hH) 101 { 102 validRxHeight = false; 103 setWeather(T,P,H); 104 setHeights(hT,hP,hH); 105 } 106 107 // re-define this to get the throws correction(double elevation) const108 double GGHeightTropModel::correction(double elevation) const 109 { 110 THROW_IF_INVALID_DETAILED(); 111 112 if(elevation < 0.0) return 0.0; 113 114 return (dry_zenith_delay() * dry_mapping_function(elevation) 115 + wet_zenith_delay() * wet_mapping_function(elevation)); 116 117 } 118 119 // Compute and return the full tropospheric delay, given the positions of 120 // receiver and satellite and the time tag. This version is most useful 121 // within positioning algorithms, where the receiver position and timetag 122 // may vary; it computes the elevation (and other receiver location 123 // information) and passes them to appropriate set...() routines and 124 // the correction(elevation) routine. 125 // @param RX Receiver position 126 // @param SV Satellite position 127 // @param tt Time tag of the signal correction(const Position & RX,const Position & SV,const CommonTime & tt)128 double GGHeightTropModel::correction(const Position& RX, 129 const Position& SV, 130 const CommonTime& tt) 131 { 132 THROW_IF_INVALID_DETAILED(); 133 134 // compute height from RX 135 setReceiverHeight(RX.getHeight()); 136 137 return TropModel::correction(RX.elevation(SV)); 138 139 } // end GGHeightTropModel::correction(RX,SV,TT) 140 141 // Compute and return the full tropospheric delay, given the positions of 142 // receiver and satellite and the time tag. This version is most useful 143 // within positioning algorithms, where the receiver position and timetag 144 // may vary; it computes the elevation (and other receiver location 145 // information) and passes them to appropriate set...() routines and 146 // the correction(elevation) routine. 147 // @param RX Receiver position in ECEF cartesian coordinates (meters) 148 // @param SV Satellite position in ECEF cartesian coordinates (meters) 149 // @param tt Time tag of the signal 150 // This function is deprecated; use the Position version correction(const Xvt & RX,const Xvt & SV,const CommonTime & tt)151 double GGHeightTropModel::correction(const Xvt& RX, 152 const Xvt& SV, 153 const CommonTime& tt) 154 { 155 Position R(RX),S(SV); 156 return GGHeightTropModel::correction(R,S,tt); 157 } 158 159 // Compute and return the zenith delay for dry component of the troposphere dry_zenith_delay(void) const160 double GGHeightTropModel::dry_zenith_delay(void) const 161 { 162 THROW_IF_INVALID_DETAILED(); 163 double hrate=6.5e-3; 164 double Ts=temp+hrate*height; 165 double em=978.77/(2.8704e4*hrate); 166 double Tp=Ts-hrate*hpress; 167 double ps=press*std::pow(Ts/Tp,em)/1000.0; 168 double rs=77.624e-3/Ts; 169 double ho=11.385/rs; 170 rs *= ps; 171 double zen=(ho-height)/ho; 172 zen = rs*zen*zen*zen*zen; 173 // normalize 174 zen *= (ho-height)/5; 175 return zen; 176 177 } 178 179 // Compute and return the zenith delay for wet component of the troposphere wet_zenith_delay(void) const180 double GGHeightTropModel::wet_zenith_delay(void) const 181 { 182 THROW_IF_INVALID_DETAILED(); 183 184 double hrate=6.5e-3; // deg K / m 185 double Th=temp-273.15-hrate*(hhumid-htemp); 186 double Ta=7.5*Th/(237.3+Th); 187 // water vapor partial pressure 188 double e0=6.11e-5*humid*std::pow(10.0,Ta); 189 double Ts=temp+hrate*htemp; 190 double em=978.77/(2.8704e4*hrate); 191 double Tk=Ts-hrate*hhumid; 192 double es=e0*std::pow(Ts/Tk,4.0*em); 193 double rs=(371900.0e-3/Ts-12.92e-3)/Ts; 194 double ho=11.385*(1255/Ts+0.05)/rs; 195 double zen=(ho-height)/ho; 196 zen = rs*es*zen*zen*zen*zen; 197 //normalize 198 zen *= (ho-height)/5; 199 return zen; 200 201 } 202 203 // Compute and return the mapping function for dry component 204 // of the troposphere 205 // @param elevation Elevation of satellite as seen at receiver, 206 // in degrees dry_mapping_function(double elevation) const207 double GGHeightTropModel::dry_mapping_function(double elevation) const 208 { 209 THROW_IF_INVALID_DETAILED(); 210 211 if(elevation < 0.0) return 0.0; 212 213 double hrate=6.5e-3; 214 double Ts=temp+hrate*htemp; 215 double ho=(11.385/77.624e-3)*Ts; 216 double se=std::sin(elevation*DEG_TO_RAD); 217 if(se < 0.0) se=0.0; 218 219 GPSEllipsoid ell; 220 double rt,a,b,rn[8],al[8],er=ell.a(); 221 rt = (er+ho)/(er+height); 222 rt = rt*rt - (1.0-se*se); 223 if(rt < 0) rt=0.0; 224 rt = (er+height)*(SQRT(rt)-se); 225 a = -se/(ho-height); 226 b = -(1.0-se*se)/(2.0*er*(ho-height)); 227 rn[0] = rt*rt; 228 for(int j=1; j<8; j++) rn[j]=rn[j-1]*rt; 229 al[0] = 2*a; 230 al[1] = 2*a*a+4*b/3; 231 al[2] = a*(a*a+3*b); 232 al[3] = a*a*a*a/5+2.4*a*a*b+1.2*b*b; 233 al[4] = 2*a*b*(a*a+3*b)/3; 234 al[5] = b*b*(6*a*a+4*b)*0.1428571; 235 if(b*b > 1.0e-35) { 236 al[6] = a*b*b*b/2; 237 al[7] = b*b*b*b/9; 238 } else { 239 al[6] = 0.0; 240 al[7] = 0.0; 241 } 242 double map=rt; 243 for(int k=0; k<8; k++) map += al[k]*rn[k]; 244 // normalize 245 double norm=(ho-height)/5; 246 return map/norm; 247 } 248 249 // Compute and return the mapping function for wet component 250 // of the troposphere 251 // @param elevation Elevation of satellite as seen at receiver, 252 // in degrees wet_mapping_function(double elevation) const253 double GGHeightTropModel::wet_mapping_function(double elevation) const 254 { 255 THROW_IF_INVALID_DETAILED(); 256 if(elevation < 0.0) return 0.0; 257 258 double hrate=6.5e-3; 259 double Ts=temp+hrate*htemp; 260 double rs=(371900.0e-3/Ts-12.92e-3)/Ts; 261 double ho=11.385*(1255/Ts+0.05)/rs; 262 double se=std::sin(elevation*DEG_TO_RAD); 263 if(se < 0.0) se=0.0; 264 265 GPSEllipsoid ell; 266 double rt,a,b,rn[8],al[8],er=ell.a(); 267 rt = (er+ho)/(er+height); 268 rt = rt*rt - (1.0-se*se); 269 if(rt < 0) rt=0.0; 270 rt = (er+height)*(SQRT(rt)-se); 271 a = -se/(ho-height); 272 b = -(1.0-se*se)/(2.0*er*(ho-height)); 273 rn[0] = rt*rt; 274 for(int i=1; i<8; i++) rn[i]=rn[i-1]*rt; 275 al[0] = 2*a; 276 al[1] = 2*a*a+4*b/3; 277 al[2] = a*(a*a+3*b); 278 al[3] = a*a*a*a/5+2.4*a*a*b+1.2*b*b; 279 al[4] = 2*a*b*(a*a+3*b)/3; 280 al[5] = b*b*(6*a*a+4*b)*0.1428571; 281 if(b*b > 1.0e-35) { 282 al[6] = a*b*b*b/2; 283 al[7] = b*b*b*b/9; 284 } else { 285 al[6] = 0.0; 286 al[7] = 0.0; 287 } 288 double map=rt; 289 for(int j=0; j<8; j++) map += al[j]*rn[j]; 290 // normalize map function 291 double norm=(ho-height)/5; 292 return map/norm; 293 294 } 295 296 // Re-define the weather data. 297 // Typically called just before correction(). 298 // @param T temperature in degrees Celsius 299 // @param P atmospheric pressure in millibars 300 // @param H relative humidity in percent setWeather(const double & T,const double & P,const double & H)301 void GGHeightTropModel::setWeather(const double& T, 302 const double& P, 303 const double& H) 304 { 305 try 306 { 307 TropModel::setWeather(T,P,H); 308 validWeather = true; 309 valid = validWeather && validHeights && validRxHeight; 310 } 311 catch(InvalidParameter& e) 312 { 313 valid = validWeather = false; 314 GPSTK_RETHROW(e); 315 } 316 } 317 318 // Re-define the tropospheric model with explicit weather data. 319 // Typically called just before correction(). 320 // @param wx the weather to use for this correction setWeather(const WxObservation & wx)321 void GGHeightTropModel::setWeather(const WxObservation& wx) 322 { 323 try 324 { 325 TropModel::setWeather(wx); 326 validWeather = true; 327 valid = validWeather && validHeights && validRxHeight; 328 } 329 catch(InvalidParameter& e) 330 { 331 valid = validWeather = false; 332 GPSTK_RETHROW(e); 333 } 334 } 335 336 337 // Re-define the heights at which the weather parameters apply. 338 // Typically called just before correction(). 339 // @param hT height (m) at which temperature applies 340 // @param hP height (m) at which atmospheric pressure applies 341 // @param hH height (m) at which relative humidity applies setHeights(const double & hT,const double & hP,const double & hH)342 void GGHeightTropModel::setHeights(const double& hT, 343 const double& hP, 344 const double& hH) 345 { 346 htemp = hT; // height (m) at which temp applies 347 hpress = hP; // height (m) at which press applies 348 hhumid = hH; // height (m) at which humid applies 349 validHeights = true; 350 valid = validWeather && validHeights && validRxHeight; 351 } 352 353 // Define the receiver height; this required before calling 354 // correction() or any of the zenith_delay or mapping_function routines. setReceiverHeight(const double & ht)355 void GGHeightTropModel::setReceiverHeight(const double& ht) 356 { 357 height = ht; 358 validRxHeight = true; 359 if(!validHeights) { 360 htemp = hpress = hhumid = ht; 361 validHeights = true; 362 } 363 valid = validWeather && validHeights && validRxHeight; 364 } 365 366 } 367