ODMParser.java

  1. /* Copyright 2002-2018 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.IOException;
  20. import java.io.InputStream;
  21. import java.util.List;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;

  24. import org.hipparchus.util.FastMath;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.TimeScalesFactory;
  29. import org.orekit.utils.IERSConventions;

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

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

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

  50.     /** Gravitational coefficient. */
  51.     private final  double mu;

  52.     /** IERS Conventions. */
  53.     private final  IERSConventions conventions;

  54.     /** Indicator for simple or accurate EOP interpolation. */
  55.     private final  boolean simpleEOP;

  56.     /** Launch Year. */
  57.     private int launchYear;

  58.     /** Launch number. */
  59.     private int launchNumber;

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

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

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

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

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

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

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

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

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

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

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

  150.     /** Get the launch year.
  151.      * @return launch year
  152.      */
  153.     public int getLaunchYear() {
  154.         return launchYear;
  155.     }

  156.     /** Get the launch number.
  157.      * @return launch number
  158.      */
  159.     public int getLaunchNumber() {
  160.         return launchNumber;
  161.     }

  162.     /** Get the piece of launch.
  163.      * @return piece of launch
  164.      */
  165.     public String getLaunchPiece() {
  166.         return launchPiece;
  167.     }

  168.     /** Parse a CCSDS Orbit Data Message.
  169.      * @param fileName name of the file containing the message
  170.      * @return parsed orbit
  171.      * @exception OrekitException if orbit message cannot be parsed
  172.      */
  173.     public ODMFile parse(final String fileName)
  174.         throws OrekitException {
  175.         try (InputStream stream = new FileInputStream(fileName)) {
  176.             return parse(stream, fileName);
  177.         } catch (IOException e) {
  178.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
  179.         }
  180.     }

  181.     /** Parse a CCSDS Orbit Data Message.
  182.      * @param stream stream containing message
  183.      * @return parsed orbit
  184.      * @exception OrekitException if orbit message cannot be parsed
  185.      */
  186.     public ODMFile parse(final InputStream stream)
  187.         throws OrekitException {
  188.         return parse(stream, "<unknown>");
  189.     }

  190.     /** Parse a CCSDS Orbit Data Message.
  191.      * @param stream stream containing message
  192.      * @param fileName name of the file containing the message (for error messages)
  193.      * @return parsed orbit
  194.      * @exception OrekitException if orbit message cannot be parsed
  195.      */
  196.     public abstract ODMFile parse(InputStream stream, String fileName)
  197.         throws OrekitException;

  198.     /** Parse a comment line.
  199.      * @param keyValue key=value pair containing the comment
  200.      * @param comment placeholder where the current comment line should be added
  201.      * @return true if the line was a comment line and was parsed
  202.      */
  203.     protected boolean parseComment(final KeyValue keyValue, final List<String> comment) {
  204.         if (keyValue.getKeyword() == Keyword.COMMENT) {
  205.             comment.add(keyValue.getValue());
  206.             return true;
  207.         } else {
  208.             return false;
  209.         }
  210.     }

  211.     /** Parse an entry from the header.
  212.      * @param keyValue key = value pair
  213.      * @param odmFile instance to update with parsed entry
  214.      * @param comment previous comment lines, will be emptied if used by the keyword
  215.      * @return true if the keyword was a header keyword and has been parsed
  216.      * @exception OrekitException if UTC time scale cannot be retrieved to parse creation date
  217.      */
  218.     protected boolean parseHeaderEntry(final KeyValue keyValue,
  219.                                        final ODMFile odmFile, final List<String> comment)
  220.         throws OrekitException {
  221.         switch (keyValue.getKeyword()) {

  222.             case CREATION_DATE:
  223.                 if (!comment.isEmpty()) {
  224.                     odmFile.setHeaderComment(comment);
  225.                     comment.clear();
  226.                 }
  227.                 odmFile.setCreationDate(new AbsoluteDate(keyValue.getValue(), TimeScalesFactory.getUTC()));
  228.                 return true;

  229.             case ORIGINATOR:
  230.                 odmFile.setOriginator(keyValue.getValue());
  231.                 return true;

  232.             default:
  233.                 return false;

  234.         }

  235.     }

  236.     /** Parse a meta-data key = value entry.
  237.      * @param keyValue key = value pair
  238.      * @param metaData instance to update with parsed entry
  239.      * @param comment previous comment lines, will be emptied if used by the keyword
  240.      * @return true if the keyword was a meta-data keyword and has been parsed
  241.      * @exception OrekitException if center body or frame cannot be retrieved
  242.      */
  243.     protected boolean parseMetaDataEntry(final KeyValue keyValue,
  244.                                          final ODMMetaData metaData, final List<String> comment)
  245.         throws OrekitException {
  246.         switch (keyValue.getKeyword()) {
  247.             case OBJECT_NAME:
  248.                 if (!comment.isEmpty()) {
  249.                     metaData.setComment(comment);
  250.                     comment.clear();
  251.                 }
  252.                 metaData.setObjectName(keyValue.getValue());
  253.                 return true;

  254.             case OBJECT_ID: {
  255.                 metaData.setObjectID(keyValue.getValue());
  256.                 final Matcher matcher = INTERNATIONAL_DESIGNATOR.matcher(keyValue.getValue());
  257.                 if (matcher.matches()) {
  258.                     metaData.setLaunchYear(Integer.parseInt(matcher.group(1)));
  259.                     metaData.setLaunchNumber(Integer.parseInt(matcher.group(2)));
  260.                     metaData.setLaunchPiece(matcher.group(3));
  261.                 }
  262.                 return true;
  263.             }

  264.             case CENTER_NAME:
  265.                 metaData.setCenterName(keyValue.getValue());
  266.                 final String canonicalValue;
  267.                 if (keyValue.getValue().equals("SOLAR SYSTEM BARYCENTER") || keyValue.getValue().equals("SSB")) {
  268.                     canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
  269.                 } else if (keyValue.getValue().equals("EARTH MOON BARYCENTER") || keyValue.getValue().equals("EARTH-MOON BARYCENTER") ||
  270.                         keyValue.getValue().equals("EARTH BARYCENTER") || keyValue.getValue().equals("EMB")) {
  271.                     canonicalValue = "EARTH_MOON";
  272.                 } else {
  273.                     canonicalValue = keyValue.getValue();
  274.                 }
  275.                 for (final CenterName c : CenterName.values()) {
  276.                     if (c.name().equals(canonicalValue)) {
  277.                         metaData.setHasCreatableBody(true);
  278.                         metaData.setCenterBody(c.getCelestialBody());
  279.                         metaData.getODMFile().setMuCreated(c.getCelestialBody().getGM());
  280.                     }
  281.                 }
  282.                 return true;

  283.             case REF_FRAME:
  284.                 metaData.setFrameString(keyValue.getValue());
  285.                 metaData.setRefFrame(parseCCSDSFrame(keyValue.getValue()).getFrame(getConventions(), isSimpleEOP()));
  286.                 return true;

  287.             case REF_FRAME_EPOCH:
  288.                 metaData.setFrameEpochString(keyValue.getValue());
  289.                 return true;

  290.             case TIME_SYSTEM:
  291.                 if (!CcsdsTimeScale.contains(keyValue.getValue())) {
  292.                     throw new OrekitException(
  293.                             OrekitMessages.CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED,
  294.                             keyValue.getValue());
  295.                 }
  296.                 final CcsdsTimeScale timeSystem =
  297.                         CcsdsTimeScale.valueOf(keyValue.getValue());
  298.                 metaData.setTimeSystem(timeSystem);
  299.                 if (metaData.getFrameEpochString() != null) {
  300.                     metaData.setFrameEpoch(parseDate(metaData.getFrameEpochString(), timeSystem));
  301.                 }
  302.                 return true;

  303.             default:
  304.                 return false;
  305.         }
  306.     }

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

  318.             case EPOCH:
  319.                 general.setEpochComment(comment);
  320.                 comment.clear();
  321.                 general.setEpoch(parseDate(keyValue.getValue(), general.getMetaData().getTimeSystem()));
  322.                 return true;

  323.             case SEMI_MAJOR_AXIS:
  324.                 general.setKeplerianElementsComment(comment);
  325.                 comment.clear();
  326.                 general.setA(keyValue.getDoubleValue() * 1000);
  327.                 general.setHasKeplerianElements(true);
  328.                 return true;

  329.             case ECCENTRICITY:
  330.                 general.setE(keyValue.getDoubleValue());
  331.                 return true;

  332.             case INCLINATION:
  333.                 general.setI(FastMath.toRadians(keyValue.getDoubleValue()));
  334.                 return true;

  335.             case RA_OF_ASC_NODE:
  336.                 general.setRaan(FastMath.toRadians(keyValue.getDoubleValue()));
  337.                 return true;

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

  341.             case TRUE_ANOMALY:
  342.                 general.setAnomalyType("TRUE");
  343.                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  344.                 return true;

  345.             case MEAN_ANOMALY:
  346.                 general.setAnomalyType("MEAN");
  347.                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  348.                 return true;

  349.             case GM:
  350.                 general.setMuParsed(keyValue.getDoubleValue() * 1e9);
  351.                 return true;

  352.             case MASS:
  353.                 comment.addAll(0, general.getSpacecraftComment());
  354.                 general.setSpacecraftComment(comment);
  355.                 comment.clear();
  356.                 general.setMass(keyValue.getDoubleValue());
  357.                 return true;

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

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

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

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

  382.             case COV_REF_FRAME:
  383.                 general.setCovarianceComment(comment);
  384.                 comment.clear();
  385.                 final CCSDSFrame covFrame = parseCCSDSFrame(keyValue.getValue());
  386.                 if (covFrame.isLof()) {
  387.                     general.setCovRefLofType(covFrame.getLofType());
  388.                 } else {
  389.                     general.setCovRefFrame(covFrame.getFrame(getConventions(), isSimpleEOP()));
  390.                 }
  391.                 return true;

  392.             case CX_X:
  393.                 general.createCovarianceMatrix();
  394.                 general.setCovarianceMatrixEntry(0, 0, keyValue.getDoubleValue() * 1.0e6);
  395.                 return true;

  396.             case CY_X:
  397.                 general.setCovarianceMatrixEntry(0, 1, keyValue.getDoubleValue() * 1.0e6);
  398.                 return true;

  399.             case CY_Y:
  400.                 general.setCovarianceMatrixEntry(1, 1, keyValue.getDoubleValue() * 1.0e6);
  401.                 return true;

  402.             case CZ_X:
  403.                 general.setCovarianceMatrixEntry(0, 2, keyValue.getDoubleValue() * 1.0e6);
  404.                 return true;

  405.             case CZ_Y:
  406.                 general.setCovarianceMatrixEntry(1, 2, keyValue.getDoubleValue() * 1.0e6);
  407.                 return true;

  408.             case CZ_Z:
  409.                 general.setCovarianceMatrixEntry(2, 2, keyValue.getDoubleValue() * 1.0e6);
  410.                 return true;

  411.             case CX_DOT_X:
  412.                 general.setCovarianceMatrixEntry(0, 3, keyValue.getDoubleValue() * 1.0e6);
  413.                 return true;

  414.             case CX_DOT_Y:
  415.                 general.setCovarianceMatrixEntry(1, 3, keyValue.getDoubleValue() * 1.0e6);
  416.                 return true;

  417.             case CX_DOT_Z:
  418.                 general.setCovarianceMatrixEntry(2, 3, keyValue.getDoubleValue() * 1.0e6);
  419.                 return true;

  420.             case CX_DOT_X_DOT:
  421.                 general.setCovarianceMatrixEntry(3, 3, keyValue.getDoubleValue() * 1.0e6);
  422.                 return true;

  423.             case CY_DOT_X:
  424.                 general.setCovarianceMatrixEntry(0, 4, keyValue.getDoubleValue() * 1.0e6);
  425.                 return true;

  426.             case CY_DOT_Y:
  427.                 general.setCovarianceMatrixEntry(1, 4, keyValue.getDoubleValue() * 1.0e6);
  428.                 return true;

  429.             case CY_DOT_Z:
  430.                 general.setCovarianceMatrixEntry(2, 4, keyValue.getDoubleValue() * 1.0e6);
  431.                 return true;

  432.             case CY_DOT_X_DOT:
  433.                 general.setCovarianceMatrixEntry(3, 4, keyValue.getDoubleValue() * 1.0e6);
  434.                 return true;

  435.             case CY_DOT_Y_DOT:
  436.                 general.setCovarianceMatrixEntry(4, 4, keyValue.getDoubleValue() * 1.0e6);
  437.                 return true;

  438.             case CZ_DOT_X:
  439.                 general.setCovarianceMatrixEntry(0, 5, keyValue.getDoubleValue() * 1.0e6);
  440.                 return true;

  441.             case CZ_DOT_Y:
  442.                 general.setCovarianceMatrixEntry(1, 5, keyValue.getDoubleValue() * 1.0e6);
  443.                 return true;

  444.             case CZ_DOT_Z:
  445.                 general.setCovarianceMatrixEntry(2, 5, keyValue.getDoubleValue() * 1.0e6);
  446.                 return true;

  447.             case CZ_DOT_X_DOT:
  448.                 general.setCovarianceMatrixEntry(3, 5, keyValue.getDoubleValue() * 1.0e6);
  449.                 return true;

  450.             case CZ_DOT_Y_DOT:
  451.                 general.setCovarianceMatrixEntry(4, 5, keyValue.getDoubleValue() * 1.0e6);
  452.                 return true;

  453.             case CZ_DOT_Z_DOT:
  454.                 general.setCovarianceMatrixEntry(5, 5, keyValue.getDoubleValue() * 1.0e6);
  455.                 return true;

  456.             case USER_DEFINED_X:
  457.                 general.setUserDefinedParameters(keyValue.getKey(), keyValue.getValue());
  458.                 return true;

  459.             default:
  460.                 return false;
  461.         }
  462.     }

  463.     /** Parse a CCSDS frame.
  464.      * @param frameName name of the frame, as the value of a CCSDS key=value line
  465.      * @return CCSDS frame corresponding to the name
  466.      */
  467.     protected CCSDSFrame parseCCSDSFrame(final String frameName) {
  468.         return CCSDSFrame.valueOf(frameName.replaceAll("-", ""));
  469.     }

  470.     /** Parse a date.
  471.      * @param date date to parse, as the value of a CCSDS key=value line
  472.      * @param timeSystem time system to use
  473.      * @return parsed date
  474.      * @exception OrekitException if some time scale cannot be retrieved
  475.      */
  476.     protected AbsoluteDate parseDate(final String date, final CcsdsTimeScale timeSystem)
  477.         throws OrekitException {
  478.         return timeSystem.parseDate(date, conventions, missionReferenceDate);
  479.     }

  480. }