AEMParser.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.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.ArrayList;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Scanner;
  28. import java.util.regex.Pattern;

  29. import org.hipparchus.exception.DummyLocalizable;
  30. import org.hipparchus.geometry.euclidean.threed.RotationOrder;
  31. import org.orekit.annotation.DefaultDataContext;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.files.general.AttitudeEphemerisFileParser;
  36. import org.orekit.frames.Frame;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.utils.IERSConventions;
  39. import org.orekit.utils.TimeStampedAngularCoordinates;

  40. /**
  41.  * A parser for the CCSDS AEM (Attitude Ephemeris Message).
  42.  * @author Bryan Cazabonne
  43.  * @since 10.2
  44.  */
  45. public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser {

  46.     /** Pattern for dash. */
  47.     private static final Pattern DASH = Pattern.compile("-");

  48.     /** Maximum number of elements in an attitude data line. */
  49.     private static final int MAX_SIZE = 8;

  50.     /** Default interpolation degree. */
  51.     private int interpolationDegree;

  52.     /** Local Spacecraft Body Reference Frame A. */
  53.     private Frame localScBodyReferenceFrameA;

  54.     /** Local Spacecraft Body Reference Frame B. */
  55.     private Frame localScBodyReferenceFrameB;

  56.     /**
  57.      * Simple constructor.
  58.      * <p>
  59.      * This class is immutable, and hence thread safe. When parts
  60.      * must be changed, such as reference date for Mission Elapsed Time or
  61.      * Mission Relative Time time systems, or the gravitational coefficient or
  62.      * the IERS conventions, the various {@code withXxx} methods must be called,
  63.      * which create a new immutable instance with the new parameters. This
  64.      * is a combination of the
  65.      * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
  66.      * pattern</a> and a
  67.      * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  68.      * interface</a>.
  69.      * </p>
  70.      * <p>
  71.      * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
  72.      * If such time systems are used, it must be initialized before parsing by calling {@link
  73.      * #withMissionReferenceDate(AbsoluteDate)}.
  74.      * </p>
  75.      * <p>
  76.      * The IERS conventions to use is not set here. If it is needed in order to
  77.      * parse some reference frames or UT1 time scale, it must be initialized before
  78.      * parsing by calling {@link #withConventions(IERSConventions)}.
  79.      * </p>
  80.      * <p>
  81.      * The international designator parameters (launch year, launch number and
  82.      * launch piece) are not set here. If they are needed, they must be initialized before
  83.      * parsing by calling {@link #withInternationalDesignator(int, int, String)}
  84.      * </p>
  85.      * <p>
  86.      * The default interpolation degree is not set here. It is set to one by default. If another value
  87.      * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
  88.      * </p>
  89.      *
  90.      * <p>This method uses the {@link DataContext#getDefault() default data context}. See
  91.      * {@link #withDataContext(DataContext)}.
  92.      */
  93.     @DefaultDataContext
  94.     public AEMParser() {
  95.         this(DataContext.getDefault());
  96.     }

  97.     /**
  98.      * Constructor with data context.
  99.      * <p>
  100.      * This class is immutable, and hence thread safe. When parts
  101.      * must be changed, such as reference date for Mission Elapsed Time or
  102.      * Mission Relative Time time systems, or the gravitational coefficient or
  103.      * the IERS conventions, the various {@code withXxx} methods must be called,
  104.      * which create a new immutable instance with the new parameters. This
  105.      * is a combination of the
  106.      * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
  107.      * pattern</a> and a
  108.      * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  109.      * interface</a>.
  110.      * </p>
  111.      * <p>
  112.      * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
  113.      * If such time systems are used, it must be initialized before parsing by calling {@link
  114.      * #withMissionReferenceDate(AbsoluteDate)}.
  115.      * </p>
  116.      * <p>
  117.      * The IERS conventions to use is not set here. If it is needed in order to
  118.      * parse some reference frames or UT1 time scale, it must be initialized before
  119.      * parsing by calling {@link #withConventions(IERSConventions)}.
  120.      * </p>
  121.      * <p>
  122.      * The international designator parameters (launch year, launch number and
  123.      * launch piece) are not set here. If they are needed, they must be initialized before
  124.      * parsing by calling {@link #withInternationalDesignator(int, int, String)}
  125.      * </p>
  126.      * <p>
  127.      * The default interpolation degree is not set here. It is set to one by default. If another value
  128.      * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
  129.      * </p>
  130.      *
  131.      * @param dataContext used by the parser.
  132.      * @see #AEMParser()
  133.      * @see #withDataContext(DataContext)
  134.      */
  135.     public AEMParser(final DataContext dataContext) {
  136.         this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 1, dataContext);
  137.     }

  138.     /**
  139.      * Complete constructor.
  140.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  141.      * @param mu gravitational coefficient
  142.      * @param conventions IERS Conventions
  143.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  144.      * @param launchYear launch year for TLEs
  145.      * @param launchNumber launch number for TLEs
  146.      * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
  147.      * @param interpolationDegree default interpolation degree
  148.      * @param dataContext used to retrieve frames, time scales, etc.
  149.      */
  150.     private AEMParser(final AbsoluteDate missionReferenceDate, final double mu,
  151.                       final IERSConventions conventions, final boolean simpleEOP,
  152.                       final int launchYear, final int launchNumber,
  153.                       final String launchPiece, final int interpolationDegree,
  154.                       final DataContext dataContext) {
  155.         super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
  156.                 launchPiece, dataContext);
  157.         this.interpolationDegree = interpolationDegree;
  158.     }

  159.     /** {@inheritDoc} */
  160.     public AEMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
  161.         return new AEMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
  162.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  163.                              getInterpolationDegree(), getDataContext());
  164.     }

  165.     /** {@inheritDoc} */
  166.     public AEMParser withMu(final double newMu) {
  167.         return new AEMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
  168.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  169.                              getInterpolationDegree(), getDataContext());
  170.     }

  171.     /** {@inheritDoc} */
  172.     public AEMParser withConventions(final IERSConventions newConventions) {
  173.         return new AEMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
  174.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  175.                              getInterpolationDegree(), getDataContext());
  176.     }

  177.     /** {@inheritDoc} */
  178.     public AEMParser withSimpleEOP(final boolean newSimpleEOP) {
  179.         return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
  180.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  181.                              getInterpolationDegree(), getDataContext());
  182.     }

  183.     /** {@inheritDoc} */
  184.     public AEMParser withInternationalDesignator(final int newLaunchYear,
  185.                                                  final int newLaunchNumber,
  186.                                                  final String newLaunchPiece) {
  187.         return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  188.                              newLaunchYear, newLaunchNumber, newLaunchPiece,
  189.                              getInterpolationDegree(), getDataContext());
  190.     }

  191.     /** {@inheritDoc} */
  192.     @Override
  193.     public AEMParser withDataContext(final DataContext dataContext) {
  194.         return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  195.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  196.                              getInterpolationDegree(), dataContext);
  197.     }

  198.     /** Set default interpolation degree.
  199.      * <p>
  200.      * This method may be used to set a default interpolation degree which will be used
  201.      * when no interpolation degree is parsed in the meta-data of the file. Upon instantiation
  202.      * with {@link #AEMParser(DataContext)} the default interpolation degree is one.
  203.      * </p>
  204.      * @param newInterpolationDegree default interpolation degree to use while parsing
  205.      * @return a new instance, with interpolation degree data replaced
  206.      * @see #getInterpolationDegree()
  207.      * @since 10.3
  208.      */
  209.     public AEMParser withInterpolationDegree(final int newInterpolationDegree) {
  210.         return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  211.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  212.                              newInterpolationDegree, getDataContext());
  213.     }

  214.     /**
  215.      * Set the local spacecraft body reference frame A.
  216.      * <p>
  217.      * This frame corresponds to {@link Keyword#REF_FRAME_A} key in AEM file.
  218.      * This method may be used to set a reference frame "A" which will be used
  219.      * if the frame parsed in the file does not correspond to a default frame available
  220.      * in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
  221.      * According to CCSDS ADM documentation, it is the responsibility of the end user
  222.      * to have an understanding of the location of these frames for their particular object.
  223.      * </p>
  224.      * @param frame the frame to set
  225.      */
  226.     public void setLocalScBodyReferenceFrameA(final Frame frame) {
  227.         this.localScBodyReferenceFrameA = frame;
  228.     }

  229.     /**
  230.      * Set the local spacecraft body reference frame B.
  231.      * <p>
  232.      * This frame corresponds to {@link Keyword#REF_FRAME_B} key in AEM file.
  233.      * This method may be used to set a reference frame "B" which will be used
  234.      * if the frame parsed in the file does not correspond to a default frame available
  235.      * in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
  236.      * According to CCSDS ADM documentation, it is the responsibility of the end user
  237.      * to have an understanding of the location of these frames for their particular object.
  238.      * </p>
  239.      * @param frame the frame to set
  240.      */
  241.     public void setLocalScBodyReferenceFrameB(final Frame frame) {
  242.         this.localScBodyReferenceFrameB = frame;
  243.     }

  244.     /** Get default interpolation degree.
  245.      * @return interpolationDegree default interpolation degree to use while parsing
  246.      * @see #withInterpolationDegree(int)
  247.      * @since 10.3
  248.      */
  249.     public int getInterpolationDegree() {
  250.         return interpolationDegree;
  251.     }

  252.     /** {@inheritDoc} */
  253.     @Override
  254.     public AEMFile parse(final String fileName) {
  255.         return (AEMFile) super.parse(fileName);
  256.     }

  257.     /** {@inheritDoc} */
  258.     @Override
  259.     public AEMFile parse(final InputStream stream) {
  260.         return (AEMFile) super.parse(stream);
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public AEMFile parse(final InputStream stream, final String fileName) {
  265.         try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
  266.             return parse(reader, fileName);
  267.         } catch (IOException ioe) {
  268.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  269.         }
  270.     }

  271.     /** {@inheritDoc} */
  272.     @Override
  273.     public AEMFile parse(final BufferedReader reader, final String fileName) {

  274.         try {

  275.             // initialize internal data structures
  276.             final ParseInfo pi = new ParseInfo();
  277.             pi.fileName = fileName;
  278.             final AEMFile file = pi.file;

  279.             // set the additional data that has been configured prior the parsing by the user.
  280.             pi.file.setMissionReferenceDate(getMissionReferenceDate());
  281.             pi.file.setMu(getMu());
  282.             pi.file.setConventions(getConventions());
  283.             pi.file.setDataContext(getDataContext());

  284.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  285.                 ++pi.lineNumber;
  286.                 if (line.trim().length() == 0) {
  287.                     continue;
  288.                 }
  289.                 pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
  290.                 if (pi.keyValue.getKeyword() == null) {
  291.                     throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  292.                 }
  293.                 switch (pi.keyValue.getKeyword()) {
  294.                     case CCSDS_AEM_VERS:
  295.                         file.setFormatVersion(pi.keyValue.getDoubleValue());
  296.                         break;

  297.                     case META_START:
  298.                         file.addAttitudeBlock();
  299.                         pi.lastEphemeridesBlock = file.getAttitudeBlocks().get(file.getAttitudeBlocks().size() - 1);
  300.                         pi.lastEphemeridesBlock.getMetaData().setLaunchYear(getLaunchYear());
  301.                         pi.lastEphemeridesBlock.getMetaData().setLaunchNumber(getLaunchNumber());
  302.                         pi.lastEphemeridesBlock.getMetaData().setLaunchPiece(getLaunchPiece());
  303.                         pi.lastEphemeridesBlock.setInterpolationDegree(getInterpolationDegree());
  304.                         break;

  305.                     case REF_FRAME_A:
  306.                         pi.lastEphemeridesBlock.setRefFrameAString(pi.keyValue.getValue());
  307.                         break;

  308.                     case REF_FRAME_B:
  309.                         pi.lastEphemeridesBlock.setRefFrameBString(pi.keyValue.getValue());
  310.                         break;

  311.                     case ATTITUDE_DIR:
  312.                         pi.lastEphemeridesBlock.setAttitudeDirection(pi.keyValue.getValue());
  313.                         break;

  314.                     case START_TIME:
  315.                         pi.lastEphemeridesBlock.setStartTime(parseDate(pi.keyValue.getValue(),
  316.                                                                        pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  317.                         break;

  318.                     case USEABLE_START_TIME:
  319.                         pi.lastEphemeridesBlock.setUseableStartTime(parseDate(pi.keyValue.getValue(),
  320.                                                                               pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  321.                         break;

  322.                     case USEABLE_STOP_TIME:
  323.                         pi.lastEphemeridesBlock.setUseableStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  324.                         break;

  325.                     case STOP_TIME:
  326.                         pi.lastEphemeridesBlock.setStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  327.                         break;

  328.                     case ATTITUDE_TYPE:
  329.                         pi.lastEphemeridesBlock.setAttitudeType(pi.keyValue.getValue());
  330.                         break;

  331.                     case QUATERNION_TYPE:
  332.                         final boolean isFirst = (pi.keyValue.getValue().equals("FIRST")) ? true : false;
  333.                         pi.lastEphemeridesBlock.setIsFirst(isFirst);
  334.                         break;

  335.                     case EULER_ROT_SEQ:
  336.                         pi.lastEphemeridesBlock.setEulerRotSeq(pi.keyValue.getValue());
  337.                         pi.lastEphemeridesBlock.setRotationOrder(AEMRotationOrder.getRotationOrder(pi.keyValue.getValue()));
  338.                         break;

  339.                     case RATE_FRAME:
  340.                         pi.lastEphemeridesBlock.setRateFrameString(pi.keyValue.getValue());
  341.                         break;

  342.                     case INTERPOLATION_METHOD:
  343.                         pi.lastEphemeridesBlock.setInterpolationMethod(pi.keyValue.getValue());
  344.                         break;

  345.                     case INTERPOLATION_DEGREE:
  346.                         pi.lastEphemeridesBlock.setInterpolationDegree(Integer.parseInt(pi.keyValue.getValue()));
  347.                         break;

  348.                     case META_STOP:
  349.                         // Set attitude reference frame
  350.                         parseReferenceFrame(pi);
  351.                         // Read attitude ephemeris data lines
  352.                         parseEphemeridesDataLines(reader, pi);
  353.                         break;

  354.                     default:
  355.                         boolean parsed = false;
  356.                         parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
  357.                         parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
  358.                         if (pi.lastEphemeridesBlock != null) {
  359.                             parsed = parsed || parseMetaDataEntry(pi.keyValue,
  360.                                                                   pi.lastEphemeridesBlock.getMetaData(), pi.commentTmp);
  361.                         }
  362.                         if (!parsed) {
  363.                             throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  364.                         }
  365.                 }

  366.             }

  367.             file.checkTimeSystems();
  368.             return file;

  369.         } catch (IOException ioe) {
  370.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  371.         }

  372.     }

  373.     /**
  374.      * Parse an attitude ephemeris data line and add its content
  375.      * to the attitude ephemerides block.
  376.      * @param reader the reader
  377.      * @param pi the parser info
  378.      * @exception IOException if an error occurs while reading from the stream
  379.      */
  380.     private void parseEphemeridesDataLines(final BufferedReader reader,  final ParseInfo pi)
  381.         throws IOException {

  382.         for (String line = reader.readLine(); line != null; line = reader.readLine()) {

  383.             ++pi.lineNumber;
  384.             if (line.trim().length() > 0) {
  385.                 pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
  386.                 if (pi.keyValue.getKeyword() == null) {
  387.                     try (Scanner sc = new Scanner(line)) {
  388.                         final AbsoluteDate date = parseDate(sc.next(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
  389.                         // Create an array with the maximum possible size
  390.                         final double[] attitudeData = new double[MAX_SIZE];
  391.                         int index = 0;
  392.                         while (sc.hasNext()) {
  393.                             attitudeData[index++] = Double.parseDouble(sc.next());
  394.                         }
  395.                         final AEMAttitudeType attType = AEMAttitudeType.getAttitudeType(pi.lastEphemeridesBlock.getAttitudeType());
  396.                         final RotationOrder rotationOrder = pi.lastEphemeridesBlock.getRotationOrder();

  397.                         final TimeStampedAngularCoordinates epDataLine = attType.getAngularCoordinates(date, attitudeData,
  398.                                                                                                        pi.lastEphemeridesBlock.isFirst(),
  399.                                                                                                        rotationOrder);
  400.                         pi.lastEphemeridesBlock.getAttitudeDataLines().add(epDataLine);
  401.                         pi.lastEphemeridesBlock.updateAngularDerivativesFilter(attType.getAngularDerivativesFilter());
  402.                     } catch (NumberFormatException nfe) {
  403.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  404.                                                   pi.lineNumber, pi.fileName, line);
  405.                     }
  406.                 } else {
  407.                     switch (pi.keyValue.getKeyword()) {

  408.                         case DATA_START:
  409.                             // Do nothing
  410.                             break;

  411.                         case DATA_STOP:
  412.                             pi.lastEphemeridesBlock.setAttitudeDataLinesComment(pi.commentTmp);
  413.                             pi.commentTmp.clear();
  414.                             //pi.lineNumber--;
  415.                             reader.reset();
  416.                             reader.readLine();
  417.                             return;

  418.                         case COMMENT:
  419.                             pi.commentTmp.add(pi.keyValue.getValue());
  420.                             break;

  421.                         default :
  422.                             throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  423.                     }
  424.                 }
  425.             }
  426.             reader.mark(300);

  427.         }
  428.     }

  429.     /**
  430.      * Parse the reference attitude frame.
  431.      * @param pi the parser info
  432.      */
  433.     private void parseReferenceFrame(final ParseInfo pi) {

  434.         // Reference frame A
  435.         final String frameAString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameAString())).replaceAll("");
  436.         final Frame frameA = isDefinedFrame(frameAString) ?
  437.                                     CCSDSFrame.valueOf(frameAString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
  438.                                         localScBodyReferenceFrameA;

  439.         // Reference frame B
  440.         final String frameBString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameBString())).replaceAll("");
  441.         final Frame frameB = isDefinedFrame(frameBString) ?
  442.                                     CCSDSFrame.valueOf(frameBString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
  443.                                         localScBodyReferenceFrameB;

  444.         // Set the attitude reference frame
  445.         final String direction = pi.lastEphemeridesBlock.getAttitudeDirection();
  446.         pi.lastEphemeridesBlock.setReferenceFrame("A2B".equals(direction) ? frameA : frameB);

  447.     }

  448.     /**
  449.      * Check if frame name is "J2000".
  450.      * <p>
  451.      * If yes, the name is changed to "EME2000" in order to match
  452.      * predefined CCSDS frame names.
  453.      * </p>
  454.      * @param frameName frame name
  455.      * @return the nex name
  456.      */
  457.     private static String j2000Check(final String frameName) {
  458.         return "J2000".equals(frameName) ? "EME2000" : frameName;
  459.     }

  460.     /**
  461.      * Verify if the given frame is defined in predefined CCSDS frames.
  462.      * @param frameName frame name
  463.      * @return true is the frame is known
  464.      */
  465.     private static boolean isDefinedFrame(final String frameName) {
  466.         // Loop on CCSDS frames
  467.         for (CCSDSFrame ccsdsFrame : CCSDSFrame.values()) {
  468.             // CCSDS frame name is defined in enumerate
  469.             if (ccsdsFrame.name().equals(frameName)) {
  470.                 return true;
  471.             }
  472.         }
  473.         // No match found
  474.         return false;
  475.     }

  476.     /** Private class used to stock AEM parsing info. */
  477.     private static class ParseInfo {

  478.         /** Ephemerides block being parsed. */
  479.         private AEMFile.AttitudeEphemeridesBlock lastEphemeridesBlock;

  480.         /** Name of the file. */
  481.         private String fileName;

  482.         /** Current line number. */
  483.         private int lineNumber;

  484.         /** AEM file being read. */
  485.         private AEMFile file;

  486.         /** Key value of the line being read. */
  487.         private KeyValue keyValue;

  488.         /** Stored comments. */
  489.         private List<String> commentTmp;

  490.         /** Create a new {@link ParseInfo} object. */
  491.         protected ParseInfo() {
  492.             lineNumber = 0;
  493.             file       = new AEMFile();
  494.             commentTmp = new ArrayList<String>();
  495.         }
  496.     }

  497.     /** Util class to convert the Euler rotation sequence to {@link RotationOrder}. */
  498.     public enum AEMRotationOrder {

  499.         /** This ordered set of rotations is around X, then around Y, then around Z. */
  500.         XYZ("123", RotationOrder.XYZ),

  501.         /** This ordered set of rotations is around X, then around Z, then around Y. */
  502.         XZY("132", RotationOrder.XZY),

  503.         /** This ordered set of rotations is around Y, then around X, then around Z. */
  504.         YXZ("213", RotationOrder.YXZ),

  505.         /** This ordered set of rotations is around Y, then around Z, then around X. */
  506.         YZX("231", RotationOrder.YZX),

  507.         /** This ordered set of rotations is around Z, then around X, then around Y. */
  508.         ZXY("312", RotationOrder.ZXY),

  509.         /** This ordered set of rotations is around Z, then around Y, then around X. */
  510.         ZYX("321", RotationOrder.ZYX),

  511.         /** This ordered set of rotations is around X, then around Y, then around X. */
  512.         XYX("121", RotationOrder.XYX),

  513.         /** This ordered set of rotations is around X, then around Z, then around X. */
  514.         XZX("131", RotationOrder.XZX),

  515.         /** This ordered set of rotations is around Y, then around X, then around Y. */
  516.         YXY("212", RotationOrder.YXY),

  517.         /** This ordered set of rotations is around Y, then around Z, then around Y. */
  518.         YZY("232", RotationOrder.YZY),

  519.         /** This ordered set of rotations is around Z, then around X, then around Z. */
  520.         ZXZ("313", RotationOrder.ZXZ),

  521.         /** This ordered set of rotations is around Z, then around Y, then around Z. */
  522.         ZYZ("323", RotationOrder.ZYZ);

  523.         /** Codes map. */
  524.         private static final Map<String, RotationOrder> CODES_MAP = new HashMap<String, RotationOrder>();
  525.         static {
  526.             for (final AEMRotationOrder type : values()) {
  527.                 CODES_MAP.put(type.getName(), type.getRotationOrder());
  528.             }
  529.         }

  530.         /** Rotation order. */
  531.         private final RotationOrder order;

  532.         /** Name. */
  533.         private final String name;

  534.         /**
  535.          * Constructor.
  536.          * @param name name of the rotation
  537.          * @param order rotation order
  538.          */
  539.         AEMRotationOrder(final String name,
  540.                          final RotationOrder order) {
  541.             this.name  = name;
  542.             this.order = order;
  543.         }

  544.         /**
  545.          * Get the name of the AEM rotation order.
  546.          * @return name
  547.          */
  548.         private String getName() {
  549.             return name;
  550.         }

  551.         /**
  552.          * Get the rotation order.
  553.          * @return rotation order
  554.          */
  555.         private RotationOrder getRotationOrder() {
  556.             return order;
  557.         }

  558.         /**
  559.          * Get the rotation order for the given name.
  560.          * @param orderName name of the rotation order (e.g. "123")
  561.          * @return the corresponding rotation order
  562.          */
  563.         public static RotationOrder getRotationOrder(final String orderName) {
  564.             final RotationOrder type = CODES_MAP.get(orderName);
  565.             if (type == null) {
  566.                 // Invalid rotation sequence
  567.                 throw new OrekitException(OrekitMessages.CCSDS_AEM_INVALID_ROTATION_SEQUENCE, orderName);
  568.             }
  569.             return type;
  570.         }

  571.     }

  572. }