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.         Scanner scanner = null;
  131.         try {
  132.             scanner = new Scanner(line).useDelimiter("\\s+").useLocale(Locale.US);

  133.             // CHECKSTYLE: stop FallThrough check

  134.             switch (lineNumber) {

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

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

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

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

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

  153.                     file.setEpoch(epoch);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  280.             // CHECKSTYLE: resume FallThrough check

  281.         } finally {
  282.             if (scanner != null) {
  283.                 scanner.close();
  284.             }
  285.         }

  286.     }

  287.     /** Parses a single content line as read from the SP3 file.
  288.      * @param line a string containing the line
  289.      * @param pi the current {@link ParseInfo} object
  290.      */
  291.     private void parseContentLine(final String line, final ParseInfo pi) {
  292.         // EP and EV lines are ignored so far

  293.         final SP3File file = pi.file;

  294.         switch (line.charAt(0)) {
  295.             case '*': {
  296.                 final int year = Integer.parseInt(line.substring(3, 7).trim());
  297.                 final int month = Integer.parseInt(line.substring(8, 10).trim());
  298.                 final int day = Integer.parseInt(line.substring(11, 13).trim());
  299.                 final int hour = Integer.parseInt(line.substring(14, 16).trim());
  300.                 final int minute = Integer.parseInt(line.substring(17, 19).trim());
  301.                 final double second = Double.parseDouble(line.substring(20, 31).trim());

  302.                 pi.latestEpoch = new AbsoluteDate(year, month, day,
  303.                                                   hour, minute, second,
  304.                                                   pi.timeScale);
  305.                 break;
  306.             }

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

  309.                 if (!file.containsSatellite(satelliteId)) {
  310.                     pi.latestPosition = null;
  311.                 } else {
  312.                     final double x = Double.parseDouble(line.substring(4, 18).trim());
  313.                     final double y = Double.parseDouble(line.substring(18, 32).trim());
  314.                     final double z = Double.parseDouble(line.substring(32, 46).trim());

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

  317.                     // clock (microsec)
  318.                     pi.latestClock = Double.parseDouble(line.substring(46, 60).trim());

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

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

  345.                     if (!pi.hasVelocityEntries) {
  346.                         final SatelliteTimeCoordinate coord =
  347.                                 new SatelliteTimeCoordinate(pi.latestEpoch,
  348.                                                             pi.latestPosition,
  349.                                                             pi.latestClock);
  350.                         file.addSatelliteCoordinate(satelliteId, coord);
  351.                     }
  352.                 }
  353.                 break;
  354.             }

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

  357.                 if (file.containsSatellite(satelliteId)) {
  358.                     final double xv = Double.parseDouble(line.substring(4, 18).trim());
  359.                     final double yv = Double.parseDouble(line.substring(18, 32).trim());
  360.                     final double zv = Double.parseDouble(line.substring(32, 46).trim());

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

  363.                     final double clockRateChange = Double.parseDouble(line.substring(46, 60).trim());

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

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

  379.                     final SatelliteTimeCoordinate coord =
  380.                             new SatelliteTimeCoordinate(pi.latestEpoch,
  381.                                                         new PVCoordinates(pi.latestPosition, velocity),
  382.                                                         pi.latestClock,
  383.                                                         clockRateChange);
  384.                     file.addSatelliteCoordinate(satelliteId, coord);
  385.                 }
  386.                 break;
  387.             }

  388.             default:
  389.                 // ignore everything else
  390.                 break;
  391.         }
  392.     }

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

  416.     /** Transient data used for parsing a sp3 file. The data is kept in a
  417.      * separate data structure to make the parser thread-safe.
  418.      * <p><b>Note</b>: The class intentionally does not provide accessor
  419.      * methods, as it is only used internally for parsing a SP3 file.</p>
  420.      */
  421.     private static class ParseInfo {

  422.         /** The corresponding SP3File object. */
  423.         private SP3File file;

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

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

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

  430.         /** Indicates if the SP3 file has velocity entries. */
  431.         private boolean hasVelocityEntries;

  432.         /** The timescale used in the SP3 file. */
  433.         private TimeScale timeScale;

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

  436.         /** The base for pos/vel. */
  437.         //private double posVelBase;

  438.         /** The base for clock/rate. */
  439.         //private double clockBase;

  440.         /** Create a new {@link ParseInfo} object. */
  441.         protected ParseInfo() {
  442.             file = new SP3File();
  443.             latestEpoch = null;
  444.             latestPosition = null;
  445.             latestClock = 0.0d;
  446.             hasVelocityEntries = false;
  447.             timeScale = TimeScalesFactory.getGPS();
  448.             maxSatellites = 0;
  449.             //posVelBase = 2d;
  450.             //clockBase = 2d;
  451.         }
  452.     }
  453. }