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