SP3Parser.java

  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. import java.io.BufferedReader;
  19. import java.io.FileInputStream;
  20. import java.io.FileNotFoundException;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.InputStreamReader;
  24. import java.util.Locale;
  25. import java.util.Scanner;

  26. import org.apache.commons.math3.exception.util.DummyLocalizable;
  27. import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.files.general.OrbitFileParser;
  31. import org.orekit.files.general.SatelliteInformation;
  32. import org.orekit.files.general.SatelliteTimeCoordinate;
  33. import org.orekit.files.general.OrbitFile.TimeSystem;
  34. import org.orekit.files.sp3.SP3File.SP3FileType;
  35. import org.orekit.files.sp3.SP3File.SP3OrbitType;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.TimeScale;
  38. import org.orekit.time.TimeScalesFactory;
  39. import org.orekit.utils.PVCoordinates;

  40. /** A parser for the SP3 orbit file format. It supports the original format as
  41.  * well as the latest SP3-c version.
  42.  * <p>
  43.  * <b>Note:</b> this parser is thread-safe, so calling {@link #parse} from
  44.  * different threads is allowed.
  45.  * </p>
  46.  * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3_docu.txt">SP3-a file format</a>
  47.  * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3c.txt">SP3-c file format</a>
  48.  * @author Thomas Neidhart
  49.  */
  50. public class SP3Parser implements OrbitFileParser {

  51.     /** {@inheritDoc} */
  52.     public SP3File parse(final String fileName) throws OrekitException {

  53.         InputStream stream = null;

  54.         try {
  55.             stream = new FileInputStream(fileName);
  56.             return parse(stream);
  57.         } catch (FileNotFoundException e) {
  58.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
  59.         } finally {
  60.             try {
  61.                 if (stream != null) {
  62.                     stream.close();
  63.                 }
  64.             } catch (IOException e) {
  65.                 // ignore
  66.             }
  67.         }
  68.     }

  69.     /** {@inheritDoc} */
  70.     public SP3File parse(final InputStream stream) throws OrekitException {
  71.         try {
  72.             return parseInternal(stream);
  73.         } catch (IOException e) {
  74.             throw new OrekitException(e, new DummyLocalizable(e.getMessage()));
  75.         }
  76.     }

  77.     /** Parses the SP3 file from the given {@link InputStream} and
  78.      * returns a {@link SP3File} object.
  79.      * @param stream the stream to read the SP3File from
  80.      * @return the parsed {@link SP3File} object
  81.      * @throws OrekitException if the file could not be parsed successfully
  82.      * @throws IOException if an error occurs while reading from the stream
  83.      */
  84.     private SP3File parseInternal(final InputStream stream)
  85.         throws OrekitException, IOException {

  86.         final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));

  87.         // initialize internal data structures
  88.         final ParseInfo pi = new ParseInfo();

  89.         String line = null;
  90.         int lineNumber = 1;
  91.         try {
  92.             while (lineNumber < 23) {
  93.                 line = reader.readLine();
  94.                 if (line == null) {
  95.                     throw new OrekitException(OrekitMessages.SP3_UNEXPECTED_END_OF_FILE, lineNumber - 1);
  96.                 } else {
  97.                     parseHeaderLine(lineNumber++, line, pi);
  98.                 }
  99.             }

  100.             // now handle the epoch/position/velocity entries

  101.             boolean done = false;
  102.             do {
  103.                 line = reader.readLine();
  104.                 if (line != null) {
  105.                     if ("EOF".equalsIgnoreCase(line)) {
  106.                         done = true;
  107.                     } else if (line.length() > 0) {
  108.                         parseContentLine(line, pi);
  109.                     }
  110.                 }
  111.             } while (!done);
  112.         } finally {
  113.             try {
  114.                 reader.close();
  115.             } catch (IOException e1) {
  116.                 // ignore
  117.             }
  118.         }

  119.         return pi.file;
  120.     }

  121.     /** Parses a header line from the SP3 file (line number 1 - 22).
  122.      * @param lineNumber the current line number
  123.      * @param line the line as read from the SP3 file
  124.      * @param pi the current {@link ParseInfo} object
  125.      * @throws OrekitException if a non-supported construct is found
  126.      */
  127.     private void parseHeaderLine(final int lineNumber, final String line, final ParseInfo pi)
  128.         throws OrekitException {

  129.         final SP3File file = pi.file;
  130.         @SuppressWarnings("resource") // we scan a String which is not closeable
  131.         final Scanner scanner = new Scanner(line).useDelimiter("\\s+").useLocale(Locale.US);

  132.         // CHECKSTYLE: stop FallThrough check

  133.         switch (lineNumber) {
  134.         // version, epoch, data used and agency information
  135.         case 1: {
  136.             scanner.skip("#");
  137.             final String v = scanner.next();

  138.             final char version = v.substring(0, 1).toLowerCase().charAt(0);
  139.             if (version != 'a' && version != 'b' && version != 'c') {
  140.                 throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_VERSION, version);
  141.             }

  142.             pi.hasVelocityEntries = "V".equals(v.substring(1, 2));

  143.             final int year = Integer.valueOf(v.substring(2));
  144.             final int month = scanner.nextInt();
  145.             final int day = scanner.nextInt();
  146.             final int hour = scanner.nextInt();
  147.             final int minute = scanner.nextInt();
  148.             final double second = scanner.nextDouble();

  149.             final AbsoluteDate epoch = new AbsoluteDate(year, month, day,
  150.                                                         hour, minute, second,
  151.                                                         TimeScalesFactory.getGPS());

  152.             file.setEpoch(epoch);

  153.             final int numEpochs = scanner.nextInt();
  154.             file.setNumberOfEpochs(numEpochs);

  155.             // data used indicator
  156.             file.setDataUsed(scanner.next());

  157.             file.setCoordinateSystem(scanner.next());
  158.             file.setOrbitType(SP3OrbitType.valueOf(scanner.next()));
  159.             file.setAgency(scanner.next());
  160.         }
  161.             break;

  162.         // additional date/time references in gps/julian day notation
  163.         case 2: {
  164.             scanner.skip("##");

  165.             // gps week
  166.             file.setGpsWeek(scanner.nextInt());
  167.             // seconds of week
  168.             file.setSecondsOfWeek(scanner.nextDouble());
  169.             // epoch interval
  170.             file.setEpochInterval(scanner.nextDouble());
  171.             // julian day
  172.             file.setJulianDay(scanner.nextInt());
  173.             // day fraction
  174.             file.setDayFraction(scanner.nextDouble());
  175.         }
  176.             break;

  177.         // line 3 contains the number of satellites
  178.         case 3:
  179.             pi.maxSatellites = Integer.valueOf(line.substring(4, 6).trim());
  180.             // fall-through intended - the line contains already the first entries

  181.             // the following 4 lines contain additional satellite ids
  182.         case 4:
  183.         case 5:
  184.         case 6:
  185.         case 7: {
  186.             final int lineLength = line.length();
  187.             int count = file.getSatelliteCount();
  188.             int startIdx = 9;
  189.             while (count++ < pi.maxSatellites && (startIdx + 3) <= lineLength) {
  190.                 final String satId = line.substring(startIdx, startIdx + 3).trim();
  191.                 file.addSatellite(satId);
  192.                 startIdx += 3;
  193.             }
  194.         }
  195.             break;

  196.         // the following 5 lines contain general accuracy information for each satellite
  197.         case 8:
  198.         case 9:
  199.         case 10:
  200.         case 11:
  201.         case 12: {
  202.             final int lineLength = line.length();
  203.             int satIdx = (lineNumber - 8) * 17;
  204.             int startIdx = 9;
  205.             while (satIdx < pi.maxSatellites && (startIdx + 3) <= lineLength) {
  206.                 final SatelliteInformation satInfo = file.getSatellite(satIdx++);
  207.                 final int exponent = Integer.valueOf(line.substring(startIdx, startIdx + 3).trim());
  208.                 // the accuracy is calculated as 2**exp (in m) -> can be safely
  209.                 // converted to an integer as there will be no fraction
  210.                 satInfo.setAccuracy((int) Math.pow(2d, exponent));
  211.                 startIdx += 3;
  212.             }
  213.         }
  214.             break;

  215.         case 13: {
  216.             file.setType(getFileType(line.substring(3, 5).trim()));

  217.             // now identify the time system in use
  218.             final String tsStr = line.substring(9, 12).trim();
  219.             final TimeSystem ts = TimeSystem.GPS;
  220.             if (!tsStr.equalsIgnoreCase("ccc")) {
  221.                 TimeSystem.valueOf(tsStr);
  222.             }
  223.             file.setTimeSystem(ts);

  224.             switch (ts) {
  225.             case GPS:
  226.                 pi.timeScale = TimeScalesFactory.getGPS();
  227.                 break;

  228.             case GAL:
  229.                 pi.timeScale = TimeScalesFactory.getGST();
  230.                 break;

  231.             case GLO:
  232.             case QZS:
  233.                 throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_TIMESYSTEM, ts.name());

  234.             case TAI:
  235.                 pi.timeScale = TimeScalesFactory.getTAI();
  236.                 break;

  237.             case UTC:
  238.                 pi.timeScale = TimeScalesFactory.getUTC();
  239.                 break;

  240.             default:
  241.                 pi.timeScale = TimeScalesFactory.getGPS();
  242.                 break;
  243.             }
  244.         }
  245.             break;

  246.         case 14:
  247.             // ignore additional custom fields
  248.             break;

  249.         // load base numbers for the standard deviations of
  250.         // position/velocity/clock components
  251.         case 15: {
  252.             // String base = line.substring(3, 13).trim();
  253.             // if (!base.equals("0.0000000")) {
  254.             //    // (mm or 10**-4 mm/sec)
  255.             //    pi.posVelBase = Double.valueOf(base);
  256.             // }

  257.             // base = line.substring(14, 26).trim();
  258.             // if (!base.equals("0.000000000")) {
  259.             //    // (psec or 10**-4 psec/sec)
  260.             //    pi.clockBase = Double.valueOf(base);
  261.             // }
  262.         }
  263.             break;

  264.         case 16:
  265.         case 17:
  266.         case 18:
  267.             // ignore additional custom parameters
  268.             break;

  269.         case 19:
  270.         case 20:
  271.         case 21:
  272.         case 22:
  273.             // ignore comment lines
  274.             break;
  275.         default:
  276.             // ignore -> method should only be called up to line 22
  277.             break;
  278.         }

  279.         // CHECKSTYLE: resume FallThrough check

  280.     }

  281.     /** Parses a single content line as read from the SP3 file.
  282.      * @param line a string containing the line
  283.      * @param pi the current {@link ParseInfo} object
  284.      */
  285.     private void parseContentLine(final String line, final ParseInfo pi) {
  286.         // EP and EV lines are ignored so far

  287.         final SP3File file = pi.file;

  288.         switch (line.charAt(0)) {
  289.         case '*': {
  290.             final int year = Integer.valueOf(line.substring(3, 7).trim());
  291.             final int month = Integer.valueOf(line.substring(8, 10).trim());
  292.             final int day = Integer.valueOf(line.substring(11, 13).trim());
  293.             final int hour = Integer.valueOf(line.substring(14, 16).trim());
  294.             final int minute = Integer.valueOf(line.substring(17, 19).trim());
  295.             final double second = Double.valueOf(line.substring(20, 31).trim());

  296.             pi.latestEpoch = new AbsoluteDate(year, month, day,
  297.                                               hour, minute, second,
  298.                                               pi.timeScale);
  299.         }
  300.             break;

  301.         case 'P': {
  302.             final String satelliteId = line.substring(1, 4).trim();

  303.             if (!file.containsSatellite(satelliteId)) {
  304.                 pi.latestPosition = null;
  305.             } else {
  306.                 final double x = Double.valueOf(line.substring(4, 18).trim());
  307.                 final double y = Double.valueOf(line.substring(18, 32).trim());
  308.                 final double z = Double.valueOf(line.substring(32, 46).trim());

  309.                 // the position values are in km and have to be converted to m
  310.                 pi.latestPosition = new Vector3D(x * 1000, y * 1000, z * 1000);

  311.                 // clock (microsec)
  312.                 pi.latestClock = Double.valueOf(line.substring(46, 60).trim());

  313.                 // the additional items are optional and not read yet

  314.                 // if (line.length() >= 73) {
  315.                 // // x-sdev (b**n mm)
  316.                 // int xStdDevExp = Integer.valueOf(line.substring(61,
  317.                 // 63).trim());
  318.                 // // y-sdev (b**n mm)
  319.                 // int yStdDevExp = Integer.valueOf(line.substring(64,
  320.                 // 66).trim());
  321.                 // // z-sdev (b**n mm)
  322.                 // int zStdDevExp = Integer.valueOf(line.substring(67,
  323.                 // 69).trim());
  324.                 // // c-sdev (b**n psec)
  325.                 // int cStdDevExp = Integer.valueOf(line.substring(70,
  326.                 // 73).trim());
  327.                 //
  328.                 // pi.posStdDevRecord =
  329.                 // new PositionStdDevRecord(Math.pow(pi.posVelBase, xStdDevExp),
  330.                 // Math.pow(pi.posVelBase,
  331.                 // yStdDevExp), Math.pow(pi.posVelBase, zStdDevExp),
  332.                 // Math.pow(pi.clockBase, cStdDevExp));
  333.                 //
  334.                 // String clockEventFlag = line.substring(74, 75);
  335.                 // String clockPredFlag = line.substring(75, 76);
  336.                 // String maneuverFlag = line.substring(78, 79);
  337.                 // String orbitPredFlag = line.substring(79, 80);
  338.                 // }

  339.                 if (!pi.hasVelocityEntries) {
  340.                     final SatelliteTimeCoordinate coord =
  341.                         new SatelliteTimeCoordinate(pi.latestEpoch,
  342.                                                     pi.latestPosition,
  343.                                                     pi.latestClock);
  344.                     file.addSatelliteCoordinate(satelliteId, coord);
  345.                 }
  346.             }
  347.         }
  348.             break;

  349.         case 'V': {
  350.             final String satelliteId = line.substring(1, 4).trim();

  351.             if (file.containsSatellite(satelliteId)) {
  352.                 final double xv = Double.valueOf(line.substring(4, 18).trim());
  353.                 final double yv = Double.valueOf(line.substring(18, 32).trim());
  354.                 final double zv = Double.valueOf(line.substring(32, 46).trim());

  355.                 // the velocity values are in dm/s and have to be converted to m/s
  356.                 final Vector3D velocity = new Vector3D(xv / 10d, yv / 10d, zv / 10d);

  357.                 final double clockRateChange = Double.valueOf(line.substring(46, 60).trim());

  358.                 // the additional items are optional and not read yet

  359.                 // if (line.length() >= 73) {
  360.                 // // xvel-sdev (b**n 10**-4 mm/sec)
  361.                 // int xVstdDevExp = Integer.valueOf(line.substring(61,
  362.                 // 63).trim());
  363.                 // // yvel-sdev (b**n 10**-4 mm/sec)
  364.                 // int yVstdDevExp = Integer.valueOf(line.substring(64,
  365.                 // 66).trim());
  366.                 // // zvel-sdev (b**n 10**-4 mm/sec)
  367.                 // int zVstdDevExp = Integer.valueOf(line.substring(67,
  368.                 // 69).trim());
  369.                 // // clkrate-sdev (b**n 10**-4 psec/sec)
  370.                 // int clkStdDevExp = Integer.valueOf(line.substring(70,
  371.                 // 73).trim());
  372.                 // }

  373.                 final SatelliteTimeCoordinate coord =
  374.                     new SatelliteTimeCoordinate(pi.latestEpoch,
  375.                                                 new PVCoordinates(pi.latestPosition, velocity),
  376.                                                 pi.latestClock,
  377.                                                 clockRateChange);
  378.                 file.addSatelliteCoordinate(satelliteId, coord);
  379.             }
  380.         }
  381.             break;

  382.         default:
  383.             // ignore everything else
  384.             break;
  385.         }
  386.     }

  387.     /** Returns the {@link SP3FileType} that corresponds to a given string in a SP3 file.
  388.      * @param fileType file type as string
  389.      * @return file type as enum
  390.      */
  391.     private SP3FileType getFileType(final String fileType) {
  392.         SP3FileType type = SP3FileType.UNDEFINED;
  393.         if ("G".equalsIgnoreCase(fileType)) {
  394.             type = SP3FileType.GPS;
  395.         } else if ("M".equalsIgnoreCase(fileType)) {
  396.             type = SP3FileType.MIXED;
  397.         } else if ("R".equalsIgnoreCase(fileType)) {
  398.             type = SP3FileType.GLONASS;
  399.         } else if ("L".equalsIgnoreCase(fileType)) {
  400.             type = SP3FileType.LEO;
  401.         } else if ("E".equalsIgnoreCase(fileType)) {
  402.             type = SP3FileType.GALILEO;
  403.         } else if ("C".equalsIgnoreCase(fileType)) {
  404.             type = SP3FileType.COMPASS;
  405.         } else if ("J".equalsIgnoreCase(fileType)) {
  406.             type = SP3FileType.QZSS;
  407.         }
  408.         return type;
  409.     }

  410.     /** Transient data used for parsing a sp3 file. The data is kept in a
  411.      * separate data structure to make the parser thread-safe.
  412.      * <p><b>Note</b>: The class intentionally does not provide accessor
  413.      * methods, as it is only used internally for parsing a SP3 file.</p>
  414.      */
  415.     private static class ParseInfo {

  416.         /** The corresponding SP3File object. */
  417.         private SP3File file;

  418.         /** The latest epoch as read from the SP3 file. */
  419.         private AbsoluteDate latestEpoch;

  420.         /** The latest position as read from the SP3 file. */
  421.         private Vector3D latestPosition;

  422.         /** The latest clock value as read from the SP3 file. */
  423.         private double latestClock;

  424.         /** Indicates if the SP3 file has velocity entries. */
  425.         private boolean hasVelocityEntries;

  426.         /** The timescale used in the SP3 file. */
  427.         private TimeScale timeScale;

  428.         /** The number of satellites as contained in the SP3 file. */
  429.         private int maxSatellites;

  430.         /** The base for pos/vel. */
  431.         //private double posVelBase;

  432.         /** The base for clock/rate. */
  433.         //private double clockBase;

  434.         /** Create a new {@link ParseInfo} object. */
  435.         protected ParseInfo() {
  436.             file = new SP3File();
  437.             latestEpoch = null;
  438.             latestPosition = null;
  439.             latestClock = 0.0d;
  440.             hasVelocityEntries = false;
  441.             timeScale = TimeScalesFactory.getGPS();
  442.             maxSatellites = 0;
  443.             //posVelBase = 2d;
  444.             //clockBase = 2d;
  445.         }
  446.     }
  447. }