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