AEMParser.java
- /* Copyright 2002-2020 CS GROUP
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * CS licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.orekit.files.ccsds;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.nio.charset.StandardCharsets;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Scanner;
- import java.util.regex.Pattern;
- import org.hipparchus.exception.DummyLocalizable;
- import org.hipparchus.geometry.euclidean.threed.RotationOrder;
- import org.orekit.annotation.DefaultDataContext;
- import org.orekit.data.DataContext;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.files.general.AttitudeEphemerisFileParser;
- import org.orekit.frames.Frame;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.utils.IERSConventions;
- import org.orekit.utils.TimeStampedAngularCoordinates;
- /**
- * A parser for the CCSDS AEM (Attitude Ephemeris Message).
- * @author Bryan Cazabonne
- * @since 10.2
- */
- public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser {
- /** Pattern for dash. */
- private static final Pattern DASH = Pattern.compile("-");
- /** Maximum number of elements in an attitude data line. */
- private static final int MAX_SIZE = 8;
- /** Default interpolation degree. */
- private int interpolationDegree;
- /** Local Spacecraft Body Reference Frame A. */
- private Frame localScBodyReferenceFrameA;
- /** Local Spacecraft Body Reference Frame B. */
- private Frame localScBodyReferenceFrameB;
- /**
- * Simple constructor.
- * <p>
- * This class is immutable, and hence thread safe. When parts
- * must be changed, such as reference date for Mission Elapsed Time or
- * Mission Relative Time time systems, or the gravitational coefficient or
- * the IERS conventions, the various {@code withXxx} methods must be called,
- * which create a new immutable instance with the new parameters. This
- * is a combination of the
- * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
- * pattern</a> and a
- * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
- * interface</a>.
- * </p>
- * <p>
- * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
- * If such time systems are used, it must be initialized before parsing by calling {@link
- * #withMissionReferenceDate(AbsoluteDate)}.
- * </p>
- * <p>
- * The IERS conventions to use is not set here. If it is needed in order to
- * parse some reference frames or UT1 time scale, it must be initialized before
- * parsing by calling {@link #withConventions(IERSConventions)}.
- * </p>
- * <p>
- * The international designator parameters (launch year, launch number and
- * launch piece) are not set here. If they are needed, they must be initialized before
- * parsing by calling {@link #withInternationalDesignator(int, int, String)}
- * </p>
- * <p>
- * The default interpolation degree is not set here. It is set to one by default. If another value
- * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
- * </p>
- *
- * <p>This method uses the {@link DataContext#getDefault() default data context}. See
- * {@link #withDataContext(DataContext)}.
- */
- @DefaultDataContext
- public AEMParser() {
- this(DataContext.getDefault());
- }
- /**
- * Constructor with data context.
- * <p>
- * This class is immutable, and hence thread safe. When parts
- * must be changed, such as reference date for Mission Elapsed Time or
- * Mission Relative Time time systems, or the gravitational coefficient or
- * the IERS conventions, the various {@code withXxx} methods must be called,
- * which create a new immutable instance with the new parameters. This
- * is a combination of the
- * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
- * pattern</a> and a
- * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
- * interface</a>.
- * </p>
- * <p>
- * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
- * If such time systems are used, it must be initialized before parsing by calling {@link
- * #withMissionReferenceDate(AbsoluteDate)}.
- * </p>
- * <p>
- * The IERS conventions to use is not set here. If it is needed in order to
- * parse some reference frames or UT1 time scale, it must be initialized before
- * parsing by calling {@link #withConventions(IERSConventions)}.
- * </p>
- * <p>
- * The international designator parameters (launch year, launch number and
- * launch piece) are not set here. If they are needed, they must be initialized before
- * parsing by calling {@link #withInternationalDesignator(int, int, String)}
- * </p>
- * <p>
- * The default interpolation degree is not set here. It is set to one by default. If another value
- * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
- * </p>
- *
- * @param dataContext used by the parser.
- * @see #AEMParser()
- * @see #withDataContext(DataContext)
- */
- public AEMParser(final DataContext dataContext) {
- this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 1, dataContext);
- }
- /**
- * Complete constructor.
- * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
- * @param mu gravitational coefficient
- * @param conventions IERS Conventions
- * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
- * @param launchYear launch year for TLEs
- * @param launchNumber launch number for TLEs
- * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
- * @param interpolationDegree default interpolation degree
- * @param dataContext used to retrieve frames, time scales, etc.
- */
- private AEMParser(final AbsoluteDate missionReferenceDate, final double mu,
- final IERSConventions conventions, final boolean simpleEOP,
- final int launchYear, final int launchNumber,
- final String launchPiece, final int interpolationDegree,
- final DataContext dataContext) {
- super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
- launchPiece, dataContext);
- this.interpolationDegree = interpolationDegree;
- }
- /** {@inheritDoc} */
- public AEMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
- return new AEMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- getInterpolationDegree(), getDataContext());
- }
- /** {@inheritDoc} */
- public AEMParser withMu(final double newMu) {
- return new AEMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- getInterpolationDegree(), getDataContext());
- }
- /** {@inheritDoc} */
- public AEMParser withConventions(final IERSConventions newConventions) {
- return new AEMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- getInterpolationDegree(), getDataContext());
- }
- /** {@inheritDoc} */
- public AEMParser withSimpleEOP(final boolean newSimpleEOP) {
- return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- getInterpolationDegree(), getDataContext());
- }
- /** {@inheritDoc} */
- public AEMParser withInternationalDesignator(final int newLaunchYear,
- final int newLaunchNumber,
- final String newLaunchPiece) {
- return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
- newLaunchYear, newLaunchNumber, newLaunchPiece,
- getInterpolationDegree(), getDataContext());
- }
- /** {@inheritDoc} */
- @Override
- public AEMParser withDataContext(final DataContext dataContext) {
- return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- getInterpolationDegree(), dataContext);
- }
- /** Set default interpolation degree.
- * <p>
- * This method may be used to set a default interpolation degree which will be used
- * when no interpolation degree is parsed in the meta-data of the file. Upon instantiation
- * with {@link #AEMParser(DataContext)} the default interpolation degree is one.
- * </p>
- * @param newInterpolationDegree default interpolation degree to use while parsing
- * @return a new instance, with interpolation degree data replaced
- * @see #getInterpolationDegree()
- * @since 10.3
- */
- public AEMParser withInterpolationDegree(final int newInterpolationDegree) {
- return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
- getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
- newInterpolationDegree, getDataContext());
- }
- /**
- * Set the local spacecraft body reference frame A.
- * <p>
- * This frame corresponds to {@link Keyword#REF_FRAME_A} key in AEM file.
- * This method may be used to set a reference frame "A" which will be used
- * if the frame parsed in the file does not correspond to a default frame available
- * in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
- * According to CCSDS ADM documentation, it is the responsibility of the end user
- * to have an understanding of the location of these frames for their particular object.
- * </p>
- * @param frame the frame to set
- */
- public void setLocalScBodyReferenceFrameA(final Frame frame) {
- this.localScBodyReferenceFrameA = frame;
- }
- /**
- * Set the local spacecraft body reference frame B.
- * <p>
- * This frame corresponds to {@link Keyword#REF_FRAME_B} key in AEM file.
- * This method may be used to set a reference frame "B" which will be used
- * if the frame parsed in the file does not correspond to a default frame available
- * in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
- * According to CCSDS ADM documentation, it is the responsibility of the end user
- * to have an understanding of the location of these frames for their particular object.
- * </p>
- * @param frame the frame to set
- */
- public void setLocalScBodyReferenceFrameB(final Frame frame) {
- this.localScBodyReferenceFrameB = frame;
- }
- /** Get default interpolation degree.
- * @return interpolationDegree default interpolation degree to use while parsing
- * @see #withInterpolationDegree(int)
- * @since 10.3
- */
- public int getInterpolationDegree() {
- return interpolationDegree;
- }
- /** {@inheritDoc} */
- @Override
- public AEMFile parse(final String fileName) {
- return (AEMFile) super.parse(fileName);
- }
- /** {@inheritDoc} */
- @Override
- public AEMFile parse(final InputStream stream) {
- return (AEMFile) super.parse(stream);
- }
- /** {@inheritDoc} */
- @Override
- public AEMFile parse(final InputStream stream, final String fileName) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
- return parse(reader, fileName);
- } catch (IOException ioe) {
- throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
- }
- }
- /** {@inheritDoc} */
- @Override
- public AEMFile parse(final BufferedReader reader, final String fileName) {
- try {
- // initialize internal data structures
- final ParseInfo pi = new ParseInfo();
- pi.fileName = fileName;
- final AEMFile file = pi.file;
- // set the additional data that has been configured prior the parsing by the user.
- pi.file.setMissionReferenceDate(getMissionReferenceDate());
- pi.file.setMu(getMu());
- pi.file.setConventions(getConventions());
- pi.file.setDataContext(getDataContext());
- for (String line = reader.readLine(); line != null; line = reader.readLine()) {
- ++pi.lineNumber;
- if (line.trim().length() == 0) {
- continue;
- }
- pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
- if (pi.keyValue.getKeyword() == null) {
- throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
- }
- switch (pi.keyValue.getKeyword()) {
- case CCSDS_AEM_VERS:
- file.setFormatVersion(pi.keyValue.getDoubleValue());
- break;
- case META_START:
- file.addAttitudeBlock();
- pi.lastEphemeridesBlock = file.getAttitudeBlocks().get(file.getAttitudeBlocks().size() - 1);
- pi.lastEphemeridesBlock.getMetaData().setLaunchYear(getLaunchYear());
- pi.lastEphemeridesBlock.getMetaData().setLaunchNumber(getLaunchNumber());
- pi.lastEphemeridesBlock.getMetaData().setLaunchPiece(getLaunchPiece());
- pi.lastEphemeridesBlock.setInterpolationDegree(getInterpolationDegree());
- break;
- case REF_FRAME_A:
- pi.lastEphemeridesBlock.setRefFrameAString(pi.keyValue.getValue());
- break;
- case REF_FRAME_B:
- pi.lastEphemeridesBlock.setRefFrameBString(pi.keyValue.getValue());
- break;
- case ATTITUDE_DIR:
- pi.lastEphemeridesBlock.setAttitudeDirection(pi.keyValue.getValue());
- break;
- case START_TIME:
- pi.lastEphemeridesBlock.setStartTime(parseDate(pi.keyValue.getValue(),
- pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
- break;
- case USEABLE_START_TIME:
- pi.lastEphemeridesBlock.setUseableStartTime(parseDate(pi.keyValue.getValue(),
- pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
- break;
- case USEABLE_STOP_TIME:
- pi.lastEphemeridesBlock.setUseableStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
- break;
- case STOP_TIME:
- pi.lastEphemeridesBlock.setStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
- break;
- case ATTITUDE_TYPE:
- pi.lastEphemeridesBlock.setAttitudeType(pi.keyValue.getValue());
- break;
- case QUATERNION_TYPE:
- final boolean isFirst = (pi.keyValue.getValue().equals("FIRST")) ? true : false;
- pi.lastEphemeridesBlock.setIsFirst(isFirst);
- break;
- case EULER_ROT_SEQ:
- pi.lastEphemeridesBlock.setEulerRotSeq(pi.keyValue.getValue());
- pi.lastEphemeridesBlock.setRotationOrder(AEMRotationOrder.getRotationOrder(pi.keyValue.getValue()));
- break;
- case RATE_FRAME:
- pi.lastEphemeridesBlock.setRateFrameString(pi.keyValue.getValue());
- break;
- case INTERPOLATION_METHOD:
- pi.lastEphemeridesBlock.setInterpolationMethod(pi.keyValue.getValue());
- break;
- case INTERPOLATION_DEGREE:
- pi.lastEphemeridesBlock.setInterpolationDegree(Integer.parseInt(pi.keyValue.getValue()));
- break;
- case META_STOP:
- // Set attitude reference frame
- parseReferenceFrame(pi);
- // Read attitude ephemeris data lines
- parseEphemeridesDataLines(reader, pi);
- break;
- default:
- boolean parsed = false;
- parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
- parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
- if (pi.lastEphemeridesBlock != null) {
- parsed = parsed || parseMetaDataEntry(pi.keyValue,
- pi.lastEphemeridesBlock.getMetaData(), pi.commentTmp);
- }
- if (!parsed) {
- throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
- }
- }
- }
- file.checkTimeSystems();
- return file;
- } catch (IOException ioe) {
- throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
- }
- }
- /**
- * Parse an attitude ephemeris data line and add its content
- * to the attitude ephemerides block.
- * @param reader the reader
- * @param pi the parser info
- * @exception IOException if an error occurs while reading from the stream
- */
- private void parseEphemeridesDataLines(final BufferedReader reader, final ParseInfo pi)
- throws IOException {
- for (String line = reader.readLine(); line != null; line = reader.readLine()) {
- ++pi.lineNumber;
- if (line.trim().length() > 0) {
- pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
- if (pi.keyValue.getKeyword() == null) {
- try (Scanner sc = new Scanner(line)) {
- final AbsoluteDate date = parseDate(sc.next(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
- // Create an array with the maximum possible size
- final double[] attitudeData = new double[MAX_SIZE];
- int index = 0;
- while (sc.hasNext()) {
- attitudeData[index++] = Double.parseDouble(sc.next());
- }
- final AEMAttitudeType attType = AEMAttitudeType.getAttitudeType(pi.lastEphemeridesBlock.getAttitudeType());
- final RotationOrder rotationOrder = pi.lastEphemeridesBlock.getRotationOrder();
- final TimeStampedAngularCoordinates epDataLine = attType.getAngularCoordinates(date, attitudeData,
- pi.lastEphemeridesBlock.isFirst(),
- rotationOrder);
- pi.lastEphemeridesBlock.getAttitudeDataLines().add(epDataLine);
- pi.lastEphemeridesBlock.updateAngularDerivativesFilter(attType.getAngularDerivativesFilter());
- } catch (NumberFormatException nfe) {
- throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- pi.lineNumber, pi.fileName, line);
- }
- } else {
- switch (pi.keyValue.getKeyword()) {
- case DATA_START:
- // Do nothing
- break;
- case DATA_STOP:
- pi.lastEphemeridesBlock.setAttitudeDataLinesComment(pi.commentTmp);
- pi.commentTmp.clear();
- //pi.lineNumber--;
- reader.reset();
- reader.readLine();
- return;
- case COMMENT:
- pi.commentTmp.add(pi.keyValue.getValue());
- break;
- default :
- throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
- }
- }
- }
- reader.mark(300);
- }
- }
- /**
- * Parse the reference attitude frame.
- * @param pi the parser info
- */
- private void parseReferenceFrame(final ParseInfo pi) {
- // Reference frame A
- final String frameAString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameAString())).replaceAll("");
- final Frame frameA = isDefinedFrame(frameAString) ?
- CCSDSFrame.valueOf(frameAString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
- localScBodyReferenceFrameA;
- // Reference frame B
- final String frameBString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameBString())).replaceAll("");
- final Frame frameB = isDefinedFrame(frameBString) ?
- CCSDSFrame.valueOf(frameBString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
- localScBodyReferenceFrameB;
- // Set the attitude reference frame
- final String direction = pi.lastEphemeridesBlock.getAttitudeDirection();
- pi.lastEphemeridesBlock.setReferenceFrame("A2B".equals(direction) ? frameA : frameB);
- }
- /**
- * Check if frame name is "J2000".
- * <p>
- * If yes, the name is changed to "EME2000" in order to match
- * predefined CCSDS frame names.
- * </p>
- * @param frameName frame name
- * @return the nex name
- */
- private static String j2000Check(final String frameName) {
- return "J2000".equals(frameName) ? "EME2000" : frameName;
- }
- /**
- * Verify if the given frame is defined in predefined CCSDS frames.
- * @param frameName frame name
- * @return true is the frame is known
- */
- private static boolean isDefinedFrame(final String frameName) {
- // Loop on CCSDS frames
- for (CCSDSFrame ccsdsFrame : CCSDSFrame.values()) {
- // CCSDS frame name is defined in enumerate
- if (ccsdsFrame.name().equals(frameName)) {
- return true;
- }
- }
- // No match found
- return false;
- }
- /** Private class used to stock AEM parsing info. */
- private static class ParseInfo {
- /** Ephemerides block being parsed. */
- private AEMFile.AttitudeEphemeridesBlock lastEphemeridesBlock;
- /** Name of the file. */
- private String fileName;
- /** Current line number. */
- private int lineNumber;
- /** AEM file being read. */
- private AEMFile file;
- /** Key value of the line being read. */
- private KeyValue keyValue;
- /** Stored comments. */
- private List<String> commentTmp;
- /** Create a new {@link ParseInfo} object. */
- protected ParseInfo() {
- lineNumber = 0;
- file = new AEMFile();
- commentTmp = new ArrayList<String>();
- }
- }
- /** Util class to convert the Euler rotation sequence to {@link RotationOrder}. */
- public enum AEMRotationOrder {
- /** This ordered set of rotations is around X, then around Y, then around Z. */
- XYZ("123", RotationOrder.XYZ),
- /** This ordered set of rotations is around X, then around Z, then around Y. */
- XZY("132", RotationOrder.XZY),
- /** This ordered set of rotations is around Y, then around X, then around Z. */
- YXZ("213", RotationOrder.YXZ),
- /** This ordered set of rotations is around Y, then around Z, then around X. */
- YZX("231", RotationOrder.YZX),
- /** This ordered set of rotations is around Z, then around X, then around Y. */
- ZXY("312", RotationOrder.ZXY),
- /** This ordered set of rotations is around Z, then around Y, then around X. */
- ZYX("321", RotationOrder.ZYX),
- /** This ordered set of rotations is around X, then around Y, then around X. */
- XYX("121", RotationOrder.XYX),
- /** This ordered set of rotations is around X, then around Z, then around X. */
- XZX("131", RotationOrder.XZX),
- /** This ordered set of rotations is around Y, then around X, then around Y. */
- YXY("212", RotationOrder.YXY),
- /** This ordered set of rotations is around Y, then around Z, then around Y. */
- YZY("232", RotationOrder.YZY),
- /** This ordered set of rotations is around Z, then around X, then around Z. */
- ZXZ("313", RotationOrder.ZXZ),
- /** This ordered set of rotations is around Z, then around Y, then around Z. */
- ZYZ("323", RotationOrder.ZYZ);
- /** Codes map. */
- private static final Map<String, RotationOrder> CODES_MAP = new HashMap<String, RotationOrder>();
- static {
- for (final AEMRotationOrder type : values()) {
- CODES_MAP.put(type.getName(), type.getRotationOrder());
- }
- }
- /** Rotation order. */
- private final RotationOrder order;
- /** Name. */
- private final String name;
- /**
- * Constructor.
- * @param name name of the rotation
- * @param order rotation order
- */
- AEMRotationOrder(final String name,
- final RotationOrder order) {
- this.name = name;
- this.order = order;
- }
- /**
- * Get the name of the AEM rotation order.
- * @return name
- */
- private String getName() {
- return name;
- }
- /**
- * Get the rotation order.
- * @return rotation order
- */
- private RotationOrder getRotationOrder() {
- return order;
- }
- /**
- * Get the rotation order for the given name.
- * @param orderName name of the rotation order (e.g. "123")
- * @return the corresponding rotation order
- */
- public static RotationOrder getRotationOrder(final String orderName) {
- final RotationOrder type = CODES_MAP.get(orderName);
- if (type == null) {
- // Invalid rotation sequence
- throw new OrekitException(OrekitMessages.CCSDS_AEM_INVALID_ROTATION_SEQUENCE, orderName);
- }
- return type;
- }
- }
- }