ADMParser.java

  1. /* Copyright 2002-2020 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.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.complex.Quaternion;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.bodies.CelestialBodies;
  28. import org.orekit.data.DataContext;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.utils.IERSConventions;

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

  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.     /** Data context used for obtain frames and time scales. */
  60.     private final DataContext dataContext;

  61.     /** Launch Year. */
  62.     private int launchYear;

  63.     /** Launch number. */
  64.     private int launchNumber;

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

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

  91.     /**
  92.      * Set initial date.
  93.      * @param newMissionReferenceDate mission reference date to use while parsing
  94.      * @return a new instance, with mission reference date replaced
  95.      * @see #getMissionReferenceDate()
  96.      */
  97.     public abstract ADMParser withMissionReferenceDate(AbsoluteDate newMissionReferenceDate);

  98.     /**
  99.      * Get initial date.
  100.      * @return mission reference date to use while parsing
  101.      * @see #withMissionReferenceDate(AbsoluteDate)
  102.      */
  103.     public AbsoluteDate getMissionReferenceDate() {
  104.         return missionReferenceDate;
  105.     }

  106.     /**
  107.      * Set gravitational coefficient.
  108.      * @param newMu gravitational coefficient to use while parsing
  109.      * @return a new instance, with gravitational coefficient date replaced
  110.      * @see #getMu()
  111.      */
  112.     public abstract ADMParser withMu(double newMu);

  113.     /**
  114.      * Get gravitational coefficient.
  115.      * @return gravitational coefficient to use while parsing
  116.      * @see #withMu(double)
  117.      */
  118.     public double getMu() {
  119.         return mu;
  120.     }

  121.     /**
  122.      * Set IERS conventions.
  123.      * @param newConventions IERS conventions to use while parsing
  124.      * @return a new instance, with IERS conventions replaced
  125.      * @see #getConventions()
  126.      */
  127.     public abstract ADMParser withConventions(IERSConventions newConventions);

  128.     /**
  129.      * Get IERS conventions.
  130.      * @return IERS conventions to use while parsing
  131.      * @see #withConventions(IERSConventions)
  132.      */
  133.     public IERSConventions getConventions() {
  134.         return conventions;
  135.     }

  136.     /**
  137.      * Set EOP interpolation method.
  138.      * @param newSimpleEOP if true, tidal effects are ignored when interpolating EOP
  139.      * @return a new instance, with EOP interpolation method replaced
  140.      * @see #isSimpleEOP()
  141.      */
  142.     public abstract ADMParser withSimpleEOP(boolean newSimpleEOP);

  143.     /**
  144.      * Get EOP interpolation method.
  145.      * @return true if tidal effects are ignored when interpolating EOP
  146.      * @see #withSimpleEOP(boolean)
  147.      */
  148.     public boolean isSimpleEOP() {
  149.         return simpleEOP;
  150.     }

  151.     /**
  152.      * Set international designator.
  153.      * <p>
  154.      * This method may be used to ensure the launch year number and pieces are
  155.      * correctly set if they are not present in the CCSDS file header in the
  156.      * OBJECT_ID in the form YYYY-NNNP{PP}. If they are already in the header,
  157.      * they will be parsed automatically regardless of this method being called
  158.      * or not (i.e. header information override information set here).
  159.      * </p>
  160.      * @param newLaunchYear launch year
  161.      * @param newLaunchNumber launch number
  162.      * @param newLaunchPiece piece of launch (from "A" to "ZZZ")
  163.      * @return a new instance, with TLE settings replaced
  164.      */
  165.     public abstract ADMParser withInternationalDesignator(int newLaunchYear,
  166.                                                           int newLaunchNumber,
  167.                                                           String newLaunchPiece);

  168.     /**
  169.      * Get the launch year.
  170.      * @return launch year
  171.      */
  172.     public int getLaunchYear() {
  173.         return launchYear;
  174.     }

  175.     /**
  176.      * Get the launch number.
  177.      * @return launch number
  178.      */
  179.     public int getLaunchNumber() {
  180.         return launchNumber;
  181.     }

  182.     /**
  183.      * Get the piece of launch.
  184.      * @return piece of launch
  185.      */
  186.     public String getLaunchPiece() {
  187.         return launchPiece;
  188.     }

  189.     /**
  190.      * Get the data context used for getting frames, time scales, and celestial bodies.
  191.      * @return the data context.
  192.      */
  193.     public DataContext getDataContext() {
  194.         return dataContext;
  195.     }

  196.     /**
  197.      * Set the data context.
  198.      * @param newDataContext used for frames, time scales, and celestial bodies.
  199.      * @return a new instance with the data context replaced.
  200.      */
  201.     public abstract ADMParser withDataContext(DataContext newDataContext);

  202.     /**
  203.      * Parse a CCSDS Attitude Data Message.
  204.      * @param fileName name of the file containing the message
  205.      * @return parsed ADM file
  206.      */
  207.     public ADMFile parse(final String fileName) {
  208.         try (InputStream stream = new FileInputStream(fileName)) {
  209.             return parse(stream, fileName);
  210.         } catch (IOException e) {
  211.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
  212.         }
  213.     }

  214.     /**
  215.      * Parse a CCSDS Attitude Data Message.
  216.      * @param stream stream containing message
  217.      * @return parsed ADM file
  218.      */
  219.     public ADMFile parse(final InputStream stream) {
  220.         return parse(stream, "<unknown>");
  221.     }

  222.     /**
  223.      * Parse a CCSDS Attitude Data Message.
  224.      * @param stream stream containing message
  225.      * @param fileName name of the file containing the message (for error messages)
  226.      * @return parsed ADM file
  227.      */
  228.     public abstract ADMFile parse(InputStream stream, String fileName);

  229.     /**
  230.      * Parse a comment line.
  231.      * @param keyValue key=value pair containing the comment
  232.      * @param comment placeholder where the current comment line should be added
  233.      * @return true if the line was a comment line and was parsed
  234.      */
  235.     protected boolean parseComment(final KeyValue keyValue, final List<String> comment) {
  236.         if (keyValue.getKeyword() == Keyword.COMMENT) {
  237.             comment.add(keyValue.getValue());
  238.             return true;
  239.         } else {
  240.             return false;
  241.         }
  242.     }

  243.     /**
  244.      * Parse an entry from the header.
  245.      * @param keyValue key = value pair
  246.      * @param admFile instance to update with parsed entry
  247.      * @param comment previous comment lines, will be emptied if used by the keyword
  248.      * @return true if the keyword was a header keyword and has been parsed
  249.      */
  250.     protected boolean parseHeaderEntry(final KeyValue keyValue,
  251.                                        final ADMFile admFile, final List<String> comment) {
  252.         switch (keyValue.getKeyword()) {

  253.             case CREATION_DATE:
  254.                 if (!comment.isEmpty()) {
  255.                     admFile.setHeaderComment(comment);
  256.                     comment.clear();
  257.                 }
  258.                 admFile.setCreationDate(new AbsoluteDate(
  259.                         keyValue.getValue(),
  260.                         dataContext.getTimeScales().getUTC()));
  261.                 return true;

  262.             case ORIGINATOR:
  263.                 admFile.setOriginator(keyValue.getValue());
  264.                 return true;

  265.             default:
  266.                 return false;

  267.         }

  268.     }

  269.     /**
  270.      * Parse a meta-data key = value entry.
  271.      * @param keyValue key = value pair
  272.      * @param metaData instance to update with parsed entry
  273.      * @param comment previous comment lines, will be emptied if used by the keyword
  274.      * @return true if the keyword was a meta-data keyword and has been parsed
  275.      */
  276.     protected boolean parseMetaDataEntry(final KeyValue keyValue,
  277.                                          final ADMMetaData metaData, final List<String> comment) {
  278.         switch (keyValue.getKeyword()) {
  279.             case OBJECT_NAME:
  280.                 if (!comment.isEmpty()) {
  281.                     metaData.setComment(comment);
  282.                     comment.clear();
  283.                 }
  284.                 metaData.setObjectName(keyValue.getValue());
  285.                 return true;

  286.             case OBJECT_ID: {
  287.                 metaData.setObjectID(keyValue.getValue());
  288.                 final Matcher matcher = INTERNATIONAL_DESIGNATOR.matcher(keyValue.getValue());
  289.                 if (matcher.matches()) {
  290.                     metaData.setLaunchYear(Integer.parseInt(matcher.group(1)));
  291.                     metaData.setLaunchNumber(Integer.parseInt(matcher.group(2)));
  292.                     metaData.setLaunchPiece(matcher.group(3));
  293.                 }
  294.                 return true;
  295.             }

  296.             case CENTER_NAME:
  297.                 metaData.setCenterName(keyValue.getValue());
  298.                 final String canonicalValue;
  299.                 if (keyValue.getValue().equals("SOLAR SYSTEM BARYCENTER") || keyValue.getValue().equals("SSB")) {
  300.                     canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
  301.                 } else if (keyValue.getValue().equals("EARTH MOON BARYCENTER") || keyValue.getValue().equals("EARTH-MOON BARYCENTER") ||
  302.                         keyValue.getValue().equals("EARTH BARYCENTER") || keyValue.getValue().equals("EMB")) {
  303.                     canonicalValue = "EARTH_MOON";
  304.                 } else {
  305.                     canonicalValue = keyValue.getValue();
  306.                 }
  307.                 for (final CenterName c : CenterName.values()) {
  308.                     if (c.name().equals(canonicalValue)) {
  309.                         metaData.setHasCreatableBody(true);
  310.                         final CelestialBodies celestialBodies =
  311.                                 getDataContext().getCelestialBodies();
  312.                         metaData.setCenterBody(c.getCelestialBody(celestialBodies));
  313.                         metaData.getADMFile().setMu( c.getCelestialBody(celestialBodies).getGM());
  314.                     }
  315.                 }
  316.                 return true;

  317.             case TIME_SYSTEM:
  318.                 if (!CcsdsTimeScale.contains(keyValue.getValue())) {
  319.                     throw new OrekitException(
  320.                             OrekitMessages.CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED,
  321.                             keyValue.getValue());
  322.                 }
  323.                 final CcsdsTimeScale timeSystem =
  324.                         CcsdsTimeScale.valueOf(keyValue.getValue());
  325.                 metaData.setTimeSystem(timeSystem);
  326.                 return true;

  327.             default:
  328.                 return false;
  329.         }
  330.     }

  331.     /**
  332.      * Parse a general state data key = value entry.
  333.      * @param keyValue key = value pair
  334.      * @param general instance to update with parsed entry
  335.      * @param comment previous comment lines, will be emptied if used by the keyword
  336.      * @return true if the keyword was a meta-data keyword and has been parsed
  337.      */
  338.     protected boolean parseGeneralStateDataEntry(final KeyValue keyValue,
  339.                                                  final APMFile general, final List<String> comment) {
  340.         switch (keyValue.getKeyword()) {

  341.             case EPOCH:
  342.                 general.setEpochComment(comment);
  343.                 comment.clear();
  344.                 general.setEpoch(parseDate(keyValue.getValue(), general.getMetaData().getTimeSystem()));
  345.                 return true;

  346.             case QC_DOT:
  347.                 general.setQuaternionDot(new Quaternion(keyValue.getDoubleValue(),
  348.                                                         general.getQuaternionDot().getQ1(),
  349.                                                         general.getQuaternionDot().getQ2(),
  350.                                                         general.getQuaternionDot().getQ3()));
  351.                 return true;

  352.             case Q1_DOT:
  353.                 general.setQuaternionDot(new Quaternion(general.getQuaternionDot().getQ0(),
  354.                                                         keyValue.getDoubleValue(),
  355.                                                         general.getQuaternionDot().getQ2(),
  356.                                                         general.getQuaternionDot().getQ3()));
  357.                 return true;

  358.             case Q2_DOT:
  359.                 general.setQuaternionDot(new Quaternion(general.getQuaternionDot().getQ0(),
  360.                                                         general.getQuaternionDot().getQ1(),
  361.                                                         keyValue.getDoubleValue(),
  362.                                                         general.getQuaternionDot().getQ3()));
  363.                 return true;

  364.             case Q3_DOT:
  365.                 general.setQuaternionDot(new Quaternion(general.getQuaternionDot().getQ0(),
  366.                                                         general.getQuaternionDot().getQ1(),
  367.                                                         general.getQuaternionDot().getQ2(),
  368.                                                         keyValue.getDoubleValue()));
  369.                 return true;

  370.             case EULER_FRAME_A:
  371.                 general.setEulerComment(comment);
  372.                 comment.clear();
  373.                 general.setEulerFrameAString(keyValue.getValue());
  374.                 return true;

  375.             case EULER_FRAME_B:
  376.                 general.setEulerFrameBString(keyValue.getValue());
  377.                 return true;

  378.             case EULER_DIR:
  379.                 general.setEulerDirection(keyValue.getValue());
  380.                 return true;

  381.             case EULER_ROT_SEQ:
  382.                 general.setEulerRotSeq(keyValue.getValue());
  383.                 return true;

  384.             case RATE_FRAME:
  385.                 general.setRateFrameString(keyValue.getValue());
  386.                 return true;

  387.             case X_ANGLE:
  388.                 general.setRotationAngles(new Vector3D(toRadians(keyValue),
  389.                                                        general.getRotationAngles().getY(),
  390.                                                        general.getRotationAngles().getZ()));
  391.                 return true;

  392.             case Y_ANGLE:
  393.                 general.setRotationAngles(new Vector3D(general.getRotationAngles().getX(),
  394.                                                        toRadians(keyValue),
  395.                                                        general.getRotationAngles().getZ()));
  396.                 return true;

  397.             case Z_ANGLE:
  398.                 general.setRotationAngles(new Vector3D(general.getRotationAngles().getX(),
  399.                                                        general.getRotationAngles().getY(),
  400.                                                        toRadians(keyValue)));
  401.                 return true;

  402.             case X_RATE:
  403.                 general.setRotationRates(new Vector3D(toRadians(keyValue),
  404.                                                       general.getRotationRates().getY(),
  405.                                                       general.getRotationRates().getZ()));
  406.                 return true;

  407.             case Y_RATE:
  408.                 general.setRotationRates(new Vector3D(general.getRotationRates().getX(),
  409.                                                       toRadians(keyValue),
  410.                                                       general.getRotationRates().getZ()));
  411.                 return true;

  412.             case Z_RATE:
  413.                 general.setRotationRates(new Vector3D(general.getRotationRates().getX(),
  414.                                                       general.getRotationRates().getY(),
  415.                                                       toRadians(keyValue)));
  416.                 return true;

  417.             case SPIN_FRAME_A:
  418.                 general.setSpinComment(comment);
  419.                 comment.clear();
  420.                 general.setSpinFrameAString(keyValue.getValue());
  421.                 return true;

  422.             case SPIN_FRAME_B:
  423.                 general.setSpinFrameBString(keyValue.getValue());
  424.                 return true;

  425.             case SPIN_DIR:
  426.                 general.setSpinDirection(keyValue.getValue());
  427.                 return true;

  428.             case SPIN_ALPHA:
  429.                 general.setSpinAlpha(toRadians(keyValue));
  430.                 return true;

  431.             case SPIN_DELTA:
  432.                 general.setSpinDelta(toRadians(keyValue));
  433.                 return true;

  434.             case SPIN_ANGLE:
  435.                 general.setSpinAngle(toRadians(keyValue));
  436.                 return true;

  437.             case SPIN_ANGLE_VEL:
  438.                 general.setSpinAngleVel(toRadians(keyValue));
  439.                 return true;

  440.             case NUTATION:
  441.                 general.setNutation(toRadians(keyValue));
  442.                 return true;

  443.             case NUTATION_PER:
  444.                 general.setNutationPeriod(keyValue.getDoubleValue());
  445.                 return true;

  446.             case NUTATION_PHASE:
  447.                 general.setNutationPhase(toRadians(keyValue));
  448.                 return true;

  449.             case INERTIA_REF_FRAME:
  450.                 general.setSpacecraftComment(comment);
  451.                 comment.clear();
  452.                 general.setInertiaRefFrameString(keyValue.getValue());
  453.                 return true;

  454.             case I11:
  455.                 general.setI11(keyValue.getDoubleValue());
  456.                 return true;

  457.             case I22:
  458.                 general.setI22(keyValue.getDoubleValue());
  459.                 return true;

  460.             case I33:
  461.                 general.setI33(keyValue.getDoubleValue());
  462.                 return true;

  463.             case I12:
  464.                 general.setI12(keyValue.getDoubleValue());
  465.                 return true;

  466.             case I13:
  467.                 general.setI13(keyValue.getDoubleValue());
  468.                 return true;

  469.             case I23:
  470.                 general.setI23(keyValue.getDoubleValue());
  471.                 return true;

  472.             default:
  473.                 return false;

  474.         }

  475.     }

  476.     /**
  477.      * Parse a date.
  478.      * @param date date to parse, as the value of a CCSDS key=value line
  479.      * @param timeSystem time system to use
  480.      * @return parsed date
  481.      */
  482.     protected AbsoluteDate parseDate(final String date, final CcsdsTimeScale timeSystem) {
  483.         return timeSystem.parseDate(date, conventions, missionReferenceDate,
  484.                 getDataContext().getTimeScales());
  485.     }

  486.     /**
  487.      * Convert a {@link KeyValue} in degrees to a real value in randians.
  488.      * @param keyValue key value
  489.      * @return the value in radians
  490.      */
  491.     protected double toRadians(final KeyValue keyValue) {
  492.         return FastMath.toRadians(keyValue.getDoubleValue());
  493.     }

  494. }