ApmParser.java
- /* Copyright 2002-2024 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.ndm.adm.apm;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.function.Function;
- import org.orekit.data.DataContext;
- import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
- import org.orekit.files.ccsds.ndm.adm.AdmCommonMetadataKey;
- import org.orekit.files.ccsds.ndm.adm.AdmHeader;
- import org.orekit.files.ccsds.ndm.adm.AdmMetadata;
- import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
- import org.orekit.files.ccsds.ndm.adm.AdmParser;
- import org.orekit.files.ccsds.section.CommentsContainer;
- import org.orekit.files.ccsds.section.HeaderProcessingState;
- import org.orekit.files.ccsds.section.MetadataKey;
- import org.orekit.files.ccsds.section.Segment;
- import org.orekit.files.ccsds.section.XmlStructureProcessingState;
- import org.orekit.files.ccsds.utils.ContextBinding;
- import org.orekit.files.ccsds.utils.FileFormat;
- import org.orekit.files.ccsds.utils.lexical.ParseToken;
- import org.orekit.files.ccsds.utils.lexical.TokenType;
- import org.orekit.files.ccsds.utils.parsing.ErrorState;
- import org.orekit.files.ccsds.utils.parsing.ProcessingState;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.utils.IERSConventions;
- /**
- * A parser for the CCSDS APM (Attitude Parameter Message).
- * @author Bryan Cazabonne * <p>
- * Note than starting with Orekit 11.0, CCSDS message parsers are
- * mutable objects that gather the data being parsed, until the
- * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
- * parseMessage} method has returned. This implies that parsers
- * should <em>not</em> be used in a multi-thread context. The recommended
- * way to use parsers is to either dedicate one parser for each message
- * and drop it afterwards, or to use a single-thread loop.
- * </p>
- * @since 10.2
- */
- public class ApmParser extends AdmParser<Apm, ApmParser> {
- /** File header. */
- private AdmHeader header;
- /** File segments. */
- private List<Segment<AdmMetadata, ApmData>> segments;
- /** APM metadata being read. */
- private AdmMetadata metadata;
- /** Context binding valid for current metadata. */
- private ContextBinding context;
- /** APM epoch.
- * @since 12.0
- */
- private AbsoluteDate epoch;
- /** APM general comments block being read. */
- private CommentsContainer commentsBlock;
- /** APM quaternion logical block being read. */
- private ApmQuaternion quaternionBlock;
- /** APM Euler angles logical block being read. */
- private Euler eulerBlock;
- /** APM angular velocity logical block being read.
- * @since 12.0
- */
- private AngularVelocity angularVelocityBlock;
- /** APM spin-stabilized logical block being read. */
- private SpinStabilized spinStabilizedBlock;
- /** APM inertia block being read.
- * @since 12.0
- */
- private Inertia inertiaBlock;
- /** Current maneuver. */
- private Maneuver currentManeuver;
- /** All maneuvers. */
- private List<Maneuver> maneuvers;
- /** Processor for global message structure. */
- private ProcessingState structureProcessor;
- /** Complete constructor.
- * <p>
- * Calling this constructor directly is not recommended. Users should rather use
- * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildApmParser()
- * parserBuilder.buildApmParser()}.
- * </p>
- * @param conventions IERS Conventions
- * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
- * @param dataContext used to retrieve frames, time scales, etc.
- * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
- * (may be null if time system is absolute)
- * @param parsedUnitsBehavior behavior to adopt for handling parsed units
- * @param filters filters to apply to parse tokens
- * @since 12.0
- */
- public ApmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
- final AbsoluteDate missionReferenceDate, final ParsedUnitsBehavior parsedUnitsBehavior,
- final Function<ParseToken, List<ParseToken>>[] filters) {
- super(Apm.ROOT, Apm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
- missionReferenceDate, parsedUnitsBehavior, filters);
- }
- /** {@inheritDoc} */
- @Override
- public AdmHeader getHeader() {
- return header;
- }
- /** {@inheritDoc} */
- @Override
- public void reset(final FileFormat fileFormat) {
- header = new AdmHeader();
- segments = new ArrayList<>();
- metadata = null;
- context = null;
- quaternionBlock = null;
- eulerBlock = null;
- spinStabilizedBlock = null;
- inertiaBlock = null;
- currentManeuver = null;
- maneuvers = new ArrayList<>();
- if (fileFormat == FileFormat.XML) {
- structureProcessor = new XmlStructureProcessingState(Apm.ROOT, this);
- reset(fileFormat, structureProcessor);
- } else {
- structureProcessor = new ErrorState(); // should never be called
- reset(fileFormat, new HeaderProcessingState(this));
- }
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareHeader() {
- anticipateNext(new HeaderProcessingState(this));
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean inHeader() {
- anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeHeader() {
- header.validate(header.getFormatVersion());
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareMetadata() {
- if (metadata != null) {
- return false;
- }
- metadata = new AdmMetadata();
- context = new ContextBinding(this::getConventions, this::isSimpleEOP,
- this::getDataContext, this::getParsedUnitsBehavior,
- this::getMissionReferenceDate,
- metadata::getTimeSystem, () -> 0.0, () -> 1.0);
- anticipateNext(this::processMetadataToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean inMetadata() {
- anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processDataToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeMetadata() {
- metadata.validate(header.getFormatVersion());
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareData() {
- commentsBlock = new CommentsContainer();
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processDataToken : this::processDataSubStructureToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean inData() {
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeData() {
- if (metadata != null) {
- final ApmData data = new ApmData(commentsBlock, epoch, quaternionBlock, eulerBlock,
- angularVelocityBlock, spinStabilizedBlock, inertiaBlock);
- if (currentManeuver != null) {
- // current maneuver is completed
- maneuvers.add(currentManeuver);
- currentManeuver = null;
- }
- for (final Maneuver maneuver : maneuvers) {
- data.addManeuver(maneuver);
- }
- data.validate(header.getFormatVersion());
- segments.add(new Segment<>(metadata, data));
- }
- metadata = null;
- context = null;
- quaternionBlock = null;
- eulerBlock = null;
- angularVelocityBlock = null;
- spinStabilizedBlock = null;
- inertiaBlock = null;
- currentManeuver = null;
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public Apm build() {
- // APM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
- // automatically before the end of the file
- finalizeData();
- final Apm file = new Apm(header, segments, getConventions(), getDataContext());
- return file;
- }
- /** Add a general comment.
- * @param comment comment to add
- * @return always return true
- */
- boolean addGeneralComment(final String comment) {
- return commentsBlock.addComment(comment);
- }
- /** Set current epoch.
- * @param epoch epoch to set
- * @since 12.0
- */
- void setEpoch(final AbsoluteDate epoch) {
- this.epoch = epoch;
- }
- /** Manage quaternion section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageQuaternionSection(final boolean starting) {
- anticipateNext(starting ? this::processQuaternionToken : structureProcessor);
- return true;
- }
- /** Manage Euler elements / three axis stabilized section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageEulerElementsSection(final boolean starting) {
- anticipateNext(starting ? this::processEulerToken : structureProcessor);
- return true;
- }
- /** Manage angular velocity section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- * @since 12.0
- */
- boolean manageAngularVelocitylementsSection(final boolean starting) {
- anticipateNext(starting ? this::processAngularVelocityToken : structureProcessor);
- return true;
- }
- /** Manage Euler elements /spin stabilized section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageSpinElementsSection(final boolean starting) {
- anticipateNext(starting ? this::processSpinStabilizedToken : structureProcessor);
- return true;
- }
- /** Manage inertia section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- * @since 12.0
- */
- boolean manageInertiaSection(final boolean starting) {
- anticipateNext(starting ? this::processInertiaToken : structureProcessor);
- return true;
- }
- /** Manage maneuver parameters section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageManeuverParametersSection(final boolean starting) {
- anticipateNext(starting ? this::processManeuverToken : structureProcessor);
- return true;
- }
- /** Process one metadata token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processMetadataToken(final ParseToken token) {
- if (metadata == null) {
- // APM KVN file lack a META_START keyword, hence we can't call prepareMetadata()
- // automatically before the first metadata token arrives
- prepareMetadata();
- }
- inMetadata();
- try {
- return token.getName() != null &&
- MetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeM) {
- try {
- return AdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeD) {
- try {
- return AdmCommonMetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeC) {
- // token has not been recognized
- return false;
- }
- }
- }
- }
- /** Process one data substructure token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processDataSubStructureToken(final ParseToken token) {
- try {
- return token.getName() != null &&
- ApmDataSubStructureKey.valueOf(token.getName()).process(token, context, this);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processDataToken(final ParseToken token) {
- if (commentsBlock == null) {
- // APM KVN file lack a META_STOP keyword, hence we can't call finalizeMetadata()
- // automatically before the first data token arrives
- finalizeMetadata();
- // APM KVN file lack a DATA_START keyword, hence we can't call prepareData()
- // automatically before the first data token arrives
- prepareData();
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processQuaternionToken : this::processDataSubStructureToken);
- if ("COMMENT".equals(token.getName())) {
- if (token.getType() == TokenType.ENTRY) {
- commentsBlock.addComment(token.getContentAsNormalizedString());
- }
- return true;
- } else if ("EPOCH".equals(token.getName())) {
- if (token.getType() == TokenType.ENTRY) {
- token.processAsDate(date -> epoch = date, context);
- }
- return true;
- } else {
- return false;
- }
- }
- /** Process one quaternion data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processQuaternionToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (quaternionBlock == null) {
- quaternionBlock = new ApmQuaternion();
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processEulerToken : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- ApmQuaternionKey.valueOf(token.getName()).process(token, context, quaternionBlock, this::setEpoch);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one Euler angles data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processEulerToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (eulerBlock == null) {
- eulerBlock = new Euler();
- if (moveCommentsIfEmpty(quaternionBlock, eulerBlock)) {
- // get rid of the empty logical block
- quaternionBlock = null;
- }
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processAngularVelocityToken : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- EulerKey.valueOf(token.getName()).process(token, context, eulerBlock);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one angular velocity data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- * @since 12.0
- */
- private boolean processAngularVelocityToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (angularVelocityBlock == null) {
- angularVelocityBlock = new AngularVelocity();
- if (moveCommentsIfEmpty(eulerBlock, angularVelocityBlock)) {
- // get rid of the empty logical block
- eulerBlock = null;
- }
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processSpinStabilizedToken : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- AngularVelocityKey.valueOf(token.getName()).process(token, context, angularVelocityBlock);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one spin-stabilized data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processSpinStabilizedToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (spinStabilizedBlock == null) {
- spinStabilizedBlock = new SpinStabilized();
- if (moveCommentsIfEmpty(angularVelocityBlock, spinStabilizedBlock)) {
- // get rid of the empty logical block
- angularVelocityBlock = null;
- }
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processInertiaToken : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- SpinStabilizedKey.valueOf(token.getName()).process(token, context, spinStabilizedBlock);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one spacecraft parameters data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processInertiaToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (inertiaBlock == null) {
- inertiaBlock = new Inertia();
- if (moveCommentsIfEmpty(spinStabilizedBlock, inertiaBlock)) {
- // get rid of the empty logical block
- spinStabilizedBlock = null;
- }
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- this::processManeuverToken : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- InertiaKey.valueOf(token.getName()).process(token, context, inertiaBlock);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one maneuver data token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processManeuverToken(final ParseToken token) {
- commentsBlock.refuseFurtherComments();
- if (currentManeuver == null) {
- currentManeuver = new Maneuver();
- if (moveCommentsIfEmpty(inertiaBlock, currentManeuver)) {
- // get rid of the empty logical block
- inertiaBlock = null;
- }
- }
- anticipateNext(getFileFormat() == FileFormat.KVN && header.getFormatVersion() < 2.0 ?
- new ErrorState() : this::processDataSubStructureToken);
- try {
- return token.getName() != null &&
- ManeuverKey.valueOf(token.getName()).process(token, context, currentManeuver);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- maneuvers.add(currentManeuver);
- currentManeuver = null;
- return false;
- }
- }
- /** Move comments from one empty logical block to another logical block.
- * @param origin origin block
- * @param destination destination block
- * @return true if origin block was empty
- */
- private boolean moveCommentsIfEmpty(final CommentsContainer origin, final CommentsContainer destination) {
- if (origin != null && origin.acceptComments()) {
- // origin block is empty, move the existing comments
- for (final String comment : origin.getComments()) {
- destination.addComment(comment);
- }
- return true;
- } else {
- return false;
- }
- }
- }