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