ODMParser.java

  1. /* Copyright 2002-2013 CS Systèmes d'Information
  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.ccsds;

  18. import java.io.FileInputStream;
  19. import java.io.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.util.List;
  23. import java.util.regex.Matcher;
  24. import java.util.regex.Pattern;

  25. import org.apache.commons.math3.util.FastMath;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.files.general.OrbitFile;
  29. import org.orekit.time.AbsoluteDate;
  30. import org.orekit.time.DateTimeComponents;
  31. import org.orekit.time.TimeScalesFactory;
  32. import org.orekit.utils.Constants;
  33. import org.orekit.utils.IERSConventions;

  34. /** Base class for all CCSDS Orbit Data Message parsers.
  35.  * <p>
  36.  * This base class is immutable, and hence thread safe. When parts
  37.  * must be changed, such as reference date for Mission Elapsed Time or
  38.  * Mission Relative Time time systems, or the gravitational coefficient or
  39.  * the IERS conventions, the various {@code withXxx} methods must be called,
  40.  * which create a new immutable instance with the new parameters. This
  41.  * is a combination of the <a href="">builder design pattern</a> and
  42.  * a <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  43.  * interface</a>.
  44.  * </p>
  45.  * @author Luc Maisonobe
  46.  * @since 6.1
  47.  */
  48. public abstract class ODMParser {

  49.     /** Pattern for international designator. */
  50.     private static final Pattern INTERNATIONAL_DESIGNATOR = Pattern.compile("(\\p{Digit}{4})-(\\p{Digit}{3})(\\p{Upper}{1,3})");

  51.     /** Reference date for Mission Elapsed Time or Mission Relative Time time systems. */
  52.     private final AbsoluteDate missionReferenceDate;

  53.     /** Gravitational coefficient. */
  54.     private final  double mu;

  55.     /** IERS Conventions. */
  56.     private final  IERSConventions conventions;

  57.     /** Indicator for simple or accurate EOP interpolation. */
  58.     private final  boolean simpleEOP;

  59.     /** Launch Year. */
  60.     private int launchYear;

  61.     /** Launch number. */
  62.     private int launchNumber;

  63.     /** Piece of launch (from "A" to "ZZZ"). */
  64.     private String launchPiece;

  65.     /** Complete constructor.
  66.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  67.      * @param mu gravitational coefficient
  68.      * @param conventions IERS Conventions
  69.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  70.      * @param launchYear launch year for TLEs
  71.      * @param launchNumber launch number for TLEs
  72.      * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
  73.      */
  74.     protected ODMParser(final AbsoluteDate missionReferenceDate, final double mu,
  75.                         final IERSConventions conventions, final boolean simpleEOP,
  76.                         final int launchYear, final int launchNumber, final String launchPiece) {
  77.         this.missionReferenceDate = missionReferenceDate;
  78.         this.mu                   = mu;
  79.         this.conventions          = conventions;
  80.         this.simpleEOP            = simpleEOP;
  81.         this.launchYear           = launchYear;
  82.         this.launchNumber         = launchNumber;
  83.         this.launchPiece          = launchPiece;
  84.     }

  85.     /** Set initial date.
  86.      * @param newMissionReferenceDate mission reference date to use while parsing
  87.      * @return a new instance, with mission reference date replaced
  88.      * @see #getMissionReferenceDate()
  89.      */
  90.     public abstract ODMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate);

  91.     /** Get initial date.
  92.      * @return mission reference date to use while parsing
  93.      * @see #withMissionReferenceDate(AbsoluteDate)
  94.      */
  95.     public AbsoluteDate getMissionReferenceDate() {
  96.         return missionReferenceDate;
  97.     }

  98.     /** Set gravitational coefficient.
  99.      * @param newMu gravitational coefficient to use while parsing
  100.      * @return a new instance, with gravitational coefficient date replaced
  101.      * @see #getMu()
  102.      */
  103.     public abstract ODMParser withMu(final double newMu);

  104.     /** Get gravitational coefficient.
  105.      * @return gravitational coefficient to use while parsing
  106.      * @see #withMu(double)
  107.      */
  108.     public double getMu() {
  109.         return mu;
  110.     }

  111.     /** Set IERS conventions.
  112.      * @param newConventions IERS conventions to use while parsing
  113.      * @return a new instance, with IERS conventions replaced
  114.      * @see #getConventions()
  115.      */
  116.     public abstract ODMParser withConventions(final IERSConventions newConventions);

  117.     /** Get IERS conventions.
  118.      * @return IERS conventions to use while parsing
  119.      * @see #withConventions(IERSConventions)
  120.      */
  121.     public IERSConventions getConventions() {
  122.         return conventions;
  123.     }

  124.     /** Set EOP interpolation method.
  125.      * @param newSimpleEOP if true, tidal effects are ignored when interpolating EOP
  126.      * @return a new instance, with EOP interpolation method replaced
  127.      * @see #isSimpleEOP()
  128.      */
  129.     public abstract ODMParser withSimpleEOP(final boolean newSimpleEOP);

  130.     /** Get EOP interpolation method.
  131.      * @return true if tidal effects are ignored when interpolating EOP
  132.      * @see #withSimpleEOP(boolean)
  133.      */
  134.     public boolean isSimpleEOP() {
  135.         return simpleEOP;
  136.     }

  137.     /** Set international designator.
  138.      * <p>
  139.      * This method may be used to ensure the launch year number and pieces are
  140.      * correctly set if they are not present in the CCSDS file header in the
  141.      * OBJECT_ID in the form YYYY-NNN-P{PP}. If they are already in the header,
  142.      * they will be parsed automatically regardless of this method being called
  143.      * or not (i.e. header information override information set here).
  144.      * </p>
  145.      * @param newLaunchYear launch year
  146.      * @param newLaunchNumber launch number
  147.      * @param newLaunchPiece piece of launch (from "A" to "ZZZ")
  148.      * @return a new instance, with TLE settings replaced
  149.      */
  150.     public abstract ODMParser withInternationalDesignator(final int newLaunchYear,
  151.                                                           final int newLaunchNumber,
  152.                                                           final String newLaunchPiece);

  153.     /** Get the launch year.
  154.      * @return launch year
  155.      */
  156.     public int getLaunchYear() {
  157.         return launchYear;
  158.     }

  159.     /** Get the launch number.
  160.      * @return launch number
  161.      */
  162.     public int getLaunchNumber() {
  163.         return launchNumber;
  164.     }

  165.     /** Get the piece of launch.
  166.      * @return piece of launch
  167.      */
  168.     public String getLaunchPiece() {
  169.         return launchPiece;
  170.     }

  171.     /** Parse a CCSDS Orbit Data Message.
  172.      * @param fileName name of the file containing the message
  173.      * @return parsed orbit
  174.      * @exception OrekitException if orbit message cannot be parsed
  175.      */
  176.     public ODMFile parse(final String fileName)
  177.         throws OrekitException {

  178.         InputStream stream = null;

  179.         try {
  180.             stream = new FileInputStream(fileName);
  181.             return parse(stream, fileName);
  182.         } catch (FileNotFoundException e) {
  183.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
  184.         } finally {
  185.             try {
  186.                 if (stream != null) {
  187.                     stream.close();
  188.                 }
  189.             } catch (IOException e) {
  190.                 // ignore
  191.             }
  192.         }

  193.     }

  194.     /** Parse a CCSDS Orbit Data Message.
  195.      * @param stream stream containing message
  196.      * @return parsed orbit
  197.      * @exception OrekitException if orbit message cannot be parsed
  198.      */
  199.     public ODMFile parse(final InputStream stream)
  200.         throws OrekitException {
  201.         return parse(stream, "<unknown>");
  202.     }

  203.     /** Parse a CCSDS Orbit Data Message.
  204.      * @param stream stream containing message
  205.      * @param fileName name of the file containing the message (for error messages)
  206.      * @return parsed orbit
  207.      * @exception OrekitException if orbit message cannot be parsed
  208.      */
  209.     public abstract ODMFile parse(final InputStream stream, final String fileName)
  210.         throws OrekitException;

  211.     /** Parse a comment line.
  212.      * @param keyValue key=value pair containing the comment
  213.      * @param comment placeholder where the current comment line should be added
  214.      * @return true if the line was a comment line and was parsed
  215.      */
  216.     protected boolean parseComment(final KeyValue keyValue, final List<String> comment) {
  217.         if (keyValue.getKeyword() == Keyword.COMMENT) {
  218.             comment.add(keyValue.getValue());
  219.             return true;
  220.         } else {
  221.             return false;
  222.         }
  223.     }

  224.     /** Parse an entry from the header.
  225.      * @param keyValue key = value pair
  226.      * @param odmFile instance to update with parsed entry
  227.      * @param comment previous comment lines, will be emptied if used by the keyword
  228.      * @return true if the keyword was a header keyword and has been parsed
  229.      * @exception OrekitException if UTC time scale cannot be retrieved to parse creation date
  230.      */
  231.     protected boolean parseHeaderEntry(final KeyValue keyValue,
  232.                                        final ODMFile odmFile, final List<String> comment)
  233.         throws OrekitException {
  234.         switch (keyValue.getKeyword()) {

  235.         case CREATION_DATE:
  236.             if (!comment.isEmpty()) {
  237.                 odmFile.setHeaderComment(comment);
  238.                 comment.clear();
  239.             }
  240.             odmFile.setCreationDate(new AbsoluteDate(keyValue.getValue(), TimeScalesFactory.getUTC()));
  241.             return true;

  242.         case ORIGINATOR:
  243.             odmFile.setOriginator(keyValue.getValue());
  244.             return true;

  245.         default:
  246.             return false;

  247.         }

  248.     }

  249.     /** Parse a meta-data key = value entry.
  250.      * @param keyValue key = value pair
  251.      * @param metaData instance to update with parsed entry
  252.      * @param comment previous comment lines, will be emptied if used by the keyword
  253.      * @return true if the keyword was a meta-data keyword and has been parsed
  254.      * @exception OrekitException if center body or frame cannot be retrieved
  255.      */
  256.     protected boolean parseMetaDataEntry(final KeyValue keyValue,
  257.                                          final ODMMetaData metaData, final List<String> comment)
  258.         throws OrekitException {
  259.         switch (keyValue.getKeyword()) {
  260.         case OBJECT_NAME:
  261.             if (!comment.isEmpty()) {
  262.                 metaData.setComment(comment);
  263.                 comment.clear();
  264.             }
  265.             metaData.setObjectName(keyValue.getValue());
  266.             return true;

  267.         case OBJECT_ID: {
  268.             metaData.setObjectID(keyValue.getValue());
  269.             final Matcher matcher = INTERNATIONAL_DESIGNATOR.matcher(keyValue.getValue());
  270.             if (matcher.matches()) {
  271.                 metaData.setLaunchYear(Integer.parseInt(matcher.group(1)));
  272.                 metaData.setLaunchNumber(Integer.parseInt(matcher.group(2)));
  273.                 metaData.setLaunchPiece(matcher.group(3));
  274.             }
  275.             return true;
  276.         }

  277.         case CENTER_NAME:
  278.             metaData.setCenterName(keyValue.getValue());
  279.             final String canonicalValue;
  280.             if (keyValue.getValue().equals("SOLAR SYSTEM BARYCENTER") || keyValue.getValue().equals("SSB")) {
  281.                 canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
  282.             } else if (keyValue.getValue().equals("EARTH MOON BARYCENTER") || keyValue.getValue().equals("EARTH-MOON BARYCENTER") ||
  283.                        keyValue.getValue().equals("EARTH BARYCENTER") || keyValue.getValue().equals("EMB")) {
  284.                 canonicalValue = "EARTH_MOON";
  285.             } else {
  286.                 canonicalValue = keyValue.getValue();
  287.             }
  288.             for (final CenterName c : CenterName.values()) {
  289.                 if (c.name().equals(canonicalValue)) {
  290.                     metaData.setHasCreatableBody(true);
  291.                     metaData.setCenterBody(c.getCelestialBody());
  292.                     metaData.getODMFile().setMuCreated(c.getCelestialBody().getGM());
  293.                 }
  294.             }
  295.             return true;

  296.         case REF_FRAME:
  297.             metaData.setRefFrame(parseCCSDSFrame(keyValue.getValue()).getFrame(getConventions(), isSimpleEOP()));
  298.             return true;

  299.         case REF_FRAME_EPOCH:
  300.             metaData.setFrameEpochString(keyValue.getValue());
  301.             return true;

  302.         case TIME_SYSTEM:
  303.             final OrbitFile.TimeSystem timeSystem = OrbitFile.TimeSystem.valueOf(keyValue.getValue());
  304.             metaData.setTimeSystem(timeSystem);
  305.             if (metaData.getFrameEpochString() != null) {
  306.                 metaData.setFrameEpoch(parseDate(metaData.getFrameEpochString(), timeSystem));
  307.             }
  308.             return true;

  309.         default:
  310.             return false;
  311.         }
  312.     }

  313.     /** Parse a general state data key = value entry.
  314.      * @param keyValue key = value pair
  315.      * @param general instance to update with parsed entry
  316.      * @param comment previous comment lines, will be emptied if used by the keyword
  317.      * @return true if the keyword was a meta-data keyword and has been parsed
  318.      * @exception OrekitException if center body or frame cannot be retrieved
  319.      */
  320.     protected boolean parseGeneralStateDataEntry(final KeyValue keyValue,
  321.                                                  final OGMFile general, final List<String> comment)
  322.         throws OrekitException {
  323.         switch (keyValue.getKeyword()) {

  324.         case EPOCH:
  325.             general.setEpochComment(comment);
  326.             comment.clear();
  327.             general.setEpoch(parseDate(keyValue.getValue(), general.getTimeSystem()));
  328.             return true;

  329.         case SEMI_MAJOR_AXIS:
  330.             general.setKeplerianElementsComment(comment);
  331.             comment.clear();
  332.             general.setA(keyValue.getDoubleValue() * 1000);
  333.             general.setHasKeplerianElements(true);
  334.             return true;

  335.         case ECCENTRICITY:
  336.             general.setE(keyValue.getDoubleValue());
  337.             return true;

  338.         case INCLINATION:
  339.             general.setI(FastMath.toRadians(keyValue.getDoubleValue()));
  340.             return true;

  341.         case RA_OF_ASC_NODE:
  342.             general.setRaan(FastMath.toRadians(keyValue.getDoubleValue()));
  343.             return true;

  344.         case ARG_OF_PERICENTER:
  345.             general.setPa(FastMath.toRadians(keyValue.getDoubleValue()));
  346.             return true;

  347.         case TRUE_ANOMALY:
  348.             general.setAnomalyType("TRUE");
  349.             general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  350.             return true;

  351.         case MEAN_ANOMALY:
  352.             general.setAnomalyType("MEAN");
  353.             general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  354.             return true;

  355.         case GM:
  356.             general.setMuParsed(keyValue.getDoubleValue() * 1e9);
  357.             return true;

  358.         case MASS:
  359.             comment.addAll(0, general.getSpacecraftComment());
  360.             general.setSpacecraftComment(comment);
  361.             comment.clear();
  362.             general.setMass(keyValue.getDoubleValue());
  363.             return true;

  364.         case SOLAR_RAD_AREA:
  365.             comment.addAll(0, general.getSpacecraftComment());
  366.             general.setSpacecraftComment(comment);
  367.             comment.clear();
  368.             general.setSolarRadArea(keyValue.getDoubleValue());
  369.             return true;

  370.         case SOLAR_RAD_COEFF:
  371.             comment.addAll(0, general.getSpacecraftComment());
  372.             general.setSpacecraftComment(comment);
  373.             comment.clear();
  374.             general.setSolarRadCoeff(keyValue.getDoubleValue());
  375.             return true;

  376.         case DRAG_AREA:
  377.             comment.addAll(0, general.getSpacecraftComment());
  378.             general.setSpacecraftComment(comment);
  379.             comment.clear();
  380.             general.setDragArea(keyValue.getDoubleValue());
  381.             return true;

  382.         case DRAG_COEFF:
  383.             comment.addAll(0, general.getSpacecraftComment());
  384.             general.setSpacecraftComment(comment);
  385.             comment.clear();
  386.             general.setDragCoeff(keyValue.getDoubleValue());
  387.             return true;

  388.         case COV_REF_FRAME:
  389.             general.setCovarianceComment(comment);
  390.             comment.clear();
  391.             final CCSDSFrame covFrame = parseCCSDSFrame(keyValue.getValue());
  392.             if (covFrame.isLof()) {
  393.                 general.setCovRefLofType(covFrame.getLofType());
  394.             } else {
  395.                 general.setCovRefFrame(covFrame.getFrame(getConventions(), isSimpleEOP()));
  396.             }
  397.             return true;

  398.         case CX_X:
  399.             general.createCovarianceMatrix();
  400.             general.setCovarianceMatrixEntry(0, 0, keyValue.getDoubleValue());
  401.             return true;

  402.         case CY_X:
  403.             general.setCovarianceMatrixEntry(0, 1, keyValue.getDoubleValue());
  404.             return true;

  405.         case CY_Y:
  406.             general.setCovarianceMatrixEntry(1, 1, keyValue.getDoubleValue());
  407.             return true;

  408.         case CZ_X:
  409.             general.setCovarianceMatrixEntry(0, 2, keyValue.getDoubleValue());
  410.             return true;

  411.         case CZ_Y:
  412.             general.setCovarianceMatrixEntry(1, 2, keyValue.getDoubleValue());
  413.             return true;

  414.         case CZ_Z:
  415.             general.setCovarianceMatrixEntry(2, 2, keyValue.getDoubleValue());
  416.             return true;

  417.         case CX_DOT_X:
  418.             general.setCovarianceMatrixEntry(0, 3, keyValue.getDoubleValue());
  419.             return true;

  420.         case CX_DOT_Y:
  421.             general.setCovarianceMatrixEntry(1, 3, keyValue.getDoubleValue());
  422.             return true;

  423.         case CX_DOT_Z:
  424.             general.setCovarianceMatrixEntry(2, 3, keyValue.getDoubleValue());
  425.             return true;

  426.         case CX_DOT_X_DOT:
  427.             general.setCovarianceMatrixEntry(3, 3, keyValue.getDoubleValue());
  428.             return true;

  429.         case CY_DOT_X:
  430.             general.setCovarianceMatrixEntry(0, 4, keyValue.getDoubleValue());
  431.             return true;

  432.         case CY_DOT_Y:
  433.             general.setCovarianceMatrixEntry(1, 4, keyValue.getDoubleValue());
  434.             return true;

  435.         case CY_DOT_Z:
  436.             general.setCovarianceMatrixEntry(2, 4, keyValue.getDoubleValue());
  437.             return true;

  438.         case CY_DOT_X_DOT:
  439.             general.setCovarianceMatrixEntry(3, 4, keyValue.getDoubleValue());
  440.             return true;

  441.         case CY_DOT_Y_DOT:
  442.             general.setCovarianceMatrixEntry(4, 4, keyValue.getDoubleValue());
  443.             return true;

  444.         case CZ_DOT_X:
  445.             general.setCovarianceMatrixEntry(0, 5, keyValue.getDoubleValue());
  446.             return true;

  447.         case CZ_DOT_Y:
  448.             general.setCovarianceMatrixEntry(1, 5, keyValue.getDoubleValue());
  449.             return true;

  450.         case CZ_DOT_Z:
  451.             general.setCovarianceMatrixEntry(2, 5, keyValue.getDoubleValue());
  452.             return true;

  453.         case CZ_DOT_X_DOT:
  454.             general.setCovarianceMatrixEntry(3, 5, keyValue.getDoubleValue());
  455.             return true;

  456.         case CZ_DOT_Y_DOT:
  457.             general.setCovarianceMatrixEntry(4, 5, keyValue.getDoubleValue());
  458.             return true;

  459.         case CZ_DOT_Z_DOT:
  460.             general.setCovarianceMatrixEntry(5, 5, keyValue.getDoubleValue());
  461.             return true;

  462.         case USER_DEFINED_X:
  463.             general.setUserDefinedParameters(keyValue.getKey(), keyValue.getValue());
  464.             return true;

  465.         default:
  466.             return false;
  467.         }
  468.     }

  469.     /** Parse a CCSDS frame.
  470.      * @param frameName name of the frame, as the value of a CCSDS key=value line
  471.      * @return CCSDS frame corresponding to the name
  472.      */
  473.     protected CCSDSFrame parseCCSDSFrame(final String frameName) {
  474.         return CCSDSFrame.valueOf(frameName.replaceAll("-", ""));
  475.     }

  476.     /** Parse a date.
  477.      * @param date date to parse, as the value of a CCSDS key=value line
  478.      * @param timeSystem time system to use
  479.      * @return parsed date
  480.      * @exception OrekitException if some time scale cannot be retrieved
  481.      */
  482.     protected AbsoluteDate parseDate(final String date, final OrbitFile.TimeSystem timeSystem)
  483.         throws OrekitException {
  484.         switch (timeSystem) {
  485.         case GMST:
  486.             return new AbsoluteDate(date, TimeScalesFactory.getGMST());
  487.         case GPS:
  488.             return new AbsoluteDate(date, TimeScalesFactory.getGPS());
  489.         case TAI:
  490.             return new AbsoluteDate(date, TimeScalesFactory.getTAI());
  491.         case TCB:
  492.             return new AbsoluteDate(date, TimeScalesFactory.getTCB());
  493.         case TDB:
  494.             return new AbsoluteDate(date, TimeScalesFactory.getTDB());
  495.         case TCG:
  496.             return new AbsoluteDate(date, TimeScalesFactory.getTCG());
  497.         case TT:
  498.             return new AbsoluteDate(date, TimeScalesFactory.getTT());
  499.         case UT1:
  500.             return new AbsoluteDate(date, TimeScalesFactory.getUT1(conventions, false));
  501.         case UTC:
  502.             return new AbsoluteDate(date, TimeScalesFactory.getUTC());
  503.         case MET: {
  504.             final DateTimeComponents clock = DateTimeComponents.parseDateTime(date);
  505.             final double offset = clock.getDate().getYear() * Constants.JULIAN_YEAR +
  506.                     clock.getDate().getDayOfYear() * Constants.JULIAN_DAY +
  507.                     clock.getTime().getSecondsInDay();
  508.             return missionReferenceDate.shiftedBy(offset);
  509.         }
  510.         case MRT: {
  511.             final DateTimeComponents clock = DateTimeComponents.parseDateTime(date);
  512.             final double offset = clock.getDate().getYear() * Constants.JULIAN_YEAR +
  513.                     clock.getDate().getDayOfYear() * Constants.JULIAN_DAY +
  514.                     clock.getTime().getSecondsInDay();
  515.             return missionReferenceDate.shiftedBy(offset);
  516.         }
  517.         default:
  518.             throw OrekitException.createInternalError(null);
  519.         }
  520.     }

  521. }