1   /* Copyright 2002-2012 Space Applications Services
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.sp3;
18  
19  import java.io.BufferedReader;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.util.Locale;
26  import java.util.Scanner;
27  
28  import org.hipparchus.exception.DummyLocalizable;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.util.FastMath;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.files.general.OrbitFile.TimeSystem;
34  import org.orekit.files.general.OrbitFileParser;
35  import org.orekit.files.general.SatelliteInformation;
36  import org.orekit.files.general.SatelliteTimeCoordinate;
37  import org.orekit.files.sp3.SP3File.SP3FileType;
38  import org.orekit.files.sp3.SP3File.SP3OrbitType;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.TimeScale;
41  import org.orekit.time.TimeScalesFactory;
42  import org.orekit.utils.PVCoordinates;
43  
44  /** A parser for the SP3 orbit file format. It supports the original format as
45   * well as the latest SP3-c version.
46   * <p>
47   * <b>Note:</b> this parser is thread-safe, so calling {@link #parse} from
48   * different threads is allowed.
49   * </p>
50   * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3_docu.txt">SP3-a file format</a>
51   * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3c.txt">SP3-c file format</a>
52   * @author Thomas Neidhart
53   */
54  public class SP3Parser implements OrbitFileParser {
55  
56      /** {@inheritDoc} */
57      public SP3File parse(final String fileName) throws OrekitException {
58  
59          InputStream stream = null;
60  
61          try {
62              stream = new FileInputStream(fileName);
63              return parse(stream);
64          } catch (FileNotFoundException e) {
65              throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
66          } finally {
67              try {
68                  if (stream != null) {
69                      stream.close();
70                  }
71              } catch (IOException e) {
72                  // ignore
73              }
74          }
75      }
76  
77      /** {@inheritDoc} */
78      public SP3File parse(final InputStream stream) throws OrekitException {
79          try {
80              return parseInternal(stream);
81          } catch (IOException e) {
82              throw new OrekitException(e, new DummyLocalizable(e.getMessage()));
83          }
84      }
85  
86      /** Parses the SP3 file from the given {@link InputStream} and
87       * returns a {@link SP3File} object.
88       * @param stream the stream to read the SP3File from
89       * @return the parsed {@link SP3File} object
90       * @throws OrekitException if the file could not be parsed successfully
91       * @throws IOException if an error occurs while reading from the stream
92       */
93      private SP3File parseInternal(final InputStream stream)
94          throws OrekitException, IOException {
95  
96          final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
97  
98          // initialize internal data structures
99          final ParseInfo pi = new ParseInfo();
100 
101         String line = null;
102         int lineNumber = 1;
103         try {
104             while (lineNumber < 23) {
105                 line = reader.readLine();
106                 if (line == null) {
107                     throw new OrekitException(OrekitMessages.SP3_UNEXPECTED_END_OF_FILE, lineNumber - 1);
108                 } else {
109                     parseHeaderLine(lineNumber++, line, pi);
110                 }
111             }
112 
113             // now handle the epoch/position/velocity entries
114 
115             boolean done = false;
116             do {
117                 line = reader.readLine();
118                 if (line == null || "EOF".equalsIgnoreCase(line.trim())) {
119                     done = true;
120                 } else if (line.length() > 0) {
121                     parseContentLine(line, pi);
122                 }
123             } while (!done);
124         } finally {
125             try {
126                 reader.close();
127             } catch (IOException e1) {
128                 // ignore
129             }
130         }
131 
132         return pi.file;
133     }
134 
135     /** Parses a header line from the SP3 file (line number 1 - 22).
136      * @param lineNumber the current line number
137      * @param line the line as read from the SP3 file
138      * @param pi the current {@link ParseInfo} object
139      * @throws OrekitException if a non-supported construct is found
140      */
141     private void parseHeaderLine(final int lineNumber, final String line, final ParseInfo pi)
142         throws OrekitException {
143 
144         final SP3File file = pi.file;
145 
146         try (final Scanner s1      = new Scanner(line);
147              final Scanner s2      = s1.useDelimiter("\\s+");
148              final Scanner scanner = s2.useLocale(Locale.US)) {
149 
150             // CHECKSTYLE: stop FallThrough check
151 
152             switch (lineNumber) {
153 
154                 // version, epoch, data used and agency information
155                 case 1: {
156                     scanner.skip("#");
157                     final String v = scanner.next();
158 
159                     final char version = v.substring(0, 1).toLowerCase().charAt(0);
160                     if (version != 'a' && version != 'b' && version != 'c') {
161                         throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_VERSION, version);
162                     }
163 
164                     pi.hasVelocityEntries = "V".equals(v.substring(1, 2));
165 
166                     final int year = Integer.parseInt(v.substring(2));
167                     final int month = scanner.nextInt();
168                     final int day = scanner.nextInt();
169                     final int hour = scanner.nextInt();
170                     final int minute = scanner.nextInt();
171                     final double second = scanner.nextDouble();
172 
173                     final AbsoluteDate epoch = new AbsoluteDate(year, month, day,
174                                                                 hour, minute, second,
175                                                                 TimeScalesFactory.getGPS());
176 
177                     file.setEpoch(epoch);
178 
179                     final int numEpochs = scanner.nextInt();
180                     file.setNumberOfEpochs(numEpochs);
181 
182                     // data used indicator
183                     file.setDataUsed(scanner.next());
184 
185                     file.setCoordinateSystem(scanner.next());
186                     file.setOrbitType(SP3OrbitType.parseType(scanner.next()));
187                     file.setAgency(scanner.next());
188                     break;
189                 }
190 
191                 // additional date/time references in gps/julian day notation
192                 case 2: {
193                     scanner.skip("##");
194 
195                     // gps week
196                     file.setGpsWeek(scanner.nextInt());
197                     // seconds of week
198                     file.setSecondsOfWeek(scanner.nextDouble());
199                     // epoch interval
200                     file.setEpochInterval(scanner.nextDouble());
201                     // julian day
202                     file.setJulianDay(scanner.nextInt());
203                     // day fraction
204                     file.setDayFraction(scanner.nextDouble());
205                     break;
206                 }
207 
208                 // line 3 contains the number of satellites
209                 case 3:
210                     pi.maxSatellites = Integer.parseInt(line.substring(4, 6).trim());
211                     // fall-through intended - the line contains already the first entries
212 
213                     // the following 4 lines contain additional satellite ids
214                 case 4:
215                 case 5:
216                 case 6:
217                 case 7: {
218                     final int lineLength = line.length();
219                     int count = file.getSatelliteCount();
220                     int startIdx = 9;
221                     while (count++ < pi.maxSatellites && (startIdx + 3) <= lineLength) {
222                         final String satId = line.substring(startIdx, startIdx + 3).trim();
223                         file.addSatellite(satId);
224                         startIdx += 3;
225                     }
226                     break;
227                 }
228 
229                 // the following 5 lines contain general accuracy information for each satellite
230                 case 8:
231                 case 9:
232                 case 10:
233                 case 11:
234                 case 12: {
235                     final int lineLength = line.length();
236                     int satIdx = (lineNumber - 8) * 17;
237                     int startIdx = 9;
238                     while (satIdx < pi.maxSatellites && (startIdx + 3) <= lineLength) {
239                         final SatelliteInformation satInfo = file.getSatellite(satIdx++);
240                         final int exponent = Integer.parseInt(line.substring(startIdx, startIdx + 3).trim());
241                         // the accuracy is calculated as 2**exp (in m) -> can be safely
242                         // converted to an integer as there will be no fraction
243                         satInfo.setAccuracy((int) FastMath.pow(2d, exponent));
244                         startIdx += 3;
245                     }
246                     break;
247                 }
248 
249                 case 13: {
250                     file.setType(getFileType(line.substring(3, 5).trim()));
251 
252                     // now identify the time system in use
253                     final String tsStr = line.substring(9, 12).trim();
254                     final TimeSystem ts;
255                     if (tsStr.equalsIgnoreCase("ccc")) {
256                         ts = TimeSystem.GPS;
257                     } else {
258                         ts = TimeSystem.valueOf(tsStr);
259                     }
260                     file.setTimeSystem(ts);
261 
262                     switch (ts) {
263                         case GPS:
264                             pi.timeScale = TimeScalesFactory.getGPS();
265                             break;
266 
267                         case GAL:
268                             pi.timeScale = TimeScalesFactory.getGST();
269                             break;
270 
271                         case GLO:
272                             pi.timeScale = TimeScalesFactory.getGLONASS();
273                             break;
274 
275                         case QZS:
276                             pi.timeScale = TimeScalesFactory.getQZSS();
277 
278                         case TAI:
279                             pi.timeScale = TimeScalesFactory.getTAI();
280                             break;
281 
282                         case UTC:
283                             pi.timeScale = TimeScalesFactory.getUTC();
284                             break;
285 
286                         default:
287                             pi.timeScale = TimeScalesFactory.getGPS();
288                             break;
289                     }
290                     break;
291                 }
292 
293                 case 14:
294                     // ignore additional custom fields
295                     break;
296 
297                     // load base numbers for the standard deviations of
298                     // position/velocity/clock components
299                 case 15: {
300                     // String base = line.substring(3, 13).trim();
301                     // if (!base.equals("0.0000000")) {
302                     //    // (mm or 10**-4 mm/sec)
303                     //    pi.posVelBase = Double.valueOf(base);
304                     // }
305 
306                     // base = line.substring(14, 26).trim();
307                     // if (!base.equals("0.000000000")) {
308                     //    // (psec or 10**-4 psec/sec)
309                     //    pi.clockBase = Double.valueOf(base);
310                     // }
311                     break;
312                 }
313 
314                 case 16:
315                 case 17:
316                 case 18:
317                     // ignore additional custom parameters
318                     break;
319 
320                 case 19:
321                 case 20:
322                 case 21:
323                 case 22:
324                     // ignore comment lines
325                     break;
326                 default:
327                     // ignore -> method should only be called up to line 22
328                     break;
329             }
330 
331             // CHECKSTYLE: resume FallThrough check
332 
333         }
334 
335     }
336 
337     /** Parses a single content line as read from the SP3 file.
338      * @param line a string containing the line
339      * @param pi the current {@link ParseInfo} object
340      */
341     private void parseContentLine(final String line, final ParseInfo pi) {
342         // EP and EV lines are ignored so far
343 
344         final SP3File file = pi.file;
345 
346         switch (line.charAt(0)) {
347             case '*': {
348                 final int year = Integer.parseInt(line.substring(3, 7).trim());
349                 final int month = Integer.parseInt(line.substring(8, 10).trim());
350                 final int day = Integer.parseInt(line.substring(11, 13).trim());
351                 final int hour = Integer.parseInt(line.substring(14, 16).trim());
352                 final int minute = Integer.parseInt(line.substring(17, 19).trim());
353                 final double second = Double.parseDouble(line.substring(20, 31).trim());
354 
355                 pi.latestEpoch = new AbsoluteDate(year, month, day,
356                                                   hour, minute, second,
357                                                   pi.timeScale);
358                 break;
359             }
360 
361             case 'P': {
362                 final String satelliteId = line.substring(1, 4).trim();
363 
364                 if (!file.containsSatellite(satelliteId)) {
365                     pi.latestPosition = null;
366                 } else {
367                     final double x = Double.parseDouble(line.substring(4, 18).trim());
368                     final double y = Double.parseDouble(line.substring(18, 32).trim());
369                     final double z = Double.parseDouble(line.substring(32, 46).trim());
370 
371                     // the position values are in km and have to be converted to m
372                     pi.latestPosition = new Vector3D(x * 1000, y * 1000, z * 1000);
373 
374                     // clock (microsec)
375                     pi.latestClock = Double.parseDouble(line.substring(46, 60).trim());
376 
377                     // the additional items are optional and not read yet
378 
379                     // if (line.length() >= 73) {
380                     // // x-sdev (b**n mm)
381                     // int xStdDevExp = Integer.valueOf(line.substring(61,
382                     // 63).trim());
383                     // // y-sdev (b**n mm)
384                     // int yStdDevExp = Integer.valueOf(line.substring(64,
385                     // 66).trim());
386                     // // z-sdev (b**n mm)
387                     // int zStdDevExp = Integer.valueOf(line.substring(67,
388                     // 69).trim());
389                     // // c-sdev (b**n psec)
390                     // int cStdDevExp = Integer.valueOf(line.substring(70,
391                     // 73).trim());
392                     //
393                     // pi.posStdDevRecord =
394                     // new PositionStdDevRecord(FastMath.pow(pi.posVelBase, xStdDevExp),
395                     // FastMath.pow(pi.posVelBase,
396                     // yStdDevExp), FastMath.pow(pi.posVelBase, zStdDevExp),
397                     // FastMath.pow(pi.clockBase, cStdDevExp));
398                     //
399                     // String clockEventFlag = line.substring(74, 75);
400                     // String clockPredFlag = line.substring(75, 76);
401                     // String maneuverFlag = line.substring(78, 79);
402                     // String orbitPredFlag = line.substring(79, 80);
403                     // }
404 
405                     if (!pi.hasVelocityEntries) {
406                         final SatelliteTimeCoordinate coord =
407                                 new SatelliteTimeCoordinate(pi.latestEpoch,
408                                                             pi.latestPosition,
409                                                             pi.latestClock);
410                         file.addSatelliteCoordinate(satelliteId, coord);
411                     }
412                 }
413                 break;
414             }
415 
416             case 'V': {
417                 final String satelliteId = line.substring(1, 4).trim();
418 
419                 if (file.containsSatellite(satelliteId)) {
420                     final double xv = Double.parseDouble(line.substring(4, 18).trim());
421                     final double yv = Double.parseDouble(line.substring(18, 32).trim());
422                     final double zv = Double.parseDouble(line.substring(32, 46).trim());
423 
424                     // the velocity values are in dm/s and have to be converted to m/s
425                     final Vector3D velocity = new Vector3D(xv / 10d, yv / 10d, zv / 10d);
426 
427                     final double clockRateChange = Double.parseDouble(line.substring(46, 60).trim());
428 
429                     // the additional items are optional and not read yet
430 
431                     // if (line.length() >= 73) {
432                     // // xvel-sdev (b**n 10**-4 mm/sec)
433                     // int xVstdDevExp = Integer.valueOf(line.substring(61,
434                     // 63).trim());
435                     // // yvel-sdev (b**n 10**-4 mm/sec)
436                     // int yVstdDevExp = Integer.valueOf(line.substring(64,
437                     // 66).trim());
438                     // // zvel-sdev (b**n 10**-4 mm/sec)
439                     // int zVstdDevExp = Integer.valueOf(line.substring(67,
440                     // 69).trim());
441                     // // clkrate-sdev (b**n 10**-4 psec/sec)
442                     // int clkStdDevExp = Integer.valueOf(line.substring(70,
443                     // 73).trim());
444                     // }
445 
446                     final SatelliteTimeCoordinate coord =
447                             new SatelliteTimeCoordinate(pi.latestEpoch,
448                                                         new PVCoordinates(pi.latestPosition, velocity),
449                                                         pi.latestClock,
450                                                         clockRateChange);
451                     file.addSatelliteCoordinate(satelliteId, coord);
452                 }
453                 break;
454             }
455 
456             default:
457                 // ignore everything else
458                 break;
459         }
460     }
461 
462     /** Returns the {@link SP3FileType} that corresponds to a given string in a SP3 file.
463      * @param fileType file type as string
464      * @return file type as enum
465      */
466     private SP3FileType getFileType(final String fileType) {
467         SP3FileType type = SP3FileType.UNDEFINED;
468         if ("G".equalsIgnoreCase(fileType)) {
469             type = SP3FileType.GPS;
470         } else if ("M".equalsIgnoreCase(fileType)) {
471             type = SP3FileType.MIXED;
472         } else if ("R".equalsIgnoreCase(fileType)) {
473             type = SP3FileType.GLONASS;
474         } else if ("L".equalsIgnoreCase(fileType)) {
475             type = SP3FileType.LEO;
476         } else if ("E".equalsIgnoreCase(fileType)) {
477             type = SP3FileType.GALILEO;
478         } else if ("C".equalsIgnoreCase(fileType)) {
479             type = SP3FileType.COMPASS;
480         } else if ("J".equalsIgnoreCase(fileType)) {
481             type = SP3FileType.QZSS;
482         }
483         return type;
484     }
485 
486     /** Transient data used for parsing a sp3 file. The data is kept in a
487      * separate data structure to make the parser thread-safe.
488      * <p><b>Note</b>: The class intentionally does not provide accessor
489      * methods, as it is only used internally for parsing a SP3 file.</p>
490      */
491     private static class ParseInfo {
492 
493         /** The corresponding SP3File object. */
494         private SP3File file;
495 
496         /** The latest epoch as read from the SP3 file. */
497         private AbsoluteDate latestEpoch;
498 
499         /** The latest position as read from the SP3 file. */
500         private Vector3D latestPosition;
501 
502         /** The latest clock value as read from the SP3 file. */
503         private double latestClock;
504 
505         /** Indicates if the SP3 file has velocity entries. */
506         private boolean hasVelocityEntries;
507 
508         /** The timescale used in the SP3 file. */
509         private TimeScale timeScale;
510 
511         /** The number of satellites as contained in the SP3 file. */
512         private int maxSatellites;
513 
514         /** The base for pos/vel. */
515         //private double posVelBase;
516 
517         /** The base for clock/rate. */
518         //private double clockBase;
519 
520         /** Create a new {@link ParseInfo} object. */
521         protected ParseInfo() {
522             file = new SP3File();
523             latestEpoch = null;
524             latestPosition = null;
525             latestClock = 0.0d;
526             hasVelocityEntries = false;
527             timeScale = TimeScalesFactory.getGPS();
528             maxSatellites = 0;
529             //posVelBase = 2d;
530             //clockBase = 2d;
531         }
532     }
533 }