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 IonexHeader.cpp
41  * This class encapsulates the header of Ionex file, including I/O
42  */
43 
44 #include <cctype>
45 
46 #include "StringUtils.hpp"
47 #include "MathBase.hpp"
48 #include "IonexHeader.hpp"
49 #include "IonexStream.hpp"
50 #include "CivilTime.hpp"
51 
52 using namespace std;
53 using namespace gpstk::StringUtils;
54 
55 namespace gpstk
56 {
57    const string IonexHeader::versionString         =  "IONEX VERSION / TYPE";
58    const string IonexHeader::runByString           =  "PGM / RUN BY / DATE";
59    const string IonexHeader::descriptionString     =  "DESCRIPTION";
60    const string IonexHeader::commentString         =  "COMMENT";
61    const string IonexHeader::firstTimeString       =  "EPOCH OF FIRST MAP";
62    const string IonexHeader::lastTimeString        =  "EPOCH OF LAST MAP";
63    const string IonexHeader::intervalString        =  "INTERVAL";
64    const string IonexHeader::numMapsString         =  "# OF MAPS IN FILE";
65    const string IonexHeader::mappingFunctionString =  "MAPPING FUNCTION";
66    const string IonexHeader::elevationString       =  "ELEVATION CUTOFF";
67    const string IonexHeader::observablesUsedString =  "OBSERVABLES USED";
68    const string IonexHeader::numStationsString     =  "# OF STATIONS";
69    const string IonexHeader::numSatsString         =  "# OF SATELLITES";
70    const string IonexHeader::baseRadiusString      =  "BASE RADIUS";
71    const string IonexHeader::mapDimensionString    =  "MAP DIMENSION";
72    const string IonexHeader::hgtGridString         =  "HGT1 / HGT2 / DHGT";
73    const string IonexHeader::latGridString         =  "LAT1 / LAT2 / DLAT";
74    const string IonexHeader::lonGridString         =  "LON1 / LON2 / DLON";
75    const string IonexHeader::exponentString        =  "EXPONENT";
76    const string IonexHeader::startAuxDataString    =  "START OF AUX DATA";
77    const string IonexHeader::endAuxDataString      =  "END OF AUX DATA";
78    const string IonexHeader::endOfHeader           =  "END OF HEADER";
79 
80    const string IonexHeader::DCB::svsAuxDataString       =  "PRN / BIAS / RMS";
81    const string IonexHeader::DCB::stationsAuxDataString  =
82                                                          "STATION / BIAS / RMS";
83 
84 
85       // Clear (empty out) header
clear(void)86    void IonexHeader::clear(void)
87    {
88 
89       version = 1.0;
90       descriptionList.clear();
91       commentList.clear();
92       interval = 0;
93       numMaps = numStations = numSVs = mapDims = 0;
94       elevation = baseRadius = 0;
95 
96       hgt[0] = hgt[1] = hgt[2] = 0.0;
97       lat[0] = lat[1] = lat[2] = 0.0;
98       lon[0] = lon[1] = lon[2] = 0.0;
99 
100       exponent = -1;    // that's the default value
101       svsmap.clear();
102       valid = auxDataFlag = false;
103 
104       return;
105 
106    }  // End of method 'IonexHeader::clear()'
107 
108 
109 
110       /* Simple debug output function.
111        *
112        * It simply outputs the version, name and number of maps contained
113        * in this Ionex header.
114        */
dump(std::ostream & os) const115    void IonexHeader::dump(std::ostream& os) const
116    {
117 
118       os << "-------------------------------- IONEX HEADER"
119          << "--------------------------------" << endl;
120 
121       os << "First epoch            : " << firstEpoch << endl;
122       os << "Last epoch             : " << lastEpoch << endl;
123       os << "Interval               : " << interval << endl;
124       os << "Number of ionex maps   : " << numMaps << endl;
125       os << "Mapping function       : " << mappingFunction << endl;
126       os << "Elevation cut off      : " << elevation << endl;
127       os << "Number of stations     : " << numStations << endl;
128       os << "Number of satellites   : " << numSVs << endl;
129       os << "Map dimensions         : " << mapDims << endl;
130 
131       os << "HGT1 / HGT2 / DHGT     : " << hgt[0] << " / "
132                                         << hgt[1] << " / "
133                                         << hgt[2] << endl;
134       os << "LAT1 / LAT2 / DLAT     : " << lat[0] << " / "
135                                         << lat[1] << " / "
136                                         << lat[2] << endl;
137       os << "LON1 / LON2 / DLON     : " << lon[0] << " / "
138                                         << lon[1] << " / "
139                                         << lon[2] << endl;
140       os << "Valid object?          : " << valid  << endl;
141 
142       os << "-------------------------------- END OF HEADER"
143          << "-------------------------------" << endl;
144 
145       os << endl;
146 
147    }  //End of method 'IonexHeader::dump()'
148 
149 
150 
151       /*
152        * Parse a single auxiliary header record that contains "Differential
153        * code biases".
154        */
ParseDcbRecord(std::string & line)155    void IonexHeader::ParseDcbRecord(std::string &line)
156    {
157 
158       string label(line, 60, 20);
159 
160       if (label == DCB::svsAuxDataString)
161       {
162             // prepare the DCB structure
163          char c = isspace(line[3]) ? 'G' : line[3];
164          int prn     = asInt(line.substr(4,2));
165          double bias = asDouble(line.substr(6,16));// * 1e-9; // change to seconds
166          double rms  = asDouble(line.substr(16,26));
167 
168             // prepare SatID object that is the key of the map
169          SatelliteSystem system;
170          switch(line[3])
171          {
172 
173             case ' ': case 'G': case 'g':
174                system = SatelliteSystem::GPS;
175                break;
176 
177             case 'R': case 'r':
178                system = SatelliteSystem::Glonass;
179                break;
180 
181             default:                   // non-IONEX system character
182                FFStreamError e(std::string("Invalid system character \"")
183                                + c + std::string("\""));
184                GPSTK_THROW(e);
185 
186          }  // End of 'switch(line[3])'
187 
188          SatID svid = SatID(prn,system);
189 
190             // add to map
191          svsmap[svid] = DCB(c,prn,bias,rms);
192 
193       }  // End of 'if (label == DCB::svsAuxDataString)'...
194       else if (label == DCB::stationsAuxDataString)
195       {
196 
197          // WARNING: at this stage the DCB values for the contributing
198          // stations are not mapped.
199 
200       }
201       else if (label == commentString)
202       {
203 
204             // CODE's product has a comment line before aux data end
205          string s = strip(line.substr(0,60));
206          commentList.push_back(s);
207 
208       }
209       else if (label == endAuxDataString)
210       {
211 
212          auxDataFlag = false;          // End of aux data
213 
214       }
215       else
216       {
217 
218          FFStreamError e(std::string( "Unidentified IONEX::DCB label: "
219                                       + label) );
220 
221          GPSTK_THROW(e);
222 
223       }  // End of 'if (label == endAuxDataString)'...
224 
225       return;
226 
227    }  // End of method 'IonexHeader::ParseDcbRecord()'
228 
229 
230 
231       /* Parse a single header record, and modify 'valid' accordingly.
232        *
233        * Used by reallyGetRecord for both IonexHeader and IonexData.
234        */
ParseHeaderRecord(std::string & line)235    void IonexHeader::ParseHeaderRecord(std::string &line)
236    {
237 
238       string label(line, 60, 20);
239 
240       if (label == versionString)
241       {
242 
243          version  = asDouble(line.substr(0,20));
244          fileType = strip(line.substr(20,20));
245          system   = strip(line.substr(40,20));
246 
247       }
248       else if (label == runByString)
249       {
250 
251          fileProgram = strip(line.substr( 0,20));
252          fileAgency  = strip(line.substr(20,20));
253          date        = strip(line.substr(40,20));
254 
255       }
256       else if (label == descriptionString)
257       {
258 
259          string s = line.substr(0,60);
260          descriptionList.push_back(s);
261 
262       }
263       else if (label == commentString)
264       {
265 
266          string s = line.substr(0,60);
267          commentList.push_back(s);
268 
269       }
270       else if (label == firstTimeString)
271       {
272 
273          firstEpoch = parseTime(line);
274 
275       }
276       else if (label == lastTimeString)
277       {
278 
279          lastEpoch = parseTime(line);
280 
281       }
282       else if (label == intervalString)
283       {
284 
285          interval = asInt(line.substr(0,6));
286 
287       }
288       else if (label == numMapsString)
289       {
290 
291          numMaps = asInt(line.substr(0,6));
292 
293       }
294       else if (label == mappingFunctionString)
295       {
296 
297          mappingFunction = strip(line.substr(0, 6));
298 
299       }
300       else if (label == elevationString)
301       {
302 
303          elevation = asDouble(line.substr(0, 8));
304 
305       }
306       else if (label == observablesUsedString)
307       {
308 
309          observablesUsed = strip(line.substr(0,60));
310 
311       }
312       else if (label == numStationsString)
313       {
314 
315          numStations = asInt(line.substr(0,6));
316 
317       }
318       else if (label == numSatsString)
319       {
320 
321          numSVs = asInt(line.substr(0,6));
322 
323       }
324       else if (label == baseRadiusString)
325       {
326 
327          baseRadius = asDouble(line.substr(0, 8));
328 
329       }
330       else if (label == mapDimensionString)
331       {
332 
333          mapDims = asInt(line.substr(0,6));
334 
335       }
336       else if (label == hgtGridString)
337       {
338 
339          hgt[0] = asDouble(line.substr( 2, 6));
340          hgt[1] = asDouble(line.substr( 8, 6));
341          hgt[2] = asDouble(line.substr(14, 6));
342 
343       }
344       else if (label == latGridString)
345       {
346 
347          lat[0] = asDouble(line.substr( 2, 6));
348          lat[1] = asDouble(line.substr( 8, 6));
349          lat[2] = asDouble(line.substr(14, 6));
350 
351       }
352       else if (label == lonGridString)
353       {
354 
355          lon[0] = asDouble(line.substr( 2, 6));
356          lon[1] = asDouble(line.substr( 8, 6));
357          lon[2] = asDouble(line.substr(14, 6));
358 
359       }
360       else if (label == exponentString)
361       {
362 
363          exponent = asInt(line.substr(0,6));
364 
365       }
366       else if (label == startAuxDataString)
367       {
368 
369          auxData = strip(line.substr(0,60));
370          auxDataFlag = true;
371 
372       }
373       else if (label == endOfHeader)
374       {
375 
376          auxDataFlag = true;
377          valid = true;
378 
379       }
380       else
381       {
382 
383          FFStreamError e("Unidentified IONEX header record: " + label);
384 
385          GPSTK_THROW(e);
386 
387       }
388 
389       return;
390 
391    }  // End of method 'IonexHeader::ParseHeaderRecord()'
392 
393 
394 
395       // This function parses the entire header from the given stream
reallyGetRecord(FFStream & ffs)396    void IonexHeader::reallyGetRecord(FFStream& ffs)
397    {
398 
399       IonexStream& strm = dynamic_cast<IonexStream&> (ffs);
400 
401          // if already read, just return
402       if (strm.headerRead == true)
403       {
404          return;
405       }
406 
407          // since we read a new header, we need to reinitialize
408          // all our list structures. All the other objects should be ok.
409          // This also applies if we threw an exception the first time we read
410          // the header and are now re-reading it. Some of these data
411          // structures could be full and we need to empty them.
412       clear();
413 
414       string line;
415 
416       while (!valid)
417       {
418 
419          strm.formattedGetLine(line);
420          StringUtils::stripTrailing(line);
421 
422             // skip empty lines
423          if (line.length() == 0)
424          {
425             continue;
426          }
427          else
428          {
429 
430             if (line.length() < 60 || line.length() > 80)
431             {
432 
433                FFStreamError e("Invalid line length");
434                GPSTK_THROW(e);
435 
436             }
437 
438          }  // End of 'if (line.length() == 0)...'
439 
440 
441          if (auxDataFlag)     // when it is set true, then parse auxiliar data
442          {
443 
444             try
445             {
446                ParseDcbRecord(line);
447             }
448             catch (FFStreamError& e)
449             {
450                GPSTK_RETHROW(e);
451             }
452 
453          }
454          else
455          {
456 
457             try
458             {
459                ParseHeaderRecord(line);
460             }
461             catch (FFStreamError& e)
462             {
463                GPSTK_RETHROW(e);
464             }
465 
466          }  // End of 'if (auxDataFlag)...'
467 
468       }  // End of 'while (!valid)...' (not for the header)
469 
470 
471          // Here come some validity checkings
472          // Checking ionex version
473       if (version != 1.0)
474       {
475          FFStreamError e( "Invalid IONEX version number " +
476                           asString(version));
477          GPSTK_THROW(e);
478       }
479 
480          // time arguments consistency
481       double interval0( (lastEpoch - firstEpoch) / (numMaps -1.0) );
482       if (interval != static_cast<int>(interval0))
483       {
484          FFStreamError e("Inconsistent time arguments.");
485          GPSTK_THROW(e);
486       }
487 
488          // map dimension consistency
489       if (mapDims == 2)
490       {
491 
492          if ( (hgt[0] != hgt[1]) || (hgt[2] != 0.0) )
493          {
494             FFStreamError e("Error concerning map dimension.");
495             GPSTK_THROW(e);
496          }
497 
498       }
499       else
500       {
501 
502          if ( (hgt[0] == hgt[1]) || (hgt[2] == 0.0) )
503          {
504             FFStreamError e("Error concerning map dimension.");
505             GPSTK_THROW(e);
506          }
507 
508       }  // End of 'if (mapDims == 2)...'
509 
510          // grid checkings
511       double grdfac[4];
512       try
513       {
514          grdfac[0] = lat[0]/lat[2];
515          grdfac[1] = lat[1]/lat[2];
516          grdfac[2] = lon[0]/lon[2];
517          grdfac[3] = lon[1]/lon[2];
518       }
519       catch(std::exception& e)
520       {
521          cerr << "Problems computing grdfac: " << e.what() << endl;
522          throw;
523       }
524 
525       for (int i = 0; i < 4; i++)
526       {
527          //const double xdif1( grdfac[i] - static_cast<int>(grdfac[i]) );
528          const double xdif( ABS(grdfac[i] - static_cast<int>(grdfac[i])) );
529 
530          if (xdif > 1e-4)
531          {
532             FFStreamError e("Irregular Ionex data grid.");
533             GPSTK_THROW(e);
534          }
535 
536       }  // End of 'for (int i = 0; i < 4; i++)...'
537 
538          // reach end of header line
539       strm.header = *this;
540       strm.headerRead = true;
541 
542       return;
543 
544    }  // End of method 'IonexHeader::reallyGetRecord()'
545 
546 
547 
reallyPutRecord(FFStream & ffs) const548    void IonexHeader::reallyPutRecord(FFStream& ffs) const
549    {
550 
551       IonexStream& strm = dynamic_cast<IonexStream&>(ffs);
552 
553       if (version != 1.0)
554       {
555          FFStreamError err( "Unknown IONEX version: " + asString(version,2) );
556          err.addText("Make sure to set the version correctly.");
557          GPSTK_THROW(err);
558       }
559 
560       try
561       {
562          WriteHeaderRecords(strm);
563       }
564       catch(FFStreamError& e)
565       {
566          GPSTK_RETHROW(e);
567       }
568       catch(StringException& e)
569       {
570          GPSTK_RETHROW(e);
571       }
572 
573    }  // End of method 'IonexHeader::reallyPutRecord()'
574 
575 
576       // this function writes all valid header records
WriteHeaderRecords(FFStream & ffs) const577    void IonexHeader::WriteHeaderRecords(FFStream& ffs) const
578    {
579       IonexStream& strm = dynamic_cast<IonexStream&>(ffs);
580       string line;
581 
582       if (valid)
583       {
584 
585             // write first IONEX record
586          line.clear();
587          line  = rightJustify(asString(version,1), 8);
588          line += string(12, ' ');
589          if ((fileType[0] != 'I') && (fileType[0] != 'i'))
590          {
591             FFStreamError err("This isn't a Ionex file: " +
592                               fileType.substr(0,1));
593             GPSTK_THROW(err);
594          }
595 
596          line += leftJustify(fileType, 20);
597          line += leftJustify(system, 20);
598          line += leftJustify(versionString,20);
599          strm << line << endl;
600          strm.lineNumber++;
601 
602 
603             // write second IONEX record
604          line.clear();
605          line += leftJustify(fileProgram,20);
606          line += leftJustify(fileAgency,20);
607          line += leftJustify(date,20);
608          line += leftJustify(runByString,20);
609          strm << line << endl;
610          strm.lineNumber++;
611 
612 
613             // write title (optional)
614          if( commentList.size() > 0 )
615          {
616             line.clear();
617             line += leftJustify(commentList[0],60);
618             line += leftJustify(commentString,20);
619             strm << line << endl;
620             strm.lineNumber++;
621          }
622 
623 
624             // write multi-line description (optional)
625          if (descriptionList.size() > 0)
626          {
627 
628             vector<std::string>::size_type i = 0;
629 
630             for( ; i < descriptionList.size(); i++)
631             {
632 
633                line.clear();
634                line += leftJustify(descriptionList[i],60);
635                line += leftJustify(descriptionString,20);
636                strm << line << endl;
637                strm.lineNumber++;
638 
639             }
640 
641          }  // End of 'if (descriptionList.size() > 0) ...'
642 
643 
644             // write epoch of first epoch
645          line.clear();
646          line += writeTime(firstEpoch);
647          line += string(24, ' ');
648          line += leftJustify(firstTimeString,20);
649          strm << line << endl;
650          strm.lineNumber++;
651 
652 
653             // write epoch of last epoch
654          line.clear();
655          line += writeTime(lastEpoch);
656          line += string(24, ' ');
657          line += leftJustify(lastTimeString,20);
658          strm << line << endl;
659          strm.lineNumber++;
660 
661 
662             // write interval
663          line.clear();
664          line += rightJustify( asString(interval), 6 );
665          line += string(54, ' ');
666          line += leftJustify(intervalString,20);
667          strm << line << endl;
668          strm.lineNumber++;
669 
670 
671             // write # of maps
672          line.clear();
673          line += rightJustify( asString<short>(numMaps), 6 );
674          line += string(54, ' ');
675          line += leftJustify(numMapsString,20);
676          strm << line << endl;
677          strm.lineNumber++;
678 
679 
680             // write mapping function
681          line.clear();
682          line += string(2, ' ');
683          line += rightJustify(mappingFunction, 4);
684          line += string(54, ' ');
685          line += leftJustify(mappingFunctionString,20);
686          strm << line << endl;
687          strm.lineNumber++;
688 
689 
690             // write elevation cutoff
691          line.clear();
692          line += rightJustify( asString(elevation,1), 8 );
693          line += string(52, ' ');
694          line += leftJustify(elevationString,20);
695          strm << line << endl;
696          strm.lineNumber++;
697 
698 
699             // write observables used
700          line.clear();
701          line += leftJustify(observablesUsed,60);
702          line += leftJustify(observablesUsedString,20);
703          strm << line << endl;
704          strm.lineNumber++;
705 
706 
707             // write # of stations (optional)
708          if (numStations > 0)
709          {
710             line.clear();
711             line += rightJustify( asString<short>(numStations), 6 );
712             line += string(54, ' ');
713             line += leftJustify(numStationsString,20);
714             strm << line << endl;
715             strm.lineNumber++;
716          }
717 
718 
719             // write # of satellites (optional)
720          if (numSVs > 0)
721          {
722             line.clear();
723             line += rightJustify( asString<short>(numSVs), 6 );
724             line += string(54, ' ');
725             line += leftJustify(numSatsString,20);
726             strm << line << endl;
727             strm.lineNumber++;
728          }
729 
730 
731             // write base radius
732          line.clear();
733          line += rightJustify( asString(baseRadius,1), 8 );
734          line += string(52, ' ');
735          line += leftJustify(baseRadiusString,20);
736          strm << line << endl;
737          strm.lineNumber++;
738 
739 
740             // write map dimension
741          line.clear();
742          line += rightJustify( asString(mapDims), 6 );
743          line += string(54, ' ');
744          line += leftJustify(mapDimensionString,20);
745          strm << line << endl;
746          strm.lineNumber++;
747 
748 
749             // write grid specifications
750          line.clear();
751          line += string(2, ' ');
752          line += rightJustify( asString(hgt[0],1), 6 );
753          line += rightJustify( asString(hgt[1],1), 6 );
754          line += rightJustify( asString(hgt[2],1), 6 );
755          line += string(40, ' ');
756          line += leftJustify(hgtGridString,20);
757          strm << line << endl;
758          strm.lineNumber++;
759 
760          line.clear();
761          line += string(2, ' ');
762          line += rightJustify( asString(lat[0],1), 6 );
763          line += rightJustify( asString(lat[1],1), 6 );
764          line += rightJustify( asString(lat[2],1), 6 );
765          line += string(40, ' ');
766          line += leftJustify(latGridString,20);
767          strm << line << endl;
768          strm.lineNumber++;
769 
770          line.clear();
771          line += string(2, ' ');
772          line += rightJustify( asString(lon[0],1), 6 );
773          line += rightJustify( asString(lon[1],1), 6 );
774          line += rightJustify( asString(lon[2],1), 6 );
775          line += string(40, ' ');
776          line += leftJustify(lonGridString,20);
777          strm << line << endl;
778          strm.lineNumber++;
779 
780 
781             // write default exponent (optional)
782          line.clear();
783          line += rightJustify( asString(exponent), 6 );
784          line += string(54, ' ');
785          line += leftJustify(exponentString,20);
786          strm << line << endl;
787          strm.lineNumber++;
788 
789 
790             // write multi-line comment
791          for( vector<std::string>::size_type i = 1;
792               i < commentList.size(); i++)
793          {
794             line.clear();
795             line += leftJustify(commentList[i],60);
796             line += leftJustify(commentString,20);
797             strm << line << endl;
798             strm.lineNumber++;
799          }
800 
801 
802             // write auxiliary data (optional)
803          if (auxDataFlag)
804          {
805 
806                // start of aux data
807             line.clear();
808             line += leftJustify(auxData,60);
809             line += leftJustify(startAuxDataString,20);
810             strm << line << endl;
811             strm.lineNumber++;
812 
813             IonexHeader::SatDCBMap::const_iterator isv = svsmap.begin();
814 
815             for(; isv != svsmap.end(); isv++)
816             {
817                line.clear();
818                line += isv->second.toString();
819                line += string(34, ' ');
820                line += leftJustify(DCB::svsAuxDataString,20);
821                strm << line << endl;
822                strm.lineNumber++;
823             }
824 
825                // end of aux data
826             line.clear();
827             line += leftJustify(auxData,60);
828             line += leftJustify(endAuxDataString,20);
829             strm << line << endl;
830             strm.lineNumber++;
831 
832          }  // End of 'if (auxDataFlag)...'
833 
834 
835             // write record closing Ionex header
836          line.clear();
837          line += string(60, ' ');
838          line += leftJustify(endOfHeader,20);
839          strm << line << endl;
840          strm.lineNumber++;
841 
842       }  // End of 'if (valid)...'
843    }
844 
845 
846       /* This function sets the time for this header.
847        *
848        * It looks at \a line to obtain the needed information.
849        */
parseTime(const string & line) const850    CommonTime IonexHeader::parseTime(const string& line) const
851    {
852 
853       int year, month, day, hour, min, sec;
854 
855       year  = asInt(line.substr( 0,6));
856       month = asInt(line.substr( 6,6));
857       day   = asInt(line.substr(12,6));
858       hour  = asInt(line.substr(18,6));
859       min   = asInt(line.substr(24,6));
860       sec   = asInt(line.substr(30,6));
861 
862       return CivilTime(year, month, day, hour, min, (double)sec);
863 
864    }  // End of method 'IonexHeader::parseTime()'
865 
866 
867          /** Converts the CommonTime \a dt into a Ionex Obs time
868           * string for the header
869           */
writeTime(const CommonTime & dt) const870    string IonexHeader::writeTime(const CommonTime& dt) const
871    {
872 
873       string line;
874 
875       line  = rightJustify(asString<short>(static_cast<CivilTime>(dt).year), 6);
876       line += rightJustify(asString<short>(static_cast<CivilTime>(dt).month), 6);
877       line += rightJustify(asString<short>(static_cast<CivilTime>(dt).day), 6);
878       line += rightJustify(asString<short>(static_cast<CivilTime>(dt).hour), 6);
879       line += rightJustify(asString<short>(static_cast<CivilTime>(dt).minute), 6);
880       line += rightJustify(asString (static_cast<int>(static_cast<CivilTime>(dt).second)), 6);
881 
882       return line;
883 
884    }  // End of method 'IonexHeader::writeTime()'
885 
886 
887 
888 }  // End of namespace gpstk
889