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 RinexNavData.cpp 41 * Encapsulates RINEX Navigation data 42 */ 43 44 #include "StringUtils.hpp" 45 46 #include "CommonTime.hpp" 47 #include "CivilTime.hpp" 48 #include "GPSWeekSecond.hpp" 49 50 #include "RinexNavData.hpp" 51 #include "RinexNavStream.hpp" 52 #include "GNSSconstants.hpp" 53 #include "TimeString.hpp" 54 55 namespace gpstk 56 { 57 using namespace gpstk::StringUtils; 58 using namespace std; 59 RinexNavData()60 RinexNavData::RinexNavData() 61 : time(gpstk::CommonTime::BEGINNING_OF_TIME), PRNID(-1), 62 sf1XmitTime(0), toeWeek(0), codeflgs(0), accuracy(0), 63 health(0), L2Pdata(0), IODC(0), IODE(0), af0(0), af1(0), af2(0), 64 Tgd(0), Cuc(0), Cus(0), Crc(0), Crs(0), Cic(0), Cis(0), Toe(0), 65 M0(0), dn(0), ecc(0), Ahalf(0), OMEGA0(0), i0(0), w(0), OMEGAdot(0), 66 idot(0), fitint(4) 67 { 68 time.setTimeSystem(gpstk::TimeSystem::GPS); 69 } 70 RinexNavData(const EngEphemeris & ee)71 RinexNavData::RinexNavData(const EngEphemeris& ee) 72 : time(ee.getEpochTime()), PRNID(ee.getPRNID()), sf1XmitTime(0), 73 toeWeek(0), codeflgs(ee.getCodeFlags()), accuracy(ee.getAccuracy()), 74 health(ee.getHealth()), L2Pdata(ee.getL2Pdata()), IODC(ee.getIODC()), 75 IODE(ee.getIODE()), af0(ee.getAf0()), af1(ee.getAf1()), 76 af2(ee.getAf2()), Tgd(ee.getTgd()), Cuc(ee.getCuc()), 77 Cus(ee.getCus()), Crc(ee.getCrc()), Crs(ee.getCrs()), 78 Cic(ee.getCic()), Cis(ee.getCis()), Toe(ee.getToe()), M0(ee.getM0()), 79 dn(ee.getDn()), ecc(ee.getEcc()), Ahalf(ee.getAhalf()), 80 OMEGA0(ee.getOmega0()), i0(ee.getI0()), w(ee.getW()), 81 OMEGAdot(ee.getOmegaDot()), idot(ee.getIDot()), 82 fitint(ee.getFitInterval()) 83 { 84 setXmitTime(ee.getFullWeek(), ee.getHOWTime(1)); 85 } 86 reallyPutRecord(FFStream & ffs) const87 void RinexNavData::reallyPutRecord(FFStream& ffs) const 88 { 89 RinexNavStream& strm = dynamic_cast<RinexNavStream&>(ffs); 90 91 strm << setw(2) << right << PRNID 92 << printTime(time, " %02y %2m %2d %2H %2M%5.1f") 93 << af0 << af1 << af2 << endlpp 94 << " " << IODE << Crs << dn << M0 << endlpp 95 << " " << Cuc << ecc << Cus << Ahalf << endlpp 96 << " " << Toe << Cic << OMEGA0 << Cis << endlpp 97 << " " << i0 << Crc << w << OMEGAdot << endlpp 98 << " " << idot << RNDouble(codeflgs) << RNDouble(toeWeek) 99 << RNDouble(L2Pdata) << endlpp 100 << " " << accuracy << RNDouble(health) << Tgd << IODC << endlpp 101 << " " << RNDouble(sf1XmitTime); 102 if (strm.header.version >= 2.1) 103 strm << fitint; 104 strm << endlpp; 105 } 106 reallyGetRecord(FFStream & ffs)107 void RinexNavData::reallyGetRecord(FFStream& ffs) 108 { 109 RinexNavStream& strm = dynamic_cast<RinexNavStream&>(ffs); 110 111 // If the header hasn't been read, read it... 112 if(!strm.headerRead) 113 strm >> strm.header; 114 115 string line; 116 117 strm.formattedGetLine(line, true); 118 getPRNEpoch(line); 119 120 strm.formattedGetLine(line); 121 getBroadcastOrbit1(line); 122 123 strm.formattedGetLine(line); 124 getBroadcastOrbit2(line); 125 126 strm.formattedGetLine(line); 127 getBroadcastOrbit3(line); 128 129 strm.formattedGetLine(line); 130 getBroadcastOrbit4(line); 131 132 strm.formattedGetLine(line); 133 getBroadcastOrbit5(line); 134 135 strm.formattedGetLine(line); 136 getBroadcastOrbit6(line); 137 138 strm.formattedGetLine(line); 139 getBroadcastOrbit7(line); 140 } 141 stableText() const142 std::string RinexNavData::stableText() const 143 { 144 ostringstream s; 145 s << "PRN: " << setw(2) << PRNID 146 << " TOE: " << printTime(getToeTime(), "%02m/%02d/%04Y %02H:%02M:%02S") 147 << " TOC: " << printTime(time, "%4F %10.3g") 148 << " IODE: " << setw(4) << int(IODE) // IODE should be int 149 << " HOWtime: " << setw(6) << getHOWWS().sow; // HOW should be double 150 return s.str(); 151 } 152 dump(ostream & s) const153 void RinexNavData::dump(ostream& s) const 154 { 155 s << "PRN: " << setw(2) << PRNID 156 << " TOE: " << printTime(getToeTime(), "%02m/%02d/%04Y %02H:%02M:%02S") 157 << " TOC: " << printTime(time, "%02m/%02d/%04Y %02H:%02M:%02S") 158 << " IODE: " << setw(4) << int(IODE) // IODE should be int 159 << " HOWtime: " << setw(6) << getHOWWS().sow // HOW should be double 160 << endl; 161 } 162 operator EngEphemeris() const163 RinexNavData::operator EngEphemeris() const throw() 164 { 165 EngEphemeris ee; 166 167 // there's no TLM word in RinexNavData, so it's set to 0. 168 // likewise, there's no AS alert or tracker. 169 // Also, in Rinex, the accuracy is in meters, and setSF1 expects 170 // the accuracy flag. We'll give it zero and pass the accuracy 171 // separately via the setAccuracy() method. 172 CommonTime how1(getHOWTime()), how2(how1+6), how3(how2+6); 173 GPSWeekSecond ws1(how1), ws2(how2), ws3(how3); 174 ee.setSF1(0, ws1.sow, 0, ws1.week, codeflgs, 0, health, 175 short(IODC), L2Pdata, Tgd, getTocWS().sow, af2, af1, af0, 0, 176 PRNID); 177 ee.setSF2(0, ws2.sow, 0, short(IODE), Crs, dn, M0, Cuc, ecc, Cus, Ahalf, 178 Toe, (fitint > 4) ? 1 : 0); 179 ee.setSF3(0, ws3.sow, 0, Cic, OMEGA0, Cis, i0, Crc, w, OMEGAdot, 180 idot); 181 ee.setFIC(false); 182 ee.setAccuracy(accuracy); 183 184 return ee; 185 } 186 187 // Convert this RinexNavData to a GPSEphemeris object. 188 // for backward compatibility only - use Rinex3NavData operator GPSEphemeris() const189 RinexNavData::operator GPSEphemeris() const 190 { 191 GPSEphemeris gpse; 192 try 193 { 194 // Overhead 195 gpse.satID = SatID(PRNID, SatelliteSystem::GPS); 196 gpse.ctToe = time; 197 198 // clock model 199 gpse.af0 = af0; 200 gpse.af1 = af1; 201 gpse.af2 = af2; 202 203 // Major orbit parameters 204 gpse.M0 = M0; 205 gpse.dn = dn; 206 gpse.ecc = ecc; 207 gpse.A = Ahalf * Ahalf; 208 gpse.OMEGA0 = OMEGA0; 209 gpse.i0 = i0; 210 gpse.w = w; 211 gpse.OMEGAdot = OMEGAdot; 212 gpse.idot = idot; 213 // modern nav msg 214 gpse.dndot = 0.; 215 gpse.Adot = 0.; 216 217 // Harmonic perturbations 218 gpse.Cuc = Cuc; 219 gpse.Cus = Cus; 220 gpse.Crc = Crc; 221 gpse.Crs = Crs; 222 gpse.Cic = Cic; 223 gpse.Cis = Cis; 224 225 gpse.dataLoadedFlag = true; 226 227 gpse.ctToc = time; 228 gpse.ctToc.setTimeSystem(TimeSystem::GPS); 229 230 // now load the GPS-specific parts 231 gpse.IODC = IODC; 232 gpse.IODE = IODE; 233 gpse.health = health; 234 gpse.accuracyFlag = accuracy; 235 gpse.Tgd = Tgd; 236 237 gpse.HOWtime = getHOWWS().sow; 238 gpse.transmitTime = getXmitTime(); 239 gpse.transmitTime.setTimeSystem(TimeSystem::GPS); 240 241 gpse.codeflags = codeflgs; 242 gpse.L2Pdata = L2Pdata; 243 244 /// @note IODC must be set first... 245 gpse.fitint = fitint; 246 gpse.setFitIntervalFlag(int(fitint)); 247 gpse.adjustValidity(); 248 } 249 catch(Exception& e) 250 { 251 GPSTK_RETHROW(e); 252 } 253 return gpse; 254 } 255 toList() const256 list<double> RinexNavData::toList() const 257 { 258 list<double> l; 259 GPSWeekSecond howws(getHOWWS()); 260 261 l.push_back(PRNID); 262 l.push_back(howws.sow); 263 l.push_back(howws.week); 264 l.push_back(codeflgs); 265 l.push_back(accuracy.val); 266 l.push_back(health); 267 l.push_back(L2Pdata); 268 l.push_back(IODC.val); 269 l.push_back(IODE.val); 270 l.push_back(getTocWS().sow); 271 l.push_back(af0.val); 272 l.push_back(af1.val); 273 l.push_back(af2.val); 274 l.push_back(Tgd.val); 275 l.push_back(Cuc.val); 276 l.push_back(Cus.val); 277 l.push_back(Crc.val); 278 l.push_back(Crs.val); 279 l.push_back(Cic.val); 280 l.push_back(Cis.val); 281 l.push_back(Toe.val); 282 l.push_back(M0.val); 283 l.push_back(dn.val); 284 l.push_back(ecc.val); 285 l.push_back(Ahalf.val); 286 l.push_back(OMEGA0.val); 287 l.push_back(i0.val); 288 l.push_back(w.val); 289 l.push_back(OMEGAdot.val); 290 l.push_back(idot.val); 291 l.push_back(fitint.val); 292 293 return l; 294 } 295 getPRNEpoch(const string & currentLine)296 void RinexNavData::getPRNEpoch(const string& currentLine) 297 { 298 try 299 { 300 // check for spaces in the right spots... 301 for (int i = 2; i <= 17; i += 3) 302 if (currentLine[i] != ' ') 303 throw(FFStreamError("Badly formatted line")); 304 305 PRNID = asInt(currentLine.substr(0,2)); 306 307 short yr = asInt(currentLine.substr(2,3)); 308 short mo = asInt(currentLine.substr(5,3)); 309 short day = asInt(currentLine.substr(8,3)); 310 short hr = asInt(currentLine.substr(11,3)); 311 short min = asInt(currentLine.substr(14,3)); 312 double sec = asDouble(currentLine.substr(17,5)); 313 314 // years 80-99 represent 1980-1999 315 const int rolloverYear = 80; 316 if (yr < rolloverYear) 317 yr += 100; 318 yr += 1900; 319 320 // Real Rinex has epochs 'yy mm dd hr 59 60.0' 321 // surprisingly often.... 322 double ds=0; 323 if(sec >= 60.) 324 { 325 ds=sec; 326 sec=0.0; 327 } 328 time = CivilTime(yr,mo,day,hr,min,sec,gpstk::TimeSystem::GPS).convertToCommonTime(); 329 if(ds != 0) time += ds; 330 331 af0 = currentLine.substr(22,19); 332 af1 = currentLine.substr(41,19); 333 af2 = currentLine.substr(60,19); 334 } 335 catch (std::exception &e) 336 { 337 FFStreamError err("std::exception: " + 338 string(e.what())); 339 GPSTK_THROW(err); 340 } 341 } 342 getBroadcastOrbit1(const string & currentLine)343 void RinexNavData::getBroadcastOrbit1(const string& currentLine) 344 { 345 try 346 { 347 IODE = currentLine.substr(3,19); 348 Crs = currentLine.substr(22,19); 349 dn = currentLine.substr(41,19); 350 M0 = currentLine.substr(60,19); 351 } 352 catch (std::exception &e) 353 { 354 FFStreamError err("std::exception: " + 355 string(e.what())); 356 GPSTK_THROW(err); 357 } 358 } 359 getBroadcastOrbit2(const string & currentLine)360 void RinexNavData::getBroadcastOrbit2(const string& currentLine) 361 { 362 try 363 { 364 Cuc = currentLine.substr(3,19); 365 ecc = currentLine.substr(22,19); 366 Cus = currentLine.substr(41,19); 367 Ahalf = currentLine.substr(60,19); 368 } 369 catch (std::exception &e) 370 { 371 FFStreamError err("std::exception: " + 372 string(e.what())); 373 GPSTK_THROW(err); 374 } 375 } 376 getBroadcastOrbit3(const string & currentLine)377 void RinexNavData::getBroadcastOrbit3(const string& currentLine) 378 { 379 try 380 { 381 Toe = currentLine.substr(3,19); 382 Cic = currentLine.substr(22,19); 383 OMEGA0 = currentLine.substr(41,19); 384 Cis = currentLine.substr(60,19); 385 } 386 catch (std::exception &e) 387 { 388 FFStreamError err("std::exception: " + 389 string(e.what())); 390 GPSTK_THROW(err); 391 } 392 } 393 getBroadcastOrbit4(const string & currentLine)394 void RinexNavData::getBroadcastOrbit4(const string& currentLine) 395 { 396 try 397 { 398 i0 = currentLine.substr(3,19); 399 Crc = currentLine.substr(22,19); 400 w = currentLine.substr(41,19); 401 OMEGAdot = currentLine.substr(60,19); 402 } 403 catch (std::exception &e) 404 { 405 FFStreamError err("std::exception: " + 406 string(e.what())); 407 GPSTK_THROW(err); 408 } 409 } 410 getBroadcastOrbit5(const string & currentLine)411 void RinexNavData::getBroadcastOrbit5(const string& currentLine) 412 { 413 try 414 { 415 RNDouble codeL2(0), L2P(0), toe_wn(0); 416 417 idot = currentLine.substr(3,19); 418 codeL2 = currentLine.substr(22,19); 419 toe_wn = currentLine.substr(41,19); 420 L2P = currentLine.substr(60,19); 421 422 codeflgs = (short) codeL2; 423 L2Pdata = (short) L2P; 424 toeWeek = (short) toe_wn; 425 } 426 catch (std::exception &e) 427 { 428 FFStreamError err("std::exception: " + 429 string(e.what())); 430 GPSTK_THROW(err); 431 } 432 } 433 getBroadcastOrbit6(const string & currentLine)434 void RinexNavData::getBroadcastOrbit6(const string& currentLine) 435 { 436 try 437 { 438 RNDouble SV_health(0); 439 440 accuracy = currentLine.substr(3,19); 441 SV_health = currentLine.substr(22,19); 442 Tgd = currentLine.substr(41,19); 443 IODC = currentLine.substr(60,19); 444 445 446 health = (short) SV_health; 447 } 448 catch (std::exception &e) 449 { 450 FFStreamError err("std::exception: " + 451 string(e.what())); 452 GPSTK_THROW(err); 453 } 454 } 455 getBroadcastOrbit7(const string & currentLine)456 void RinexNavData::getBroadcastOrbit7(const string& currentLine) 457 { 458 try 459 { 460 RNDouble HOW_sec(0); 461 462 HOW_sec = currentLine.substr(3,19); 463 // leave it alone so round-trips are possible 464 // (even though we're storing a double as a long, which 465 //could lead to failures in round-trip testing, though if 466 //that happens your transmit time is messed). 467 //setXmitTime(HOW_sec); 468 sf1XmitTime = HOW_sec; 469 fitint = currentLine.substr(22,19); 470 } 471 catch (std::exception &e) 472 { 473 FFStreamError err("std::exception: " + 474 string(e.what())); 475 GPSTK_THROW(err); 476 } 477 } 478 479 getXmitWS() const480 GPSWeekSecond RinexNavData::getXmitWS() const 481 { 482 GPSWeekSecond rv; 483 // sf1XmitTime may not actually be a proper subframe 1 484 // transmit time. It may be a HOW time or something like 485 // that. 486 if (sf1XmitTime < 0) 487 { 488 // If the transmit time is negative, assume that it 489 // corresponds to the Toe week, according to the footnote 490 // attached to Table A4 in the 2.11 standard. 491 long properXmit = fixSF1xmitSOW(sf1XmitTime+FULLWEEK); 492 rv = GPSWeekSecond(toeWeek-1, properXmit, TimeSystem::GPS); 493 } 494 else 495 { 496 // If the transmit time is >= 0, make sure that we have 497 // the right week using a trusty old half-week test. 498 long properXmit = fixSF1xmitSOW(sf1XmitTime); 499 double diff = Toe - properXmit; 500 if (diff < -HALFWEEK) 501 rv = GPSWeekSecond(toeWeek-1, properXmit, TimeSystem::GPS); 502 else if (diff > HALFWEEK) 503 rv = GPSWeekSecond(toeWeek+1, properXmit, TimeSystem::GPS); 504 else 505 rv = GPSWeekSecond(toeWeek, properXmit, TimeSystem::GPS); 506 } 507 return rv; 508 } 509 510 setXmitWeek(unsigned short fullweek)511 RinexNavData& RinexNavData::setXmitWeek(unsigned short fullweek) 512 { 513 if (sf1XmitTime < 0) 514 { 515 // If the transmit time is negative, assume that the 516 // transmit week corresponds to the Toe week, according to 517 // the footnote attached to Table A4 in the 2.11 standard. 518 toeWeek = fullweek; 519 } 520 else 521 { 522 // If the transmit time is >= 0, make sure that we have 523 // the right week using a trusty old half-week test. 524 double diff = Toe - sf1XmitTime; 525 if (diff < -HALFWEEK) 526 { 527 toeWeek = fullweek+1; 528 // adjust transmit time to be relative to the week. 529 // week is in broadcast orbit 5 530 // transmission time is in broadcast orbit 7 531 // see footnote in RINEX 2.11 document 532 sf1XmitTime -= FULLWEEK; 533 } 534 else if (diff > HALFWEEK) 535 { 536 toeWeek = fullweek-1; 537 // see comments above 538 sf1XmitTime += FULLWEEK; 539 } 540 else 541 toeWeek = fullweek; 542 } 543 return *this; 544 } 545 546 setXmitTime(unsigned short fullweek,unsigned long sow)547 RinexNavData& RinexNavData::setXmitTime(unsigned short fullweek, 548 unsigned long sow) 549 { 550 setXmitTime(sow); 551 setXmitWeek(fullweek); 552 return *this; 553 } 554 555 556 } // end of namespace 557