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 /// @file SP3Data.cpp 40 /// Encapsulate SP3 file data, including I/O 41 42 #include "SP3Stream.hpp" 43 #include "SP3Header.hpp" 44 #include "SP3Data.hpp" 45 #include "StringUtils.hpp" 46 #include "CivilTime.hpp" 47 #include "GPSWeekSecond.hpp" 48 49 using namespace gpstk::StringUtils; 50 using namespace std; 51 52 namespace gpstk 53 { reallyGetRecord(FFStream & ffs)54 void SP3Data::reallyGetRecord(FFStream& ffs) 55 { 56 // cast the stream to be an SP3Stream 57 SP3Stream& strm = dynamic_cast<SP3Stream&>(ffs); 58 59 // status says which records have been written already this call 60 // status = 1 if an epoch record was read 61 // status = 2 if an P or V record was read 62 // status = 3 if an EP or EV record was read 63 int status = 0; 64 65 // version to be written out is determined by written header (stored in strm) 66 bool isVerA = (strm.header.getVersion() == SP3Header::SP3a); 67 bool isVerC = (strm.header.getVersion() == SP3Header::SP3c) || (strm.header.getVersion() == SP3Header::SP3d); 68 69 // correlation flag will be set if there is an EP/EV record 70 correlationFlag = false; 71 72 // default for the 'c' arrays is all zeros 73 sdev[0] = sdev[1] = sdev[2] = sdev[3] = 0; 74 sig[0] = sig[1] = sig[2] = sig[3] = 0; 75 correlation[0] = correlation[1] = correlation[2] 76 = correlation[3] = correlation[4] = correlation[5] = 0; 77 78 // TimeSystem for this stream 79 TimeSystem timeSystem; 80 timeSystem = gpstk::StringUtils::asTimeSystem(strm.header.timeSystemString()); 81 82 // loop until an error occurs, or until the entire record (which may consist 83 // of two lines) is read. 84 bool unexpectedEOF=false; 85 while(1) { 86 87 // set the time to be the last epoch read by the stream 88 time = strm.currentEpoch; 89 90 // process the lastLine string stored in strm - contains the last line read 91 // empty record 92 if(strm.lastLine.size() < 3) { // 3 b/c it may contain "EOF" 93 // nothing in lastLine - do nothing here, get another line 94 ; 95 } 96 97 // EOF has been read 98 else if(strm.lastLine.substr(0,3) == string("EOF")) { 99 // if a data record has already been processed during this call, 100 // then return here, and let the next call process this EOF. 101 // This gives the caller a chance to process the data before hitting EOF. 102 103 // if an epoch record was processed this call, that's an error 104 if(status == 1) { 105 FFStreamError ffse("EOF was found immediately after epoch line"); 106 GPSTK_THROW(ffse); 107 } 108 109 // some other record was processed this call, so quit 110 if(status > 1) break; 111 112 // this next read had better fail - if it does not, then there is 113 // an "EOF" that is followed by something other than the file EOF. 114 try { 115 strm.formattedGetLine(strm.lastLine, true); // true is 'expect EOF' 116 } 117 catch(EndOfFile& err) { 118 break; // normal exit; the stream will now be at eof() 119 } 120 121 // the GetLine succeeded, so this is an error 122 FFStreamError err("EOF text found, followed by: " + strm.lastLine); 123 GPSTK_THROW(err); 124 } 125 126 // Epoch line read 127 else if(strm.lastLine[0] == '*') { 128 129 // if an epoch record was already processed this call, that's an error 130 // TD consider simply removing this if(status==1) entirely. Some SP3 files 131 // particularly those generated from a realtime stream, have consecutive 132 // epoch lines. Why would we not just ignore the epoch with no data? 133 if(status == 1) { 134 FFStreamError ffse("Consecutive epoch records found"); 135 GPSTK_THROW(ffse); 136 } 137 138 // if another record has been process during this call, quit now 139 if(status > 1) break; 140 141 // process an epoch record 142 status = 1; 143 144 // mark this record as non-data, in case another P|V record is not read 145 sat = SatID(); 146 147 // warn if the line is short but not too short 148 if(strm.lastLine.size() <= 30 && strm.lastLine.size() > 26) 149 strm.warnings.push_back(string("Warning (SP3 std): short epoch line: ") 150 + strm.lastLine); 151 152 // throw if the line is short 153 if(strm.lastLine.size() <= 26) { // some igs files cut seconds short 30) 154 FFStreamError err("Invalid line length " 155 + asString(strm.lastLine.size())); 156 GPSTK_THROW(err); 157 } 158 159 // parse the epoch line 160 RecType = strm.lastLine[0]; 161 int year = asInt(strm.lastLine.substr(3,4)); 162 int month = asInt(strm.lastLine.substr(8,2)); 163 int dom = asInt(strm.lastLine.substr(11,2)); 164 int hour = asInt(strm.lastLine.substr(14,2)); 165 int minute = asInt(strm.lastLine.substr(17,2)); 166 double second = asInt(strm.lastLine.substr(20,10)); 167 CivilTime t; 168 try { 169 t = CivilTime(year, month, dom, hour, minute, second, timeSystem); 170 } 171 catch (gpstk::Exception& e) { 172 FFStreamError fe("Invalid time in:" + strm.lastLine); 173 GPSTK_THROW(fe); 174 } 175 time = strm.currentEpoch = static_cast<CommonTime>(t); 176 } 177 178 // P or V record read 179 else if(strm.lastLine[0] == 'P' || strm.lastLine[0] == 'V') { 180 181 // if another record has been process during this call, quit now 182 if(status > 0) break; 183 184 // process this P|V 185 status = 2; 186 RecType = strm.lastLine[0]; // P or V 187 188 // if its version c and the line is short (<80) but valid(>59), 189 // then add blanks at the end to make it 80 character. 190 if (isVerC && strm.lastLine.size() < 80 && strm.lastLine.size() > 59) 191 leftJustify(strm.lastLine,80); 192 193 // throw if the line is short 194 if ((!isVerC && strm.lastLine.size() < 60) || 195 (isVerC && strm.lastLine.size() < 80) ) { 196 FFStreamError err("Invalid line length (" 197 + asString(strm.lastLine.size()) 198 + ") for line:\n" + strm.lastLine); 199 GPSTK_THROW(err); 200 } 201 202 // parse the line 203 sat = static_cast<SatID>(SP3SatID(strm.lastLine.substr(1,3))); 204 205 x[0] = asDouble(strm.lastLine.substr(4,14)); // XYZ 206 x[1] = asDouble(strm.lastLine.substr(18,14)); 207 x[2] = asDouble(strm.lastLine.substr(32,14)); 208 clk = asDouble(strm.lastLine.substr(46,14)); // Clock 209 210 // handle NGA extension to SP3a - the event flag 211 eventFlag = false; 212 if (isVerA && (RecType == 'P') 213 && (strm.lastLine.size() >= 75) 214 && (strm.lastLine[74] == 'E')) 215 { 216 eventFlag = true; 217 strm.header.allowSP3aEvents = true; 218 } 219 220 // the rest is version c only 221 if(isVerC) { 222 sig[0] = asInt(strm.lastLine.substr(61,2)); // sigma XYZ 223 sig[1] = asInt(strm.lastLine.substr(64,2)); 224 sig[2] = asInt(strm.lastLine.substr(67,2)); 225 sig[3] = asInt(strm.lastLine.substr(70,3)); // sigma clock 226 227 if(RecType == 'P') { // P flags 228 clockEventFlag = clockPredFlag 229 = orbitManeuverFlag = orbitPredFlag = false; 230 if(strm.lastLine[74] == 'E') clockEventFlag = true; 231 if(strm.lastLine[75] == 'P') clockPredFlag = true; 232 if(strm.lastLine[78] == 'M') orbitManeuverFlag = true; 233 if(strm.lastLine[79] == 'P') orbitPredFlag = true; 234 } 235 } 236 } 237 238 // EP or EV correlation record read 239 else if(strm.lastLine[0] == 'E' && 240 (strm.lastLine[1] == 'P' || strm.lastLine[1] == 'V')) 241 { 242 // throw if correlation record did not follow corresponding P|V record 243 if(status != 2 || strm.lastLine[1] != RecType) { 244 Exception e("correlation EP|V record mismatched with previous record"); 245 GPSTK_THROW(e); 246 } 247 248 // process EP|V record 249 status = 3; 250 251 // throw if line is short 252 if(strm.lastLine.size()<80) { 253 FFStreamError err("Invalid SP3c correlation line length (" 254 + asString(strm.lastLine.size()) 255 + ") for line:\n" + strm.lastLine); 256 GPSTK_THROW(err); 257 } 258 259 // parse the line 260 sdev[0] = abs(asInt(strm.lastLine.substr(4,4))); 261 sdev[1] = abs(asInt(strm.lastLine.substr(9,4))); 262 sdev[2] = abs(asInt(strm.lastLine.substr(14,4))); 263 sdev[3] = abs(asInt(strm.lastLine.substr(19,7))); 264 correlation[0] = asInt(strm.lastLine.substr(27,8)); 265 correlation[1] = asInt(strm.lastLine.substr(36,8)); 266 correlation[2] = asInt(strm.lastLine.substr(45,8)); 267 correlation[3] = asInt(strm.lastLine.substr(54,8)); 268 correlation[4] = asInt(strm.lastLine.substr(63,8)); 269 correlation[5] = asInt(strm.lastLine.substr(72,8)); 270 271 // tell the caller that correlation data is now present 272 correlationFlag = true; 273 } 274 275 else { // Unknown record 276 FFStreamError err("Unknown record label " + strm.lastLine.substr(0,2)); 277 GPSTK_THROW(err); 278 } 279 280 // be tolerant of files without EOF -- IGS! 281 // if previous iteration of the loop found unexpected EOF, then quit here. 282 if(unexpectedEOF) { 283 // add a warning 284 strm.warnings.push_back(string("Warning (SP3 std): Unexpected EOF")); 285 286 // clear the buffer so it won't be (re)processed next call 287 strm.lastLine = string("EOF"); 288 289 // clear the eof bit and return, so the user will process this SP3Data 290 strm.clear(ios::eofbit); 291 status = 3; 292 } 293 else { // normal flow 294 // read next line into the lastLine 295 try { 296 strm.formattedGetLine(strm.lastLine); 297 } 298 catch(FFStreamError& err) { 299 string what = err.what().substr(0,21); 300 //cout << "Found unexpected EOF" << endl; 301 //cout << "what is " << what << endl; 302 //cout << "Here is the buffer:\n" << strm.lastLine << endl; 303 if(what == string("text 0:Unexpected EOF")) { 304 unexpectedEOF = true; 305 306 // there could still be unprocessed data in the buffer: 307 if(strm.lastLine.size() < 3) status = 3; // nothing there, quit 308 else status = 0; // go back and process it 309 } 310 else 311 GPSTK_RETHROW(err); 312 } 313 } 314 315 if(status == 3) break; // quit if found EOF or EP|EV was processed, but 316 // go back if lastLine was empty (0) 317 // or if epoch was processed (1) 318 // or if P|V was processed (2) 319 320 } // end while loop processing records 321 322 } // end reallyGetRecord() 323 reallyPutRecord(FFStream & ffs) const324 void SP3Data::reallyPutRecord(FFStream& ffs) const 325 { 326 string line; 327 328 // cast the stream to be an SP3Stream 329 SP3Stream& strm = dynamic_cast<SP3Stream&>(ffs); 330 331 // version to be written out is determined by written header (stored in strm) 332 bool isVerA = (strm.header.getVersion() == SP3Header::SP3a); 333 bool isVerC = (strm.header.getVersion() == SP3Header::SP3c) || (strm.header.getVersion() == SP3Header::SP3d); 334 335 // output Epoch Header Record 336 if(RecType == '*') { 337 CivilTime civTime(time); 338 line = "* "; 339 line += civTime.printf(" %4Y %2m %2d %2H %2M"); 340 line += " " + rightJustify(civTime.printf("%.8f"),11); 341 } 342 343 // output Position and Clock OR Velocity and Clock Rate Record 344 else { 345 line = RecType; // P or V 346 if (isVerA) { 347 if(sat.system != SatelliteSystem::GPS) { 348 FFStreamError fse("Cannot output non-GPS to SP3a"); 349 GPSTK_THROW(fse); 350 } 351 line += rightJustify(asString(sat.id),3); 352 } 353 else 354 line += static_cast<SP3SatID>(sat).toString(); // sat ID 355 356 line += rightJustify(asString(x[0],6),14); // XYZ 357 line += rightJustify(asString(x[1],6),14); 358 line += rightJustify(asString(x[2],6),14); 359 line += rightJustify(asString(clk,6),14); // Clock 360 361 // handle NGA extension to SP3a 362 if(isVerA && strm.header.allowSP3aEvents 363 && (RecType == 'P') && eventFlag) 364 { 365 line += string(14,' '); 366 line += 'E'; 367 } 368 369 if(isVerC) { 370 line += rightJustify(asString(sig[0]),3); // sigma XYZ 371 line += rightJustify(asString(sig[1]),3); 372 line += rightJustify(asString(sig[2]),3); 373 line += rightJustify(asString(sig[3]),4); // sigma Clock 374 375 if(RecType == 'P') { // flags or blanks 376 line += string(" "); 377 line += (clockEventFlag ? string("E") : string(" ")); 378 line += (clockPredFlag ? string("P") : string(" ")); 379 line += string(" "); 380 line += (orbitManeuverFlag ? string("M") : string(" ")); 381 line += (orbitPredFlag ? string("P") : string(" ")); 382 } 383 } 384 385 // if version is 'c' and correlation flag is set, 386 // then output the P|V Correlation Record 387 if(isVerC && correlationFlag) { 388 389 // first output the P|V record you just built 390 strm << line << endl; 391 strm.lineNumber++; 392 393 // now build and output the correlation record 394 if(RecType == 'P') // P or V 395 line = "EP "; 396 else 397 line = "EV "; 398 line += rightJustify(asString(sdev[0]),5); // stddev X 399 line += rightJustify(asString(sdev[1]),5); // stddev Y 400 line += rightJustify(asString(sdev[2]),5); // stddev Z 401 line += rightJustify(asString(sdev[3]),8); // stddev Clk 402 for(int i=0; i<6; i++) // correlations 403 line += rightJustify(asString(correlation[i]),9); 404 } 405 } 406 407 // write the line just built 408 strm << line << endl; 409 strm.lineNumber++; 410 411 } // end reallyPutRecord() 412 dump(ostream & s,bool includeC) const413 void SP3Data::dump(ostream& s, bool includeC) const throw() 414 { 415 // dump record type (PV*), sat id, and current epoch 416 s << RecType << " " << static_cast<SP3SatID>(sat).toString() << " " 417 << (static_cast<CivilTime>(time)).printf("%Y/%02m/%02d %2H:%02M:%06.3f") 418 << " = " 419 << (static_cast<GPSWeekSecond>(time)).printf("%F/%10.3g"); 420 421 if(RecType != '*') { // not epoch line 422 s << fixed << setprecision(6) 423 << " X=" << setw(14) << x[0] // XYZ 424 << " Y=" << setw(14) << x[1] 425 << " Z=" << setw(14) << x[2] 426 << " C=" << setw(14) << clk; // clk 427 428 if(includeC) { 429 s << " sX=" << setw(2) << sig[0] // sigma XYZ 430 << " sY=" << setw(2) << sig[1] 431 << " sZ=" << setw(2) << sig[2] 432 << " sC=" << setw(3) << sig[3]; // sigma clock 433 434 if(RecType == 'P') // flags 435 s << " " << (clockEventFlag ? "clockEvent" : "-") 436 << " " << (clockPredFlag ? "clockPrediction" : "-") 437 << " " << (orbitManeuverFlag ? "orbitManeuver" : "-") 438 << " " << (orbitPredFlag ? "orbitPrediction" : "-"); 439 440 if(correlationFlag) // stddevs and correlations 441 s << endl 442 << " and E" << RecType 443 << " cXX=" << setw(4) << sdev[0] 444 << " cYY=" << setw(4) << sdev[1] 445 << " cZZ=" << setw(4) << sdev[2] 446 << " cCC=" << setw(7) << sdev[3] 447 << " cXY=" << setw(8) << correlation[0] 448 << " cXZ=" << setw(8) << correlation[1] 449 << " cXC=" << setw(8) << correlation[2] 450 << " cYZ=" << setw(8) << correlation[3] 451 << " cYC=" << setw(8) << correlation[4] 452 << " cZC=" << setw(8) << correlation[5]; 453 } 454 } 455 456 s << endl; 457 458 } // end dump() 459 460 } // namespace 461