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