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 RinexClockHeader.cpp
41  * Encapsulate header of RINEX clock file, including I/O
42  */
43 
44 #include <list>
45 #include <string>
46 #include <cstdlib>
47 #include "RinexClockHeader.hpp"
48 #include "RinexClockStream.hpp"
49 #include "StringUtils.hpp"
50 #include "SatID.hpp"
51 #include "FFStream.hpp"
52 #include "FFStreamError.hpp"
53 
54 #ifdef _WIN32
55 #if (_MSC_VER == 1700)
56 #define strtoll _strtoi64
57 #endif
58 #endif
59 
60 using namespace std;
61 
62 namespace gpstk
63 {
64    using namespace StringUtils;
65 
66    const string RinexClockHeader::versionString     =  "RINEX VERSION / TYPE";
67    const string RinexClockHeader::runByString       =  "PGM / RUN BY / DATE";
68    const string RinexClockHeader::commentString     =  "COMMENT";
69    const string RinexClockHeader::leapSecondsString =  "LEAP SECONDS";
70    const string RinexClockHeader::dataTypesString   =  "# / TYPES OF DATA";
71    const string RinexClockHeader::stationNameString =  "STATION NAME / NUM";
72    const string RinexClockHeader::calibrationClkString = "STATION CLK REF";
73    const string RinexClockHeader::acNameString      =   "ANALYSIS CENTER";
74    const string RinexClockHeader::numRefClkString   =  "# OF CLK REF";
75    const string RinexClockHeader::analysisClkRefString = "ANALYSIS CLK REF";
76    const string RinexClockHeader::numStationsString = "# OF SOLN STA / TRF";
77    const string RinexClockHeader::solnStaNameString =  "SOLN STA NAME / NUM";
78    const string RinexClockHeader::numSatsString     =  "# OF SOLN SATS";
79    const string RinexClockHeader::prnListString     =  "PRN LIST";
80    const string RinexClockHeader::endOfHeader       =  "END OF HEADER";
81 
82 
isValid() const83    bool RinexClockHeader::isValid() const
84    {
85       if ( !(dataTypeList.size() >= 1) )
86       {
87          return false;
88       }
89 
90       list<RinexClkType>::const_iterator itr;
91       for (itr = dataTypeList.begin(); itr != dataTypeList.end(); itr++)
92       {
93          if (*itr == AR)
94          {
95             if ( !(valid & allValidAR) )
96                return false;
97          }
98          else if (*itr == AS)
99          {
100             if ( !(valid & allValidAS) )
101                return false;
102          }
103          else if (*itr == CR)
104          {
105             if ( !(valid & allValidCR) )
106                return false;
107          }
108          else if (*itr == DR)
109          {
110             if ( !(valid & allValidDR) )
111                return false;
112          }
113          else if (*itr == MS)
114          {
115             if ( !(valid & allValidMS) )
116                return false;
117          }
118          else  // unknown type
119             return false;
120       }
121 
122       return true;
123 
124    }  // isValid
125 
126 
clear()127    void RinexClockHeader::clear()
128    {
129       version = 2.0;
130       fileType.clear();
131       fileProgram.clear();
132       fileAgency.clear();
133       date.clear();
134       commentList.clear();
135       leapSeconds = 0;
136       numType = 0;
137       dataTypeList.clear();
138       stationName.clear();
139       stationNumber.clear();
140       stationClkRef.clear();
141       ac.clear();
142       acName.clear();
143       refClkList.clear();
144       numSta = 0;
145       trf.clear();
146       solnStaList.clear();
147       numSats = 0;
148       prnList.clear();
149       valid = 0;
150    }
151 
152 
dump(ostream & s) const153    void RinexClockHeader::dump(ostream& s) const
154    {
155       s << "---------------------- REQUIRED ---------------------" << endl;
156       s << "Rinex Version: " << fixed << setw(4) << setprecision(2) << version
157         << ", File type: " << fileType << endl;
158       s << "Program: " << fileProgram
159         << ", Agency: " << fileAgency
160         << ", Date: " << date << endl;
161 
162       s << "Clock data types: ";
163       bool ar, as, cr, dr, ms;
164       ar = as = cr = dr = ms = false;
165       list<RinexClkType>::const_iterator dataTypeListItr;
166       for(dataTypeListItr = dataTypeList.begin();
167           dataTypeListItr != dataTypeList.end(); dataTypeListItr++)
168       {
169          s << leftJustify(dataTypeListItr->type, 2) <<  ' ';
170          if      (*dataTypeListItr == AS) as = true;
171          else if (*dataTypeListItr == AR) ar = true;
172          else if (*dataTypeListItr == CR) cr = true;
173          else if (*dataTypeListItr == DR) dr = true;
174          else if (*dataTypeListItr == MS) ms = true;
175       }
176       s << endl;
177 
178       if ( cr || dr || (valid & stationNameValid) )
179       {
180          s << "Station/Reciever: " << stationName
181            << ' ' << stationNumber << endl;
182       }
183 
184       if ( cr || (valid & calibrationClkValid) )
185       {
186          s << "Station Clock Ref: " << stationClkRef << endl;
187       }
188 
189       if ( ar || as || ms || (valid & acNameValid) )
190       {
191          s << "Analysis Center: " << ac
192            << ' ' << acName << endl;
193       }
194 
195       if ( ar || as || (valid & numRefClkValid) )
196       {
197          list<RefClkRecord>::const_iterator refClkListItr;
198          for (refClkListItr = refClkList.begin();
199               refClkListItr != refClkList.end(); refClkListItr++)
200          {
201             s << "Clock References from: " << refClkListItr->startEpoch
202               << ", to: " << refClkListItr->stopEpoch
203               << ", count: " << refClkListItr->numClkRef
204               << endl;
205             list<RefClk>::const_iterator clocksItr;
206             for (clocksItr = refClkListItr->clocks.begin();
207                  clocksItr != refClkListItr->clocks.end(); clocksItr++)
208             {
209                s << "     " << "name: " << clocksItr->name
210                  << ", number: " << clocksItr->number
211                  << ", constraint: " << clocksItr->clkConstraint
212                  << endl;
213             }
214          }
215       }
216 
217       if ( ar || as || (valid & numStationsValid) )
218       {
219          s << "# of Solution Stations: " << numSta
220            << ", TRF: " << trf
221            << endl;
222       }
223 
224       if ( ar || as || (valid & solnStaNameValid) )
225       {
226          list<SolnSta>::const_iterator solnStaListItr;
227          for (solnStaListItr = solnStaList.begin();
228               solnStaListItr != solnStaList.end(); solnStaListItr++)
229          {
230             s << "Soln. station/reciever name: " << solnStaListItr->name
231               << ", number: " << solnStaListItr->number
232               << endl
233               << "  pos: x:" << rightJustify(asString(solnStaListItr->posX), 11)
234               << " y:" << rightJustify(asString(solnStaListItr->posY), 11)
235               << " z:" << rightJustify(asString(solnStaListItr->posZ), 11)
236               << endl;
237          }
238       }
239 
240       if ( as || (valid & numSatsValid) )
241       {
242          s << "Soln. PRN count: " << numSats << endl;
243       }
244 
245       if ( as || (valid & prnListValid) )
246       {
247          s << "  ";
248          list<SatID>::const_iterator prnListItr;
249          for (prnListItr = prnList.begin();
250               prnListItr != prnList.end(); prnListItr++)
251          {
252             s << ' ';
253             string sat;
254             switch(prnListItr->system)
255             {
256                case SatelliteSystem::GPS:     sat = "G"; break;
257                case SatelliteSystem::Glonass: sat = "R"; break;
258                default:                   sat = "?"; break;
259             }
260             sat += rightJustify(asString(prnListItr->id), 2, '0');
261             s << sat;
262          }
263          s << endl;
264       }
265 
266       s << "---------------------- OPTIONAL* --------------------" << endl;
267       s << "*If data type is AS or AR some comments are required." << endl;
268 
269       if ( as || ar || (valid & commentValid) )
270       {
271          s << "Comment(s): " << endl;
272          list<string>::const_iterator commentListItr;
273          for (commentListItr = commentList.begin();
274               commentListItr != commentList.end(); commentListItr++)
275          {
276             s << "   " << *commentListItr << endl;
277          }
278       }
279 
280       if ( valid & leapSecondsValid )
281       {
282          s << "Leap Seconds: " << leapSeconds << endl;
283       }
284 
285       s << "-------------------- END OF HEADER ------------------" << endl;
286 
287    }  // dump
288 
289 
reallyPutRecord(FFStream & ffs) const290    void RinexClockHeader::reallyPutRecord(FFStream& ffs) const
291    {
292       RinexClockStream& strm = dynamic_cast<RinexClockStream&>(ffs);
293 
294       strm.header = *this;
295 
296       if ( !isValid() )
297       {
298          FFStreamError err("Incomplete or invalid header.");
299          err.addText("Make sure you set all header valid bits for all "
300                      "of the available data.");
301          GPSTK_THROW(err);
302       }
303 
304       if (valid & versionValid)
305       {
306          strm << right << setw(9) << setprecision(2) << fixed << version << left
307               << setw(11) << ' '
308               << setw(40) << fileType << versionString << endlpp;
309       }
310       if (valid & runByValid)
311       {
312          strm << setw(20) << fileProgram
313               << setw(20) << fileAgency
314               << setw(20) << date
315               << runByString << endlpp;
316       }
317       if (valid & commentValid)
318       {
319          list<string>::const_iterator itr;
320          for (itr = commentList.begin(); itr != commentList.end(); itr++)
321          {
322             strm << setw(60) << (*itr) << commentString << endlpp;
323          }
324       }
325       if (valid & leapSecondsValid)
326       {
327          strm << right << setw(6) << leapSeconds << left
328               << setw(54) << ' ' << leapSecondsString << endlpp;
329       }
330       if ( valid & dataTypesValid )
331       {
332          strm << right << setw(6) << numType;
333          for (const auto& itr : dataTypeList)
334          {
335             strm << "    " << right << setw(2) << itr.type;
336          }
337          strm << left << setw(54 - ((dataTypeList.size())*6)) << ' '
338               << dataTypesString << endlpp;
339       }
340       if ( valid & stationNameValid )
341       {
342          strm << setw(4) << stationName
343               << ' ' << setw(20) << stationNumber
344               << setw(35) << ' ' << stationNameString << endlpp;
345       }
346       if ( valid & calibrationClkValid )
347       {
348          strm << setw(60) << stationClkRef << calibrationClkString << endlpp;
349       }
350       if ( valid & acNameValid )
351       {
352          strm << setw(3) << ac << "  " << setw(55) << acName << acNameString
353               << endlpp;
354       }
355       if ( valid & numStationsValid )
356       {
357          for (const auto& recItr : refClkList)
358          {
359             strm << right << setw(6) << recItr.numClkRef << left
360                  << ' ' << writeTime(recItr.startEpoch)
361                  << ' ' << writeTime(recItr.stopEpoch)
362                  << numRefClkString << endlpp;
363 
364             for (const auto& clkItr : recItr.clocks)
365             {
366                strm << setw(4) << clkItr.name
367                     << ' ' << setw(20) << clkItr.number
368                     << setw(15) << ' ';
369                if (clkItr.clkConstraint != 0)
370                {
371                   strm << clkItr.clkConstraint;
372                }
373                else
374                {
375                   strm << setw(19) << ' ';
376                }
377                strm << ' ' << analysisClkRefString << endlpp;
378             }
379          }
380       }
381       if ( valid & numStationsValid )
382       {
383          strm << right << setw(6) << numSta << left
384               << "    " << setw(50) << trf
385               << numStationsString << endlpp;
386       }
387       if ( valid & solnStaNameValid )
388       {
389          for (const auto& itr : solnStaList)
390          {
391             strm << setw(4) << itr.name
392                  << ' ' << setw(20) << itr.number
393                  << right << setw(11) << itr.posX << ' '
394                  << right << setw(11) << itr.posY << ' '
395                  << right << setw(11) << itr.posZ << left
396                  << solnStaNameString << endlpp;
397          }
398       }
399       if ( valid & numSatsValid )
400       {
401          strm << right << setw(6) << numSats << left << setw(54) << ' '
402               << numSatsString << endlpp;
403       }
404       if ( valid & prnListValid )
405       {
406          int prnCount = 0;
407          for (const auto& itr : prnList)
408          {
409             prnCount++;
410 
411             if (itr.system == SatelliteSystem::GPS)
412                strm << "G";
413             else if (itr.system == SatelliteSystem::Glonass)
414                strm << "R";
415             else
416                strm << " ";
417             strm << right << setw(2) << setfill('0') << itr.id << setfill(' ')
418                  << ' ' << left;
419             if ( (prnCount % 15) == 0 )
420             {
421                strm << prnListString << endlpp;
422             }
423          }
424 
425          if ( (prnCount % 15) != 0 )
426          {
427             strm << setw((15-(prnCount % 15)) * 4) << ' ' << prnListString
428                  << endlpp;
429          }
430       }
431 
432 
433       strm << setw(60) << ' ' << endOfHeader << endlpp;
434 
435    }  // reallyPutRecord
436 
437 
438       // This function parses the entire header from the given stream
reallyGetRecord(FFStream & ffs)439    void RinexClockHeader::reallyGetRecord(FFStream& ffs)
440    {
441       RinexClockStream& strm = dynamic_cast<RinexClockStream&>(ffs);
442 
443          // if already read, just return
444       if (strm.headerRead == true)
445          return;
446 
447          // Reading a new header, clear any preexisting data.
448       clear();
449 
450       string line;
451 
452       while ( !(valid & endValid) )
453       {
454          strm.formattedGetLine(line);
455          StringUtils::stripTrailing(line);
456 
457          if ( line.length() == 0 )
458          {
459             FFStreamError ffse("No data read!");
460             GPSTK_THROW(ffse);
461          }
462          else if ( line.length() < 60 || line.length() > 80 )
463          {
464             FFStreamError ffse("Invalid line length");
465             GPSTK_THROW(ffse);
466          }
467 
468          try
469          {
470             ParseHeaderRecord(line);
471          }
472          catch(FFStreamError& ffse)
473          {
474             GPSTK_RETHROW(ffse);
475          }
476       }
477 
478          // If we get here, we should have reached the end of header line
479       strm.header = *this;
480       strm.headerRead = true;
481 
482    }  // reallyGetRecord
483 
484 
485       // this function parses a single header record
ParseHeaderRecord(const string & line)486    void RinexClockHeader::ParseHeaderRecord(const string& line)
487    {
488       string label(line, 60, 20);
489 
490          // RINEX VERSION / TYPE
491       if (label == versionString)
492       {
493          version = asDouble(line.substr(0,9));
494 
495          fileType = strip(line.substr(20, 40));
496          if ( fileType[0] != 'C' && fileType[0] != 'c' )
497          {
498                // invalid fileType - throw
499             FFStreamError e("Incorrect file type: " + fileType);
500             GPSTK_THROW(e);
501          }
502 
503          valid |= versionValid;
504 
505       }
506          // PGM / RUN BY / DATE
507       else if (label == runByString)
508       {
509          fileProgram =  strip(line.substr( 0, 20));
510          fileAgency  =  strip(line.substr(20, 20));
511          date        =  strip(line.substr(40, 20));
512 
513          valid |= runByValid;
514 
515       }
516          // COMMENT
517       else if (label == commentString)
518       {
519          string s = line.substr(0, 60);
520          commentList.push_back(s);
521 
522          valid |= commentValid;
523 
524       }
525          // LEAP SECONDS
526       else if (label == leapSecondsString)
527       {
528          leapSeconds = asInt(line.substr(0,6));
529 
530          valid |= leapSecondsValid;
531 
532       }
533          // # / TYPES OF DATA
534       else if (label == dataTypesString)
535       {
536          numType = asInt(line.substr(0,6));
537          if ( numType < 0 || numType > 5 )
538          {
539                // invalid number of data types - throw
540             FFStreamError e("Invalid number of data types: " +
541                             asString(numType));
542             GPSTK_THROW(e);
543          }
544          dataTypeList.clear();
545          for(int i = 0; i < numType; i++)
546          {
547             string dtype = line.substr(i*6+10, 2);
548             if      ( upperCase(dtype) == "AR" ) dataTypeList.push_back(AR);
549             else if ( upperCase(dtype) == "AS" ) dataTypeList.push_back(AS);
550             else if ( upperCase(dtype) == "CR" ) dataTypeList.push_back(CR);
551             else if ( upperCase(dtype) == "DR" ) dataTypeList.push_back(DR);
552             else if ( upperCase(dtype) == "MS" ) dataTypeList.push_back(MS);
553             else
554             { // unknown data type - throw
555                FFStreamError e("Invalid data type: " + dtype);
556                GPSTK_THROW(e);
557             }
558          }
559 
560          valid |= dataTypesValid;
561 
562       }
563          // STATION NAME / NUM
564       else if (label == stationNameString)
565       {
566          stationName = line.substr(0,4);
567          stationNumber = strip(line.substr(4,20));
568 
569          valid |= stationNameValid;
570 
571       }
572          // STATION CLK REF
573       else if (label == calibrationClkString)
574       {
575          stationClkRef = strip( line.substr(0,60) );
576 
577          valid |= calibrationClkValid;
578 
579       }
580          // ANALYSIS CENTER
581       else if (label == acNameString)
582       {
583          ac = line.substr(0, 3);
584          acName = strip(line.substr(5,55));
585 
586          valid |= acNameValid;
587 
588       }
589          // # OF CLK REF
590       else if (label == numRefClkString)
591       {
592          RefClkRecord record;
593          record.numClkRef = asInt( line.substr(0,6) );
594          if( asInt(line.substr(7,4)) )
595          {
596             record.startEpoch = parseTime(line.substr(7,26));
597             if ( asInt(line.substr(34,26)) )
598             {
599                record.stopEpoch = parseTime(line.substr(34,26));
600                if ( record.startEpoch > record.stopEpoch )
601                {  // invalid start/stop epochs - throw
602                   FFStreamError e("Invalid Start/Stop Epoch start: " +
603                                   line.substr(7,26) + ", stop: " +
604                                   line.substr(34,26));
605                   GPSTK_THROW(e);
606                }
607             }
608             else
609             {  // startEpoch w/o stopEpoch - throw
610                FFStreamError e("Invalid Start/Stop Epoch start: " +
611                                line.substr(7,26) + ", stop: " +
612                                line.substr(34,26));
613                GPSTK_THROW(e);
614             }
615          }
616          else
617          {
618             record.startEpoch = CommonTime::BEGINNING_OF_TIME;
619             if ( asInt(line.substr(34,26)) )
620             {  // stop epoch w/o start epoch
621                FFStreamError e("Invalid Start/Stop Epoch start: " +
622                                line.substr(7,26) + ", stop: " +
623                                line.substr(34,26));
624                GPSTK_THROW(e);
625             }
626             else
627             {
628                record.stopEpoch = CommonTime::BEGINNING_OF_TIME;
629             }
630          }
631             // add the ref clk record to the list
632          refClkList.push_back(record);
633 
634          valid |= numRefClkValid;
635 
636       }
637          /// ANALYSIS CLK REF
638       else if (label == analysisClkRefString)
639       {
640          if ( refClkList.empty() )
641          {  // empty list - throw
642             FFStreamError e("\"ANALYSIS CLK REF\" record without previous "
643                             "\"# OF CLK REF\" record.");
644             GPSTK_THROW(e);
645          }
646 
647             // get the previous reference clock record
648          std::list<RefClkRecord>::iterator itr = refClkList.end();
649          --itr;
650 
651          if ( itr->numClkRef <= itr->clocks.size() )
652          {  // Excessive # of clock references - throw
653             FFStreamError e("\"ANALYSIS CLK REF\" entry exceeds "
654                             "\"# of CLK REF\": " + asString(itr->numClkRef));
655             GPSTK_THROW(e);
656          }
657 
658          RefClk refclk;
659          refclk.name = line.substr(0,4);
660          refclk.number = strip(line.substr(5,20));
661          refclk.clkConstraint = line.substr(40,19);
662          itr->clocks.push_back(refclk);
663 
664       }
665          /// # OF SOLN STA / TRF
666       else if (label == numStationsString)
667       {
668          numSta = asInt( line.substr(0,6) );
669          trf = strip(line.substr(10,50));
670 
671          valid |= numStationsValid;
672 
673       }
674          /// SOLN STA NAME / NUM
675       else if (label == solnStaNameString)
676       {
677          SolnSta solnSta;
678 
679          solnSta.name = line.substr(0,4);
680          solnSta.number = strip(line.substr(5,20));
681          solnSta.posX = strtoll(strip(line.substr(25,11)).c_str(), 0, 10);
682          solnSta.posY = strtoll(strip(line.substr(37,11)).c_str(), 0, 10);
683          solnSta.posZ = strtoll(strip(line.substr(49,11)).c_str(), 0, 10);
684 
685          solnStaList.push_back(solnSta);
686 
687          valid |= solnStaNameValid;
688 
689       }
690          // # OF SOLN SATS
691       else if (label == numSatsString)
692       {
693          numSats = asInt(line.substr(0,6));
694 
695          valid |= numSatsValid;
696 
697       }
698          // PRN LIST
699       else if (label == prnListString)
700       {
701          string s = line.substr(0,60);
702          string word = stripFirstWord(s);
703 
704          while ( !word.empty() )
705          {
706             if ( word[0] == 'G' || word[0] == 'g' )
707             {
708                prnList.push_back(SatID(asInt(word.substr(1,2)),
709                                        SatelliteSystem::GPS));
710             }
711             else if ( word[0] == 'R' || word[0] == 'r' )
712             {
713                prnList.push_back(SatID(asInt(word.substr(1,2)),
714                                        SatelliteSystem::Glonass));
715             }
716             else
717             {  // unknown satellite system - throw
718                FFStreamError e("Invalid PRN: " + word);
719                GPSTK_THROW(e);
720             }
721 
722             word = stripFirstWord(s);
723          }
724 
725          valid |= prnListValid;
726 
727       }
728          // END OF HEADER
729       else if (label == endOfHeader)
730       {
731          valid |= endValid;
732 
733       }
734       else
735       {  // invalid label - throw
736          FFStreamError e("Invalid label: " + label);
737          GPSTK_THROW(e);
738       }
739 
740    }   // ParseHeaderRecord
741 
742 
743 }  // namespace
744