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  * @file GloEphemerisStore.cpp
41  * Get GLONASS broadcast ephemeris data information
42  */
43 
44 #include "GloEphemerisStore.hpp"
45 #include "TimeString.hpp"
46 
47 using namespace std;
48 
49 namespace gpstk
50 {
51 
52       // Add ephemeris information from a Rinex3NavData object.
addEphemeris(const Rinex3NavData & data)53    bool GloEphemerisStore::addEphemeris(const Rinex3NavData& data)
54    {
55 
56          // If enabled, check SV health before entering here (health = 0 -> OK)
57       if( (data.health == 0) || (!onlyHealthy) )
58       {
59             // Get a GloEphemeris object from Rinex3NavData object
60          GloEphemeris gloEphem(data);
61 
62          CommonTime t( data.time);
63          t.setTimeSystem(TimeSystem::GLO);   // must be GLONASS time
64 
65          SatID sat( data.sat );
66          pe[sat][t] = gloEphem; // find or add entry
67 
68          if (t < initialTime)
69             initialTime = t;
70          if (t > finalTime)
71             finalTime = t;
72 
73          return true;
74 
75       }  // End of 'if( (data.health == 0) || (!onlyHealthy) )'
76 
77       return false;
78 
79    }  // End of method 'GloEphemerisStore::addEphemeris()'
80 
81 
82       /* Returns the position, velocity and clock offset of the indicated
83        * satellite in ECEF coordinates (meters) at the indicated time,
84        * in the PZ-90 ellipsoid.
85        *
86        *  @param[in] sat   Satellite's identifier
87        *  @param[in] epoch Time to look up
88        *
89        *  @return the Xvt of the object at the indicated time
90        *
91        *  @throw InvalidRequest If the request can not be completed for any
92        *  reason, this is thrown. The text may have additional information
93        *  as to why the request failed.
94        */
getXvt(const SatID & sat,const CommonTime & epoch) const95    Xvt GloEphemerisStore::getXvt( const SatID& sat,
96                                   const CommonTime& epoch ) const
97    {
98       // TD is this too strict?
99       if(epoch.getTimeSystem() != initialTime.getTimeSystem())
100       {
101          InvalidRequest e(string("Requested time system is not GLONASS time"));
102          GPSTK_THROW(e);
103       }
104 
105          // Check that the given epoch is within the available time limits.
106          // We have to add a margin of 15 minutes (900 seconds).
107       if ( epoch <  (initialTime - 900.0) ||
108            epoch >  (finalTime   + 900.0)   )
109       {
110          InvalidRequest e( "Requested time is out of boundaries for satellite "
111                           + StringUtils::asString(sat) );
112          GPSTK_THROW(e);
113       }
114 
115          // Look for the satellite in the 'pe' (EphMap) data structure.
116       GloEphMap::const_iterator svmap = pe.find(sat);
117 
118          // If satellite was not found, issue an exception
119       if (svmap == pe.end())
120       {
121          InvalidRequest e( "Ephemeris for satellite  "
122                            + StringUtils::asString(sat) + " not found." );
123          GPSTK_THROW(e);
124       }
125 
126          // Let's take the second part of the EphMap
127       const TimeGloMap& sem = svmap->second;
128 
129          // Look for 'i': the first element whose key >= epoch.
130       TimeGloMap::const_iterator i = sem.lower_bound(epoch);;
131 
132          // Values to be returned will be stored here
133       Xvt sv;
134 
135          // If we reached the end, the requested time is beyond the last
136          // ephemeris record, but it may still be within the allowable time
137          // span, so we can use the last record.
138       if ( i == sem.end() )
139       {
140          i = --i;
141       }
142 
143          // If key > (epoch+900), we must use the previous record if possible.
144       if ( ( i->first > (epoch+900.0) ) && ( i != sem.begin() ) )
145       {
146          i = --i;
147       }
148 
149          // Check that the given epoch is within the available time limits for
150          // this specific satellite, with a margin of 15 minutes (900 seconds).
151       if ( epoch <  (i->first - 900.0) ||
152            epoch >= (i->first   + 900.0)   )
153       {
154          InvalidRequest e( "Requested time is out of boundaries for satellite "
155                           + StringUtils::asString(sat) );
156          GPSTK_THROW(e);
157       }
158 
159          // We now have the proper reference data record. Let's use it
160       GloEphemeris data( i->second );
161 
162          // Compute the satellite position, velocity and clock offset
163       sv = data.svXvt( epoch );
164 
165          // We are done, let's return
166       return sv;
167 
168    }; // End of method 'GloEphemerisStore::getXvt()'
169 
170 
computeXvt(const SatID & sat,const CommonTime & epoch) const171    Xvt GloEphemerisStore::computeXvt(const SatID& sat,
172                                      const CommonTime& epoch) const throw()
173    {
174       Xvt rv;
175       rv.health = Xvt::HealthStatus::Unavailable;
176       try
177       {
178             // TD is this too strict?
179          if (epoch.getTimeSystem() != initialTime.getTimeSystem())
180          {
181                // Not valid, but we don't have a specific "health" state
182                // of "wrong time system"
183             return rv;
184          }
185 
186             // Check that the given epoch is within the available time limits.
187             // We have to add a margin of 15 minutes (900 seconds).
188          if ((epoch <  (initialTime - 900.0)) ||
189              (epoch >  (finalTime   + 900.0)))
190          {
191             return rv;
192          }
193 
194             // Look for the satellite in the 'pe' (EphMap) data structure.
195          GloEphMap::const_iterator svmap = pe.find(sat);
196 
197             // If satellite was not found, say so
198          if (svmap == pe.end())
199          {
200             return rv;
201          }
202 
203             // Let's take the second part of the EphMap
204          const TimeGloMap& sem = svmap->second;
205 
206             // Look for 'i': the first element whose key >= epoch.
207          TimeGloMap::const_iterator i = sem.lower_bound(epoch);;
208 
209             // If we reached the end, the requested time is beyond the last
210             // ephemeris record, but it may still be within the allowable time
211             // span, so we can use the last record.
212          if (i == sem.end())
213          {
214             i = --i;
215          }
216 
217             // If key > (epoch+900), we must use the previous record
218             // if possible.
219          if ((i->first > (epoch+900.0)) && (i != sem.begin()))
220          {
221             i = --i;
222          }
223 
224             // Check that the given epoch is within the available time
225             // limits for this specific satellite, with a margin of 15
226             // minutes (900 seconds).
227          if ((epoch <  (i->first - 900.0)) ||
228              (epoch >= (i->first + 900.0)))
229          {
230             return rv;
231          }
232 
233             // We now have the proper reference data record. Let's use it
234          GloEphemeris data(i->second);
235 
236             // Compute the satellite position, velocity and clock offset
237          rv = data.svXvt(epoch);
238          rv.health = (data.getHealth() == 0 ? Xvt::HealthStatus::Healthy
239                       : Xvt::HealthStatus::Unhealthy);
240       }
241       catch (...)
242       {
243       }
244 
245          // We are done, let's return
246       return rv;
247    }
248 
249 
getSVHealth(const SatID & sat,const CommonTime & epoch) const250    Xvt::HealthStatus GloEphemerisStore::getSVHealth(const SatID& sat,
251                                                     const CommonTime& epoch)
252       const throw()
253    {
254       Xvt::HealthStatus rv = Xvt::HealthStatus::Unavailable;
255       try
256       {
257             // TD is this too strict?
258          if (epoch.getTimeSystem() != initialTime.getTimeSystem())
259          {
260                // Not valid, but we don't have a specific "health" state
261                // of "wrong time system"
262             return rv;
263          }
264 
265             // Check that the given epoch is within the available time limits.
266             // We have to add a margin of 15 minutes (900 seconds).
267          if ((epoch <  (initialTime - 900.0)) ||
268              (epoch >  (finalTime   + 900.0)))
269          {
270             return rv;
271          }
272 
273             // Look for the satellite in the 'pe' (EphMap) data structure.
274          GloEphMap::const_iterator svmap = pe.find(sat);
275 
276             // If satellite was not found, say so
277          if (svmap == pe.end())
278          {
279             return rv;
280          }
281 
282             // Let's take the second part of the EphMap
283          const TimeGloMap& sem = svmap->second;
284 
285             // Look for 'i': the first element whose key >= epoch.
286          TimeGloMap::const_iterator i = sem.lower_bound(epoch);;
287 
288             // If we reached the end, the requested time is beyond the last
289             // ephemeris record, but it may still be within the allowable time
290             // span, so we can use the last record.
291          if (i == sem.end())
292          {
293             i = --i;
294          }
295 
296             // If key > (epoch+900), we must use the previous record
297             // if possible.
298          if ((i->first > (epoch+900.0)) && (i != sem.begin()))
299          {
300             i = --i;
301          }
302 
303             // Check that the given epoch is within the available time
304             // limits for this specific satellite, with a margin of 15
305             // minutes (900 seconds).
306          if ((epoch <  (i->first - 900.0)) ||
307              (epoch >= (i->first + 900.0)))
308          {
309             return rv;
310          }
311 
312             // We now have the proper reference data record. Let's use it
313          GloEphemeris data(i->second);
314          rv = (data.getHealth() == 0 ? Xvt::HealthStatus::Healthy
315                : Xvt::HealthStatus::Unhealthy);
316       }
317       catch (...)
318       {
319       }
320 
321          // We are done, let's return
322       return rv;
323 
324    }
325 
326 
327       /* A debugging function that outputs in human readable form,
328        * all data stored in this object.
329        *
330        * @param[in] s      The stream to receive the output; defaults to cout
331        * @param[in] detail The level of detail to provide
332        *
333        * @warning GLONASS position, velocity and acceleration information are
334        * given in km, km/s and km/(s*s), respectively.
335        */
dump(std::ostream & s,short detail) const336    void GloEphemerisStore::dump( std::ostream& s, short detail ) const
337    {
338       static const string fmt("%04Y/%02m/%02d %02H:%02M:%02S %P");
339       s << "Dump of GloEphemerisStore:\n";
340 
341       //if(detail == 0 ) {
342       s << " Span is " << (initialTime == CommonTime::END_OF_TIME
343                                     ? "End_time" : printTime(initialTime, fmt))
344         << " to " << (finalTime == CommonTime::BEGINNING_OF_TIME
345                                     ? "Begin_time" : printTime(finalTime, fmt))
346         << " with " << pe.size() << " entries; checkHealthFlag is "
347         << (onlyHealthy ? "T":"F") << std::endl;
348 
349       //}
350       //else
351       if(detail > 0) {
352          if(pe.size())
353             s << "Dump every record:\nweek   sow      = year/mn/dy hr:mi:sc Sys Sat   "
354             << "X                   Y                   Z                   "
355             << "VX                  VY                  VZ                  "
356             << "AX                  AY                  AZ                  "
357             << "TauN                GammaN            MFtime Hlth fNo AgeInfo\n";
358 
359          // Iterate through all items in the 'pe' GloEphMap
360          for( GloEphMap::const_iterator it = pe.begin(); it != pe.end(); ++it ) {
361 
362             // Then, iterate through corresponding 'TimeGloMap'
363           for( TimeGloMap::const_iterator tgmIter = (*it).second.begin();
364               tgmIter != (*it).second.end();
365               ++tgmIter )
366           {
367 
368                  // First, print year, Day-Of-Year and Seconds of Day
369             s << printTime(tgmIter->first,fmt) << " ";
370 
371                // Second, print SatID information
372             s << RinexSatID((*it).first) << " ";
373 
374                // Third, print satellite ephemeris data
375             GloEphemeris data( (*tgmIter).second );
376                // Get the satellite's acceleration
377             Triple a( data.getAcc() );
378 
379             s << scientific << setprecision(12);
380             s << setw(19) << data.x[0] << " "
381               << setw(19) << data.x[1] << " "
382               << setw(19) << data.x[2] << " "
383               << setw(19) << data.v[0] << " "
384               << setw(19) << data.v[1] << " "
385               << setw(19) << data.v[2] << " "
386               << setw(19) << a[0] << " "
387               << setw(19) << a[1] << " "
388               << setw(19) << a[2] << " "
389               << setw(19) << data.getTauN() << " "
390               << setw(19) << data.getGammaN() << " "
391               << setw(6) << data.getMFtime() << " "
392               << setw(3) << data.getHealth() << " "
393               << setw(3) << data.getfreqNum() << " "
394               << setprecision(2) << setw(5) << data.getAgeOfInfo();
395 
396                // Add end-of-line
397             s << endl;
398 
399           }  // End of 'for( TimeGloMap::const_iterator tgmIter = ...'
400 
401         }  // End of 'for( GloEphMap::const_iterator it = pe.begin(); ...'
402 
403       }  // End of 'else', i.e., detail != 0
404 
405       s << "  End of GloEphemerisStore data." << std::endl;
406 
407 
408    }; // End of method 'GloEphemerisStore::dump()'
409 
410 
411       /* Edit the dataset, removing data outside the indicated time interval
412        *
413        * @param[in] tmin   Defines the beginning of the time interval
414        * @param[in] tmax   Defines the end of the time interval
415        */
edit(const CommonTime & tmin,const CommonTime & tmax)416    void GloEphemerisStore::edit( const CommonTime& tmin,
417                                  const CommonTime& tmax )
418    {
419 
420          // Create a working copy
421       GloEphMap bak;
422 
423          // Reset the initial and final times
424       initialTime = CommonTime::END_OF_TIME;
425       finalTime   = CommonTime::BEGINNING_OF_TIME;
426 
427          // Iterate through all items in the 'bak' GloEphMap
428       for( GloEphMap::const_iterator it = pe.begin();
429            it != pe.end();
430            ++it )
431       {
432 
433             // Then, iterate through corresponding 'TimeGloMap'
434          for( TimeGloMap::const_iterator tgmIter = (*it).second.begin();
435               tgmIter != (*it).second.end();
436               ++tgmIter )
437          {
438 
439             CommonTime t( (*tgmIter).first );
440 
441                // Check if the current record is within the given time interval
442             if( ( tmin <= t ) && ( t <= tmax ) )
443             {
444 
445                   // If we are within the proper boundaries, let's add the data
446                GloEphemeris data( (*tgmIter).second );
447 
448                SatID sat( (*it).first );
449                bak[sat][t] = data;     // Add entry
450 
451                   // Update 'initialTime' and 'finalTime', if necessary
452                if (t < initialTime)
453                   initialTime = t;
454                else if (t > finalTime)
455                   finalTime = t;
456 
457             }  // End of 'if ( ( (*tgmIter).first >= tmin ) && ...'
458 
459          }  // End of 'for( TimeGloMap::const_iterator tgmIter = ...'
460 
461       }  // End of 'for( GloEphMap::const_iterator it = pe.begin(); ...'
462 
463          // Update the data map before returning
464       pe = bak;
465 
466       return;
467 
468    }; // End of method 'GloEphemerisStore::edit()'
469 
470 
471       // Determine the earliest time for which this object can successfully
472       // determine the Xvt for any object.
473       // @return The initial time
474       // @throw InvalidRequest This is thrown if the object has no data.
getInitialTime() const475    CommonTime GloEphemerisStore::getInitialTime() const
476    {
477 
478          // Check if the data map is empty
479       if( pe.empty() )
480       {
481          InvalidRequest e( "GloEphemerisStore object has no data." );
482          GPSTK_THROW(e);
483       }
484 
485       return initialTime;
486 
487    }; // End of method 'GloEphemerisStore::getInitialTime()'
488 
489 
490       // Determine the latest time for which this object can successfully
491       // determine the Xvt for any object.
492       // @return The final time
493       // @throw InvalidRequest This is thrown if the object has no data.
getFinalTime() const494    CommonTime GloEphemerisStore::getFinalTime() const
495    {
496 
497          // Check if the data map is empty
498       if( pe.empty() )
499       {
500          InvalidRequest e( "GloEphemerisStore object has no data." );
501          GPSTK_THROW(e);
502       }
503 
504       return finalTime;
505 
506    }; // End of method 'GloEphemerisStore::getFinalTime()'
507 
508    // Compute initial time for the given satellite
getInitialTime(const SatID & sat) const509    CommonTime GloEphemerisStore::getInitialTime(const SatID& sat) const
510    {
511       CommonTime ret(CommonTime::END_OF_TIME);
512       GloEphMap::const_iterator svmap = pe.find(sat);
513       if(svmap != pe.end()) {
514          const TimeGloMap& tgm = svmap->second;
515          TimeGloMap::const_iterator it = tgm.begin();
516          ret = it->first;
517       }
518       return ret;
519    }
520 
521    // Compute final time for the given satellite
getFinalTime(const SatID & sat) const522    CommonTime GloEphemerisStore::getFinalTime(const SatID& sat) const
523    {
524       CommonTime ret(CommonTime::BEGINNING_OF_TIME);
525       GloEphMap::const_iterator svmap = pe.find(sat);
526       if(svmap != pe.end()) {
527          const TimeGloMap& tgm = svmap->second;
528          TimeGloMap::const_reverse_iterator rit = tgm.rbegin();
529          ret = rit->first;
530       }
531       return ret;
532    }
533 
getIndexSet() const534    set<SatID> GloEphemerisStore::getIndexSet() const
535    {
536       set<SatID> retSet;
537       GloEphMap::const_iterator cit;
538       for (cit=pe.begin();cit!=pe.end();cit++)
539       {
540         const SatID& sidr = cit->first;
541         retSet.insert(sidr);
542       }
543       return retSet;
544    }
545 
546       /* Find the corresponding GLONASS ephemeris for the given epoch.
547        *
548        * @param sat SatID of satellite of interest.
549        * @param t time with which to search for ephemeris.
550        *
551        * @return a reference to the desired ephemeris.
552        * @throw InvalidRequest object thrown when no ephemeris is found.
553        */
554    const GloEphemeris& GloEphemerisStore ::
findEphemeris(const SatID & sat,const CommonTime & epoch) const555    findEphemeris( const SatID& sat, const CommonTime& epoch ) const
556    {
557          // Check that the given epoch is within the available time limits.
558          // We have to add a margin of 15 minutes (900 seconds).
559       if ( epoch < (initialTime - 900.0) ||
560            epoch > (finalTime   + 900.0)   )
561       {
562          InvalidRequest e( "Requested time is out of boundaries for satellite "
563                           + StringUtils::asString(sat) );
564          GPSTK_THROW(e);
565       }
566 
567          // Look for the satellite in the 'pe' (EphMap) data structure.
568       GloEphMap::const_iterator svmap = pe.find(sat);
569 
570          // If satellite was not found, issue an exception
571       if (svmap == pe.end())
572       {
573          InvalidRequest e( "Ephemeris for satellite  "
574                            + StringUtils::asString(sat) + " not found." );
575          GPSTK_THROW(e);
576       }
577 
578          // Let's take the second part of the EphMap
579       const TimeGloMap& sem = svmap->second;
580 
581          // 'i' will be the first element whose key >= epoch.
582       TimeGloMap::const_iterator i = sem.lower_bound(epoch);
583 
584          // If we reached the end, the requested time is beyond the last
585          // ephemeris record, but it may still be within the allowable time
586          // span, so we can use the last record.
587       if ( i == sem.end() )
588       {
589          i = --i;
590       }
591 
592          // If key > (epoch+900), we must use the previous record if possible.
593       if ( ( i->first > (epoch+900.0) ) && ( i != sem.begin() ) )
594       {
595          i = --i;
596       }
597 
598          // Check that the given epoch is within the available time limits for
599          // this specific satellite, with a margin of 15 minutes (900 seconds).
600       if ( epoch < (i->first - 900.0) ||
601            epoch > (i->first   + 900.0)   )
602       {
603          InvalidRequest e( "Requested time is out of boundaries for satellite "
604                           + StringUtils::asString(sat) );
605          GPSTK_THROW(e);
606       }
607 
608          // We now have the proper reference data record. Let's return it
609       return ( i->second );
610 
611    }; // End of method 'GloEphemerisStore::findEphemeris()'
612 
613 
614       // Return true if the given SatID is present in the store
isPresent(const SatID & id) const615    bool GloEphemerisStore::isPresent(const SatID& id) const
616    {
617 
618          // Look for the satellite in the 'pe' (GloEphMap) data structure.
619       GloEphMap::const_iterator svmap = pe.find(id);
620 
621          // If satellite was not found return false, else return true
622       if (svmap == pe.end())
623       {
624          return false;
625       }
626       else
627       {
628          return true;
629       }
630 
631    }; // End of method 'GloEphemerisStore::isPresent(const SatID& id)'
632 
633 
addToList(std::list<GloEphemeris> & v) const634    int GloEphemerisStore::addToList(std::list<GloEphemeris>& v) const
635    {
636       int n = 0;
637       for(GloEphMap::const_iterator it = pe.begin(); it != pe.end(); ++it )
638       {
639          for(TimeGloMap::const_iterator tgmIter = (*it).second.begin();
640               tgmIter != (*it).second.end(); ++tgmIter )
641          {
642              v.push_back(tgmIter->second);
643              n++;
644          }
645       }
646       return n;
647    }
648 
649 
650 }  // End of namespace gpstk
651