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