CPFParser.java

  1. /* Copyright 2002-2021 CS GROUP
  2.  * Licensed to CS GROUP (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.ilrs;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.Reader;
  21. import java.util.Optional;
  22. import java.util.regex.Pattern;
  23. import java.util.stream.Stream;

  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.orekit.annotation.DefaultDataContext;
  27. import org.orekit.data.DataContext;
  28. import org.orekit.data.DataSource;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.files.general.EphemerisFileParser;
  32. import org.orekit.frames.Frame;
  33. import org.orekit.frames.Frames;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.time.DateComponents;
  36. import org.orekit.time.TimeScale;
  37. import org.orekit.utils.CartesianDerivativesFilter;
  38. import org.orekit.utils.Constants;
  39. import org.orekit.utils.IERSConventions;

  40. /**
  41.  * A parser for the CPF orbit file format.
  42.  * <p>
  43.  * It supports both 1.0 and 2.0 versions
  44.  * <p>
  45.  * <b>Note:</b> Only required header keys are read. Furthermore, only position data are read.
  46.  * Other keys are simply ignored
  47.  * Contributions are welcome to support more fields in the format.
  48.  * </p>
  49.  * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2006/cpf_1.01.pdf">1.0 file format</a>
  50.  * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2018/cpf_2.00h-1.pdf">2.0 file format</a>
  51.  * @author Bryan Cazabonne
  52.  * @since 10.3
  53.  */
  54. public class CPFParser implements EphemerisFileParser<CPF> {

  55.     /** File format. */
  56.     private static final String FILE_FORMAT = "CPF";

  57.     /** Miscroseconds to seconds converter. */
  58.     private static final double MS_TO_S = 1.0e-6;

  59.     /** Pattern for delimiting regular expressions. */
  60.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  61.     /** Default number of sample for interpolating data (See: reference documents. */
  62.     private static final int DEFAULT_INTERPOLATION_SAMPLE = 10;

  63.     /** Standard gravitational parameter in m^3 / s^2. */
  64.     private final double mu;

  65.     /** Time scale used to define epochs in CPF file. */
  66.     private final TimeScale timeScale;

  67.     /** Set of frames. */
  68.     private final Frames frames;

  69.     /** Interpolation sample for data interpolating. */
  70.     private final int interpolationSample;

  71.     /** IERS convention for frames. */
  72.     private final IERSConventions iersConvention;

  73.     /**
  74.      * Default constructor.
  75.      * <p>
  76.      * This constructor uses the {@link DataContext#getDefault() default data context}.
  77.      */
  78.     @DefaultDataContext
  79.     public CPFParser() {
  80.         this(Constants.EIGEN5C_EARTH_MU, DEFAULT_INTERPOLATION_SAMPLE,
  81.              IERSConventions.IERS_2010, DataContext.getDefault().getTimeScales().getUTC(),
  82.              DataContext.getDefault().getFrames());
  83.     }

  84.     /**
  85.      * Constructor.
  86.      * @param mu standard gravitational parameter to use for
  87.      *           creating {@link org.orekit.orbits.Orbit Orbits} from
  88.      *           the ephemeris data.
  89.      * @param interpolationSamples number of samples to use when interpolating
  90.      * @param iersConventions IERS convention for frames definition
  91.      * @param utc time scale used to define epochs in CPF files (UTC)
  92.      * @param frames set of frames for satellite coordinates
  93.      */
  94.     public CPFParser(final double mu,
  95.                      final int interpolationSamples,
  96.                      final IERSConventions iersConventions,
  97.                      final TimeScale utc,
  98.                      final Frames frames) {
  99.         this.mu                  = mu;
  100.         this.interpolationSample = interpolationSamples;
  101.         this.iersConvention      = iersConventions;
  102.         this.timeScale           = utc;
  103.         this.frames              = frames;
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     public CPF parse(final DataSource source) {

  108.         try (Reader reader = source.getOpener().openReaderOnce();
  109.              BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {

  110.             if (br == null) {
  111.                 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
  112.             }

  113.             // initialize internal data structures
  114.             final ParseInfo pi = new ParseInfo();

  115.             int lineNumber = 0;
  116.             Stream<LineParser> parsers = Stream.of(LineParser.H1);
  117.             for (String line = br.readLine(); line != null; line = br.readLine()) {
  118.                 ++lineNumber;
  119.                 final String l = line;
  120.                 final Optional<LineParser> selected = parsers.filter(p -> p.canHandle(l)).findFirst();
  121.                 if (selected.isPresent()) {
  122.                     try {
  123.                         selected.get().parse(line, pi);
  124.                     } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
  125.                         throw new OrekitException(e,
  126.                                                   OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  127.                                                   lineNumber, source.getName(), line);
  128.                     }
  129.                     parsers = selected.get().allowedNext();
  130.                 }
  131.                 if (pi.done) {
  132.                     pi.file.setFilter(pi.hasVelocityEntries ?
  133.                                                              CartesianDerivativesFilter.USE_PV :
  134.                                                                  CartesianDerivativesFilter.USE_P);
  135.                     // Return file
  136.                     return pi.file;
  137.                 }
  138.             }

  139.             // We never reached the EOF marker
  140.             throw new OrekitException(OrekitMessages.CPF_UNEXPECTED_END_OF_FILE, lineNumber);

  141.         } catch (IOException ioe) {
  142.             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  143.         }

  144.     }

  145.     /** Transient data used for parsing a CPF file. The data is kept in a
  146.      * separate data structure to make the parser thread-safe.
  147.      * <p><b>Note</b>: The class intentionally does not provide accessor
  148.      * methods, as it is only used internally for parsing a CPF file.</p>
  149.      */
  150.     private class ParseInfo {

  151.         /** The corresponding CPF file. */
  152.         private CPF file;

  153.         /** IERS convention. */
  154.         private IERSConventions convention;

  155.         /** Set of frames. */
  156.         private Frames frames;

  157.         /** Frame for the ephemeris data. */
  158.         private Frame frame;

  159.         /** Time scale. */
  160.         private TimeScale timeScale;

  161.         /** Indicates if the SP3 file has velocity entries. */
  162.         private boolean hasVelocityEntries;

  163.         /** End Of File reached indicator. */
  164.         private boolean done;

  165.         /**
  166.          * Constructor.
  167.          */
  168.         protected ParseInfo() {

  169.             // Initialise file
  170.             file = new CPF();

  171.             // Time scale
  172.             this.timeScale = CPFParser.this.timeScale;

  173.             // Initialise fields
  174.             file.setMu(mu);
  175.             file.setInterpolationSample(interpolationSample);
  176.             file.setTimeScale(timeScale);

  177.             // Default values
  178.             this.done               = false;
  179.             this.hasVelocityEntries = false;

  180.             // Default value for reference frame
  181.             this.convention = CPFParser.this.iersConvention;
  182.             this.frames     = CPFParser.this.frames;
  183.             frame           = frames.getITRF(convention, false);

  184.         }

  185.     }

  186.     /** Parsers for specific lines. */
  187.     private enum LineParser {

  188.         /** Header first line. */
  189.         H1("H1") {

  190.             /** {@inheritDoc} */
  191.             @Override
  192.             public void parse(final String line, final ParseInfo pi) {

  193.                 // Data contained in the line
  194.                 final String[] values = SEPARATOR.split(line);

  195.                 // Index for reading data.
  196.                 // Allow taking into consideration difference between 1.0 and 2.0 formats
  197.                 int index = 1;

  198.                 // Format
  199.                 final String format = values[index++];

  200.                 // Throw an exception if format is not equal to "CPF"
  201.                 if (!FILE_FORMAT.equals(format)) {
  202.                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
  203.                 }

  204.                 // Fill first elements
  205.                 pi.file.getHeader().setFormat(format);
  206.                 pi.file.getHeader().setVersion(Integer.parseInt(values[index++]));
  207.                 pi.file.getHeader().setSource(values[index++]);

  208.                 // Epoch of ephemeris production
  209.                 final int year  = Integer.parseInt(values[index++]);
  210.                 final int month = Integer.parseInt(values[index++]);
  211.                 final int day   = Integer.parseInt(values[index++]);
  212.                 pi.file.getHeader().setProductionEpoch(new DateComponents(year, month, day));

  213.                 // Hour of ephemeris production
  214.                 pi.file.getHeader().setProductionHour(Integer.parseInt(values[index++]));

  215.                 // Ephemeris sequence number
  216.                 pi.file.getHeader().setSequenceNumber(Integer.parseInt(values[index++]));

  217.                 // Difference between version 1.0 and 2.0: sub-daily ephemeris sequence number
  218.                 if (pi.file.getHeader().getVersion() == 2) {
  219.                     pi.file.getHeader().setSubDailySequenceNumber(Integer.parseInt(values[index++]));
  220.                 }

  221.                 // Target Name
  222.                 pi.file.getHeader().setName(values[index]);

  223.             }

  224.             /** {@inheritDoc} */
  225.             @Override
  226.             public Stream<LineParser> allowedNext() {
  227.                 return Stream.of(H2, ZERO);
  228.             }

  229.         },

  230.         /** Header second line. */
  231.         H2("H2") {

  232.             /** {@inheritDoc} */
  233.             @Override
  234.             public void parse(final String line, final ParseInfo pi) {

  235.                 // Data contained in the line
  236.                 final String[] values = SEPARATOR.split(line);

  237.                 // Identifiers
  238.                 pi.file.getHeader().setIlrsSatelliteId(values[1]);
  239.                 pi.file.getHeader().setSic(values[2]);
  240.                 pi.file.getHeader().setNoradId(values[3]);

  241.                 // Start epoch
  242.                 final int    yearS   = Integer.parseInt(values[4]);
  243.                 final int    monthS  = Integer.parseInt(values[5]);
  244.                 final int    dayS    = Integer.parseInt(values[6]);
  245.                 final int    hourS   = Integer.parseInt(values[7]);
  246.                 final int    minuteS = Integer.parseInt(values[8]);
  247.                 final double secondS = Integer.parseInt(values[9]);

  248.                 pi.file.getHeader().setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
  249.                                                                    hourS, minuteS, secondS,
  250.                                                                    pi.file.getTimeScale()));

  251.                 // End epoch
  252.                 final int    yearE   = Integer.parseInt(values[10]);
  253.                 final int    monthE  = Integer.parseInt(values[11]);
  254.                 final int    dayE    = Integer.parseInt(values[12]);
  255.                 final int    hourE   = Integer.parseInt(values[13]);
  256.                 final int    minuteE = Integer.parseInt(values[14]);
  257.                 final double secondE = Integer.parseInt(values[15]);

  258.                 pi.file.getHeader().setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
  259.                                                                  hourE, minuteE, secondE,
  260.                                                                  pi.file.getTimeScale()));

  261.                 // Time between table entries
  262.                 pi.file.getHeader().setStep(Integer.parseInt(values[16]));

  263.                 // Compatibility with TIVs
  264.                 pi.file.getHeader().setIsCompatibleWithTIVs(Integer.parseInt(values[17]) == 1);

  265.                 // Target class
  266.                 pi.file.getHeader().setTargetClass(Integer.parseInt(values[18]));

  267.                 // Reference frame
  268.                 final int frameId = Integer.parseInt(values[19]);
  269.                 switch (frameId) {
  270.                     case 0:
  271.                         pi.frame = pi.frames.getITRF(pi.convention, false);
  272.                         break;
  273.                     case 1:
  274.                         pi.frame = pi.frames.getTOD(true);
  275.                         break;
  276.                     case 2:
  277.                         pi.frame = pi.frames.getMOD(pi.convention);
  278.                         break;
  279.                     default:
  280.                         pi.frame = pi.frames.getITRF(pi.convention, false);
  281.                         break;
  282.                 }
  283.                 pi.file.getHeader().setRefFrame(pi.frame);
  284.                 pi.file.getHeader().setRefFrameId(frameId);

  285.                 // Last fields
  286.                 pi.file.getHeader().setRotationalAngleType(Integer.parseInt(values[20]));
  287.                 pi.file.getHeader().setIsCenterOfMassCorrectionApplied(Integer.parseInt(values[21]) == 1);
  288.                 if (pi.file.getHeader().getVersion() == 2) {
  289.                     pi.file.getHeader().setTargetLocation(Integer.parseInt(values[22]));
  290.                 }

  291.             }

  292.             /** {@inheritDoc} */
  293.             @Override
  294.             public Stream<LineParser> allowedNext() {
  295.                 return Stream.of(H3, H4, H5, H9, ZERO);
  296.             }

  297.         },

  298.         /** Header third line. */
  299.         H3("H3") {

  300.             /** {@inheritDoc} */
  301.             @Override
  302.             public void parse(final String line, final ParseInfo pi) {
  303.                 // Not implemented yet
  304.             }

  305.             /** {@inheritDoc} */
  306.             @Override
  307.             public Stream<LineParser> allowedNext() {
  308.                 return Stream.of(H4, H5, H9, ZERO);
  309.             }

  310.         },

  311.         /** Header fourth line. */
  312.         H4("H4") {

  313.             /** {@inheritDoc} */
  314.             @Override
  315.             public void parse(final String line, final ParseInfo pi) {

  316.                 // Data contained in the line
  317.                 final String[] values = SEPARATOR.split(line);

  318.                 // Pulse Repetition Frequency (PRF)
  319.                 pi.file.getHeader().setPrf(Double.parseDouble(values[1]));

  320.                 // Transponder information
  321.                 pi.file.getHeader().setTranspTransmitDelay(Double.parseDouble(values[2]) * MS_TO_S);
  322.                 pi.file.getHeader().setTranspUtcOffset(Double.parseDouble(values[3]) * MS_TO_S);
  323.                 pi.file.getHeader().setTranspOscDrift(Double.parseDouble(values[4]));
  324.                 if (pi.file.getHeader().getVersion() == 2) {
  325.                     pi.file.getHeader().setTranspClkRef(Double.parseDouble(values[5]));
  326.                 }

  327.             }

  328.             /** {@inheritDoc} */
  329.             @Override
  330.             public Stream<LineParser> allowedNext() {
  331.                 return Stream.of(H5, H9, ZERO);
  332.             }

  333.         },

  334.         /** Header fifth line. */
  335.         H5("H5") {

  336.             /** {@inheritDoc} */
  337.             @Override
  338.             public void parse(final String line, final ParseInfo pi) {

  339.                 // Approximate center of mass to reflector offset in meters
  340.                 final double offset = Double.parseDouble(SEPARATOR.split(line)[1]);
  341.                 pi.file.getHeader().setCenterOfMassOffset(offset);

  342.             }

  343.             /** {@inheritDoc} */
  344.             @Override
  345.             public Stream<LineParser> allowedNext() {
  346.                 return Stream.of(H9, ZERO);
  347.             }

  348.         },

  349.         /** Header last line. */
  350.         H9("H9") {

  351.             /** {@inheritDoc} */
  352.             @Override
  353.             public void parse(final String line, final ParseInfo pi) {
  354.                 // End of header. Nothing to do
  355.             }

  356.             /** {@inheritDoc} */
  357.             @Override
  358.             public Stream<LineParser> allowedNext() {
  359.                 return Stream.of(TEN, ZERO);
  360.             }

  361.         },

  362.         /** Position values. */
  363.         TEN("10") {

  364.             /** {@inheritDoc} */
  365.             @Override
  366.             public void parse(final String line, final ParseInfo pi) {

  367.                 // Data contained in the line
  368.                 final String[] values = SEPARATOR.split(line);

  369.                 // Epoch
  370.                 final int mjd           = Integer.parseInt(values[2]);
  371.                 final double secInDay   = Double.parseDouble(values[3]);
  372.                 final AbsoluteDate date = AbsoluteDate.createMJDDate(mjd, secInDay, pi.timeScale);

  373.                 // Leap second flag
  374.                 final int leap = Integer.parseInt(values[4]);

  375.                 // Coordinates
  376.                 final double x = Double.parseDouble(values[5]);
  377.                 final double y = Double.parseDouble(values[6]);
  378.                 final double z = Double.parseDouble(values[7]);
  379.                 final Vector3D position = new Vector3D(x, y, z);

  380.                 // CPF coordinate
  381.                 final CPF.CPFCoordinate coordinate = new CPF.CPFCoordinate(date, position, leap);
  382.                 pi.file.addSatelliteCoordinate(pi.file.getHeader().getIlrsSatelliteId(), coordinate);

  383.             }

  384.             /** {@inheritDoc} */
  385.             @Override
  386.             public Stream<LineParser> allowedNext() {
  387.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  388.             }

  389.         },

  390.         /** Velocity values. */
  391.         TWENTY("20") {

  392.             /** {@inheritDoc} */
  393.             @Override
  394.             public void parse(final String line, final ParseInfo pi) {
  395.                 // Not implemented yet
  396.             }

  397.             /** {@inheritDoc} */
  398.             @Override
  399.             public Stream<LineParser> allowedNext() {
  400.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  401.             }

  402.         },

  403.         /** Corrections. */
  404.         THIRTY("30") {

  405.             /** {@inheritDoc} */
  406.             @Override
  407.             public void parse(final String line, final ParseInfo pi) {
  408.                 // Not implemented yet
  409.             }

  410.             /** {@inheritDoc} */
  411.             @Override
  412.             public Stream<LineParser> allowedNext() {
  413.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  414.             }

  415.         },

  416.         /** Transponder specific. */
  417.         FORTY("40") {

  418.             /** {@inheritDoc} */
  419.             @Override
  420.             public void parse(final String line, final ParseInfo pi) {
  421.                 // Not implemented yet
  422.             }

  423.             /** {@inheritDoc} */
  424.             @Override
  425.             public Stream<LineParser> allowedNext() {
  426.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  427.             }

  428.         },

  429.         /** Offset from center of main body. */
  430.         FIFTY("50") {

  431.             /** {@inheritDoc} */
  432.             @Override
  433.             public void parse(final String line, final ParseInfo pi) {
  434.                 // Not implemented yet
  435.             }

  436.             /** {@inheritDoc} */
  437.             @Override
  438.             public Stream<LineParser> allowedNext() {
  439.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  440.             }

  441.         },

  442.         /** Rotation angle of offset. */
  443.         SIXTY("60") {

  444.             /** {@inheritDoc} */
  445.             @Override
  446.             public void parse(final String line, final ParseInfo pi) {
  447.                 // Not implemented yet
  448.             }

  449.             /** {@inheritDoc} */
  450.             @Override
  451.             public Stream<LineParser> allowedNext() {
  452.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  453.             }

  454.         },

  455.         /** Earth orientation. */
  456.         SEVENTY("70") {

  457.             /** {@inheritDoc} */
  458.             @Override
  459.             public void parse(final String line, final ParseInfo pi) {
  460.                 // Not implemented yet
  461.             }

  462.             /** {@inheritDoc} */
  463.             @Override
  464.             public Stream<LineParser> allowedNext() {
  465.                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  466.             }

  467.         },

  468.         /** Comments. */
  469.         ZERO("00") {

  470.             /** {@inheritDoc} */
  471.             @Override
  472.             public void parse(final String line, final ParseInfo pi) {

  473.                 // Comment
  474.                 final String comment = line.split(getIdentifier())[1].trim();
  475.                 pi.file.getComments().add(comment);

  476.             }

  477.             /** {@inheritDoc} */
  478.             @Override
  479.             public Stream<LineParser> allowedNext() {
  480.                 return Stream.of(H1, H2, H3, H4, H5, H9,
  481.                                  TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
  482.             }

  483.         },

  484.         /** Last record in ephemeris. */
  485.         EOF("99") {

  486.             @Override
  487.             public void parse(final String line, final ParseInfo pi) {
  488.                 pi.done = true;
  489.             }

  490.             /** {@inheritDoc} */
  491.             @Override
  492.             public Stream<LineParser> allowedNext() {
  493.                 return Stream.of(EOF);
  494.             }

  495.         };

  496.         /** Pattern for identifying line. */
  497.         private final Pattern pattern;

  498.         /** Identifier. */
  499.         private final String identifier;

  500.         /** Simple constructor.
  501.          * @param identifier regular expression for identifying line (i.e. first element)
  502.          */
  503.         LineParser(final String identifier) {
  504.             this.identifier = identifier;
  505.             pattern = Pattern.compile(identifier);
  506.         }

  507.         /**
  508.          * Get the regular expression for identifying line.
  509.          * @return the regular expression for identifying line
  510.          */
  511.         public String getIdentifier() {
  512.             return identifier;
  513.         }

  514.         /** Parse a line.
  515.          * @param line line to parse
  516.          * @param pi holder for transient data
  517.          */
  518.         public abstract void parse(String line, ParseInfo pi);

  519.         /** Get the allowed parsers for next line.
  520.          * @return allowed parsers for next line
  521.          */
  522.         public abstract Stream<LineParser> allowedNext();

  523.         /** Check if parser can handle line.
  524.          * @param line line to parse
  525.          * @return true if parser can handle the specified line
  526.          */
  527.         public boolean canHandle(final String line) {
  528.             return pattern.matcher(SEPARATOR.split(line)[0]).matches();
  529.         }

  530.     }

  531. }