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 /// @file GloEphemeris.cpp
40 /// Ephemeris data for GLONASS.
41 
42 #include <iomanip>
43 #include "GloEphemeris.hpp"
44 #include "TimeString.hpp"
45 
46 namespace gpstk
47 {
48 
49 
50       /* Compute satellite position & velocity at the given time
51        * using this ephemeris.
52        *
53        *  @throw InvalidRequest if required data has not been stored.
54        */
svXvt(const CommonTime & epoch) const55    Xvt GloEphemeris::svXvt(const CommonTime& epoch) const
56    {
57 
58          // Check that the given epoch is within the available time limits.
59          // We have to add a margin of 15 minutes (900 seconds).
60       if ( epoch <  (ephTime - 900.0) ||
61            epoch >= (ephTime + 900.0)   )
62       {
63          InvalidRequest e( "Requested time is out of ephemeris data" );
64          GPSTK_THROW(e);
65       }
66 
67       Xvt retVal = svXvtOverrideFit(epoch);
68       return retVal;
69    }
70 
svXvtOverrideFit(const CommonTime & epoch) const71    Xvt GloEphemeris::svXvtOverrideFit(const CommonTime& epoch) const
72    {
73          // Values to be returned will be stored here
74       Xvt sv;
75 
76          // If the exact epoch is found, let's return the values
77       if ( epoch == ephTime )       // exact match for epoch
78       {
79 
80          sv.x[0] = x[0]*1.e3;   // m
81          sv.x[1] = x[1]*1.e3;   // m
82          sv.x[2] = x[2]*1.e3;   // m
83          sv.v[0] = v[0]*1.e3;  // m/sec
84          sv.v[1] = v[1]*1.e3;  // m/sec
85          sv.v[2] = v[2]*1.e3;  // m/sec
86 
87             // In the GLONASS system, 'clkbias' already includes the
88             // relativistic correction, therefore we must substract the late
89             // from the former.
90          sv.relcorr = sv.computeRelativityCorrection();
91          sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr;
92          sv.clkdrift = clkdrift;
93          sv.frame = ReferenceFrame::PZ90;
94 
95             // We are done, let's return
96          return sv;
97 
98       }
99 
100          // Get the data out of the GloRecord structure
101       double px( x[0] );   // X coordinate (km)
102       double vx( v[0] );   // X velocity   (km/s)
103       double ax( a[0] );   // X acceleration (km/s^2)
104       double py( x[1] );   // Y coordinate
105       double vy( v[1] );   // Y velocity
106       double ay( a[1] );   // Y acceleration
107       double pz( x[2] );   // Z coordinate
108       double vz( v[2] );   // Z velocity
109       double az( a[2] );   // Z acceleration
110 
111          // We will need some PZ-90 ellipsoid parameters
112       PZ90Ellipsoid pz90;
113       double we( pz90.angVelocity() );
114 
115          // Get sidereal time at Greenwich at 0 hours UT
116       double gst( getSidTime( ephTime ) );
117       double s0( gst*PI/12.0 );
118       YDSTime ytime( ephTime );
119       double numSeconds( ytime.sod );
120       double s( s0 + we*numSeconds );
121       double cs( std::cos(s) );
122       double ss( std::sin(s) );
123 
124          // Initial state matrix
125       Vector<double> initialState(6), accel(3), dxt1(6), dxt2(6), dxt3(6),
126                      dxt4(6), tempRes(6);
127 
128          // Get the reference state out of GloEphemeris object data. Values
129          // must be rotated from PZ-90 to an absolute coordinate system
130          // Initial x coordinate (m)
131       initialState(0)  = (px*cs - py*ss);
132          // Initial y coordinate
133       initialState(2)  = (px*ss + py*cs);
134          // Initial z coordinate
135       initialState(4)  = pz;
136 
137          // Initial x velocity   (m/s)
138       initialState(1)  = (vx*cs - vy*ss - we*initialState(2) );
139          // Initial y velocity
140       initialState(3)  = (vx*ss + vy*cs + we*initialState(0) );
141          // Initial z velocity
142       initialState(5)  = vz;
143 
144 
145          // Integrate satellite state to desired epoch using the given step
146       double rkStep( step );
147 
148       if ( (epoch - ephTime) < 0.0 ) rkStep = step*(-1.0);
149       CommonTime workEpoch( ephTime );
150 
151       double tolerance( 1e-9 );
152       bool done( false );
153       while (!done)
154       {
155 
156             // If we are about to overstep, change the stepsize appropriately
157             // to hit our target final time.
158          if( rkStep > 0.0 )
159          {
160             if( (workEpoch + rkStep) > epoch )
161                rkStep = (epoch - workEpoch);
162          }
163          else
164          {
165             if ( (workEpoch + rkStep) < epoch )
166                rkStep = (epoch - workEpoch);
167          }
168 
169          numSeconds += rkStep;
170          s = s0 + we*( numSeconds );
171          cs = std::cos(s);
172          ss = std::sin(s);
173 
174             // Accelerations are computed once per iteration
175          accel(0) = ax*cs - ay*ss;
176          accel(1) = ax*ss + ay*cs;
177          accel(2) = az;
178 
179          dxt1 = derivative( initialState, accel );
180          for( int j = 0; j < 6; ++j )
181             tempRes(j) = initialState(j) + rkStep*dxt1(j)/2.0;
182 
183          dxt2 = derivative( tempRes, accel );
184          for( int j = 0; j < 6; ++j )
185             tempRes(j) = initialState(j) + rkStep*dxt2(j)/2.0;
186 
187          dxt3 = derivative( tempRes, accel );
188          for( int j = 0; j < 6; ++j )
189             tempRes(j) = initialState(j) + rkStep*dxt3(j);
190 
191          dxt4 = derivative( tempRes, accel );
192          for( int j = 0; j < 6; ++j )
193             initialState(j) = initialState(j) + rkStep * ( dxt1(j)
194                             + 2.0 * ( dxt2(j) + dxt3(j) ) + dxt4(j) ) / 6.0;
195 
196 
197             // If we are within tolerance of the target time, we are done.
198          workEpoch += rkStep;
199          if ( std::fabs(epoch - workEpoch ) < tolerance )
200             done = true;
201 
202       }  // End of 'while (!done)...'
203 
204 
205       px = initialState(0);
206       py = initialState(2);
207       pz = initialState(4);
208       vx = initialState(1);
209       vy = initialState(3);
210       vz = initialState(5);
211 
212       sv.x[0] = 1000.0*( px*cs + py*ss );         // X coordinate
213       sv.x[1] = 1000.0*(-px*ss + py*cs);          // Y coordinate
214       sv.x[2] = 1000.0*pz;                        // Z coordinate
215       sv.v[0] = 1000.0*( vx*cs + vy*ss + we*(sv.x[1]/1000.0) ); // X velocity
216       sv.v[1] = 1000.0*(-vx*ss + vy*cs - we*(sv.x[0]/1000.0) ); // Y velocity
217       sv.v[2] = 1000.0*vz;                        // Z velocity
218 
219          // In the GLONASS system, 'clkbias' already includes the relativistic
220          // correction, therefore we must substract the late from the former.
221       sv.relcorr = sv.computeRelativityCorrection();
222       sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr;
223       sv.clkdrift = clkdrift;
224       sv.frame = ReferenceFrame::PZ90;
225 
226          // We are done, let's return
227       return sv;
228 
229 
230    }  // End of method 'GloEphemeris::svXvt(const CommonTime& t)'
231 
232 
233       // Get the epoch time for this ephemeris
getEphemerisEpoch() const234    CommonTime GloEphemeris::getEphemerisEpoch() const
235    {
236 
237          // First, let's check if there is valid data
238       if(!valid)
239       {
240          InvalidRequest exc("getEphemerisEpoch(): No valid data stored.");
241          GPSTK_THROW(exc);
242       }
243 
244       return ephTime;
245 
246    }  // End of method 'GloEphemeris::getEphemerisEpoch()'
247 
248 
249       // This function returns the PRN ID of the SV.
getPRNID() const250    short GloEphemeris::getPRNID() const
251    {
252 
253          // First, let's check if there is valid data
254       if(!valid)
255       {
256          InvalidRequest exc("getPRNID(): No valid data stored.");
257          GPSTK_THROW(exc);
258       }
259 
260       return PRNID;
261 
262    }  // End of method 'GloEphemeris::getPRNID()'
263 
264 
265       /* Compute the satellite clock bias (sec) at the given time
266        *
267        * @param epoch   Epoch to compute satellite clock bias.
268        *
269        * @throw InvalidRequest if required data has not been stored.
270        */
svClockBias(const CommonTime & epoch) const271    double GloEphemeris::svClockBias(const CommonTime& epoch) const
272    {
273 
274          // First, let's check if there is valid data
275       if(!valid)
276       {
277          InvalidRequest exc("svClockBias(): No valid data stored.");
278          GPSTK_THROW(exc);
279       }
280 
281          // Auxiliar object
282       Xvt sv;
283       sv.x = x;
284       sv.v = v;
285 
286          // In the GLONASS system, 'clkbias' already includes the relativistic
287          // correction, therefore we must substract the late from the former.
288       sv.relcorr = sv.computeRelativityCorrection();
289       sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr;
290 
291       return sv.clkbias;
292 
293    }  // End of method 'GloEphemeris::svClockBias(const CommonTime& epoch)'
294 
295 
296       /* Compute the satellite clock drift (sec/sec) at the given time
297        *
298        * @param epoch   Epoch to compute satellite clock drift.
299        *
300        * @throw InvalidRequest if required data has not been stored.
301        */
svClockDrift(const CommonTime & epoch) const302    double GloEphemeris::svClockDrift(const CommonTime& epoch) const
303    {
304 
305          // First, let's check if there is valid data
306       if(!valid)
307       {
308          InvalidRequest exc("svClockDrift(): No valid data stored.");
309          GPSTK_THROW(exc);
310       }
311 
312       return clkdrift;
313 
314    }  // End of method 'GloEphemeris::svClockDrift(const CommonTime& epoch)'
315 
316 
317 
318 
319       // Output the contents of this ephemeris to the given stream as a single line.
dump(std::ostream & s) const320    void GloEphemeris::dump(std::ostream& s) const
321       throw()
322    {
323 
324       s << "Sys:" << satSys << ", PRN:" << PRNID
325         << ", Epoch:" << ephTime << ", pos:" << x
326         << ", vel:" << v << ", acc:" << a
327         << ", TauN:" << clkbias << ", GammaN:" << clkdrift
328          << ", MFTime:" << MFtime<< ", health:" << health
329         << ", freqNum:" << freqNum << ", ageOfInfo:" << ageOfInfo;
330 
331    }  // End of method 'GloEphemeris::dump(std::ostream& s)'
332 
prettyDump(std::ostream & s) const333    void GloEphemeris::prettyDump(std::ostream& s) const
334    {
335       s << "**********************************************" << std::endl;
336       s << "Slot ID     " << std::setw(12) << PRNID << std::endl;
337       s << "Epoch Time  " << printTime(ephTime,"%03j, %02m/%02d/%02y %02H:%02M:%02S")
338                           << std::endl;
339       s << "MFTime      " << std::setw(12) << MFtime << " sec of Week" << std::endl;
340       s << "Health      " << std::setw(12) << health << std::endl;
341       s << "Freq. Offset" << std::setw(12) << freqNum << std::endl;
342       s << "Age of Info " << std::setw(12) << ageOfInfo << " days" << std::endl;
343 
344       s << "Position    " << std::setw(12) << x << "m" << std::endl;
345       s << "Velocity    " << std::setw(12) << v << "m/sec" << std::endl;
346       s << "Acceleration" << std::setw(12) << a << "m/sec**2" << std::endl;
347       s << "TauN        " << std::setw(12) << clkbias << "units" << std::endl;
348       s << "GammaN      " << std::setw(12) << clkdrift << "units" << std::endl;
349    }
350 
terseDump(std::ostream & s) const351    void GloEphemeris::terseDump(std::ostream& s) const
352    {
353       s << " " << std::setw(2) << PRNID << " ";
354       s << printTime(ephTime,"%03j, %02m/%02d/%02y %02H:%02M:%02S") << "  ";
355       s << std::setw(6) << MFtime << "  ";
356       s << std::setw(5) << health << "    ";
357       s << std::setw(2) << freqNum << std::endl;
358    }
359 
terseHeader(std::ostream & s) const360    void GloEphemeris::terseHeader(std::ostream& s) const
361    {
362       s << "          Epoch time        Msg Time          Freq." << std::endl;
363       s << "Slot DOY mm/dd/yy HH:MM:SS    (SOW)   Health Offset" << std::endl;
364    }
365 
366       // Set the parameters for this ephemeris object.
setRecord(std::string svSys,short prn,const CommonTime & epoch,Triple pos,Triple vel,Triple acc,double tau,double gamma,long mftime,short h,short freqnum,double age,double rkStep)367    GloEphemeris& GloEphemeris::setRecord( std::string svSys,
368                                           short prn,
369                                           const CommonTime& epoch,
370                                           Triple pos,
371                                           Triple vel,
372                                           Triple acc,
373                                           double tau,
374                                           double gamma,
375                                           long mftime,
376                                           short h,
377                                           short freqnum,
378                                           double age,
379                                           double rkStep )
380    {
381       satSys   = svSys;
382       PRNID    = prn;
383       ephTime  = epoch;
384       x        = pos;
385       v        = vel;
386       a        = acc;
387       clkbias  = tau;
388       clkdrift = gamma;
389       MFtime   = mftime;
390       health   = h;
391       freqNum  = freqnum;
392       ageOfInfo = age;
393 
394       step = rkStep;
395 
396          // Set this object as valid
397       valid = true;
398 
399       return *this;
400 
401    }  // End of method 'GloEphemeris::setRecord()'
402 
403 
404       // Compute true sidereal time  (in hours) at Greenwich at 0 hours UT.
getSidTime(const CommonTime & time) const405    double GloEphemeris::getSidTime( const CommonTime& time ) const
406    {
407 
408          // The following algorithm is based on the paper:
409          // Aoki, S., Guinot,B., Kaplan, G. H., Kinoshita, H., McCarthy, D. D.
410          //    and P.K. Seidelmann. 'The New Definition of Universal Time'.
411          //    Astronomy and Astrophysics, 105, 359-361, 1982.
412 
413          // Get the Julian Day at 0 hours UT (jd)
414       YDSTime ytime( time );
415       double year( static_cast<double>(ytime.year) );
416       int doy( ytime.doy );
417       int temp( static_cast<int>(floor(365.25 * (year - 1.0))) + doy );
418 
419       double jd( static_cast<double>(temp)+ 1721409.5 );
420 
421          // Compute the Julian centuries (36525 days)
422       double jc( (jd - 2451545.0)/36525.0 );
423 
424          // Compute the sidereal time, in seconds
425       double sid( 24110.54841 + jc * ( 8640184.812866
426                               + jc * ( 0.093104 - jc * 0.0000062 ) ) );
427 
428       sid = sid / 3600.0;
429       sid = fmod(sid, 24.0);
430       if( sid < 0.0 ) sid = sid + 24.0;
431 
432       return sid;
433 
434    }; // End of method 'GloEphemeris::getSidTime()'
435 
436 
437       // Function implementing the derivative of GLONASS orbital model.
derivative(const Vector<double> & inState,const Vector<double> & accel) const438    Vector<double> GloEphemeris::derivative( const Vector<double>& inState,
439                                             const Vector<double>& accel )
440       const
441    {
442 
443          // We will need some important PZ90 ellipsoid values
444       PZ90Ellipsoid pz90;
445       const double j20( pz90.j20() );
446       const double mu( pz90.gm_km() );
447       const double ae( pz90.a_km() );
448 
449          // Let's start getting the current satellite position and velocity
450       double  x( inState(0) );          // X coordinate
451       //double vx( inState(1) );          // X velocity
452       double  y( inState(2) );          // Y coordinate
453       //double vy( inState(3) );          // Y velocity
454       double  z( inState(4) );          // Z coordinate
455       //double vz( inState(5) );          // Z velocity
456 
457       double r2( x*x + y*y + z*z );
458       double r( std::sqrt(r2) );
459       double xmu( mu/r2 );
460       double rho( ae/r );
461       double xr( x/r );
462       double yr( y/r );
463       double zr( z/r );
464       double zr2( zr*zr );
465       double k1(j20*xmu*1.5*rho*rho);
466       double  cm( k1*(1.0-5.0*zr2) );
467       double cmz( k1*(3.0-5.0*zr2) );
468       double k2(cm-xmu);
469 
470       double gloAx( k2*xr + accel(0) );
471       double gloAy( k2*yr + accel(1) );
472       double gloAz( (cmz-xmu)*zr + accel(2) );
473 
474       Vector<double> dxt(6, 0.0);
475 
476          // Let's insert data related to X coordinates
477       dxt(0) = inState(1);       // Set X'  = Vx
478       dxt(1) = gloAx;            // Set Vx' = gloAx
479 
480          // Let's insert data related to Y coordinates
481       dxt(2) = inState(3);       // Set Y'  = Vy
482       dxt(3) = gloAy;            // Set Vy' = gloAy
483 
484          // Let's insert data related to Z coordinates
485       dxt(4) = inState(5);       // Set Z'  = Vz
486       dxt(5) = gloAz;            // Set Vz' = gloAz
487 
488       return dxt;
489 
490    }  // End of method 'GloEphemeris::derivative()'
491 
492 
493       // Output the contents of this ephemeris to the given stream.
operator <<(std::ostream & s,const GloEphemeris & glo)494    std::ostream& operator<<( std::ostream& s, const GloEphemeris& glo )
495    {
496 
497       glo.dump(s);
498       return s;
499 
500    }  // End of 'std::ostream& operator<<()'
501 
502 
503 }  // End of namespace gpstk
504