OemParser.java
- /* Copyright 2002-2025 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.odm.oem;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.function.Function;
- import java.util.regex.Pattern;
- import org.orekit.data.DataContext;
- import org.orekit.data.DataSource;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.files.ccsds.definitions.Units;
- import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
- import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
- import org.orekit.files.ccsds.ndm.odm.CartesianCovarianceKey;
- import org.orekit.files.ccsds.ndm.odm.OdmCommonMetadata;
- import org.orekit.files.ccsds.ndm.odm.CommonMetadataKey;
- import org.orekit.files.ccsds.ndm.odm.OdmHeader;
- import org.orekit.files.ccsds.ndm.odm.OdmMetadataKey;
- import org.orekit.files.ccsds.ndm.odm.OdmParser;
- import org.orekit.files.ccsds.ndm.odm.StateVector;
- import org.orekit.files.ccsds.ndm.odm.StateVectorKey;
- import org.orekit.files.ccsds.section.HeaderProcessingState;
- import org.orekit.files.ccsds.section.KvnStructureProcessingState;
- import org.orekit.files.ccsds.section.MetadataKey;
- 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.ProcessingState;
- import org.orekit.files.general.EphemerisFileParser;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.utils.IERSConventions;
- import org.orekit.utils.units.Unit;
- /**
- * A parser for the CCSDS OEM (Orbit Ephemeris Message).
- * <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>
- * @author sports
- * @since 6.1
- */
- public class OemParser extends OdmParser<Oem, OemParser> implements EphemerisFileParser<Oem> {
- /** Comment marker. */
- private static final String COMMENT = "COMMENT";
- /** Pattern for splitting strings at blanks. */
- private static final Pattern SPLIT_AT_BLANKS = Pattern.compile("\\s+");
- /** File header. */
- private OdmHeader header;
- /** File segments. */
- private List<OemSegment> segments;
- /** Metadata for current observation block. */
- private OemMetadata metadata;
- /** Context binding valid for current metadata. */
- private ContextBinding context;
- /** Current Ephemerides block being parsed. */
- private OemData currentBlock;
- /** Indicator for covariance parsing. */
- private boolean inCovariance;
- /** Current covariance matrix being parsed. */
- private CartesianCovariance currentCovariance;
- /** Current row number in covariance matrix. */
- private int currentRow;
- /** Default interpolation degree. */
- private int defaultInterpolationDegree;
- /** Processor for global message structure. */
- private ProcessingState structureProcessor;
- /** State vector logical block being read. */
- private StateVector stateVectorBlock;
- /**
- * Complete constructor.
- * <p>
- * Calling this constructor directly is not recommended. Users should rather use
- * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildOemParser()
- * parserBuilder.buildOemParser()}.
- * </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 mu gravitational coefficient
- * @param defaultInterpolationDegree default interpolation degree
- * @param parsedUnitsBehavior behavior to adopt for handling parsed units
- * @param filters filters to apply to parse tokens
- * @since 12.0
- */
- public OemParser(final IERSConventions conventions, final boolean simpleEOP,
- final DataContext dataContext,
- final AbsoluteDate missionReferenceDate, final double mu,
- final int defaultInterpolationDegree, final ParsedUnitsBehavior parsedUnitsBehavior,
- final Function<ParseToken, List<ParseToken>>[] filters) {
- super(Oem.ROOT, Oem.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
- missionReferenceDate, mu, parsedUnitsBehavior, filters);
- this.defaultInterpolationDegree = defaultInterpolationDegree;
- }
- /** {@inheritDoc} */
- @Override
- public Oem parse(final DataSource source) {
- return parseMessage(source);
- }
- /** {@inheritDoc} */
- @Override
- public OdmHeader getHeader() {
- return header;
- }
- /** {@inheritDoc} */
- @Override
- public void reset(final FileFormat fileFormat) {
- header = new OdmHeader();
- segments = new ArrayList<>();
- metadata = null;
- context = null;
- currentBlock = null;
- inCovariance = false;
- currentCovariance = null;
- currentRow = -1;
- if (fileFormat == FileFormat.XML) {
- structureProcessor = new XmlStructureProcessingState(Oem.ROOT, this);
- reset(fileFormat, structureProcessor);
- } else {
- structureProcessor = new KvnStructureProcessingState(this);
- reset(fileFormat, new HeaderProcessingState(this));
- }
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareHeader() {
- anticipateNext(new HeaderProcessingState(this));
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean inHeader() {
- anticipateNext(structureProcessor);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeHeader() {
- header.validate(header.getFormatVersion());
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareMetadata() {
- if (currentBlock != null) {
- // we have started a new segment, we need to finalize the previous one
- finalizeData();
- }
- metadata = new OemMetadata(defaultInterpolationDegree);
- 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(structureProcessor);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeMetadata() {
- metadata.finalizeMetadata(context);
- metadata.validate(header.getFormatVersion());
- if (metadata.getCenter().getBody() != null) {
- setMuCreated(metadata.getCenter().getBody().getGM());
- }
- anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnDataToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean prepareData() {
- currentBlock = new OemData();
- anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean inData() {
- anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnCovarianceToken);
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public boolean finalizeData() {
- if (metadata != null) {
- currentBlock.validate(header.getFormatVersion());
- segments.add(new OemSegment(metadata, currentBlock, getSelectedMu()));
- }
- metadata = null;
- context = null;
- currentBlock = null;
- inCovariance = false;
- currentCovariance = null;
- currentRow = -1;
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public Oem build() {
- // OEM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
- // automatically before the end of the file
- finalizeData();
- final Oem file = new Oem(header, segments, getConventions(), getDataContext(), getSelectedMu());
- file.checkTimeSystems();
- return file;
- }
- /** Manage state vector section in a XML message.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageXmlStateVectorSection(final boolean starting) {
- if (starting) {
- stateVectorBlock = new StateVector();
- anticipateNext(this::processXmlStateVectorToken);
- } else {
- currentBlock.addData(stateVectorBlock.toTimeStampedPVCoordinates(),
- stateVectorBlock.hasAcceleration());
- stateVectorBlock = null;
- anticipateNext(structureProcessor);
- }
- return true;
- }
- /** Manage covariance matrix section.
- * @param starting if true, parser is entering the section
- * otherwise it is leaving the section
- * @return always return true
- */
- boolean manageCovarianceSection(final boolean starting) {
- if (starting) {
- // save the current metadata for later retrieval of reference frame
- final OdmCommonMetadata savedMetadata = metadata;
- currentCovariance = new CartesianCovariance(savedMetadata::getReferenceFrame);
- anticipateNext(getFileFormat() == FileFormat.XML ?
- this::processXmlCovarianceToken :
- this::processKvnCovarianceToken);
- } else {
- currentBlock.addCovarianceMatrix(currentCovariance);
- currentCovariance = null;
- anticipateNext(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) {
- inMetadata();
- try {
- return token.getName() != null &&
- MetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeM) {
- try {
- return OdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeD) {
- try {
- return CommonMetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeC) {
- try {
- return OemMetadataKey.valueOf(token.getName()).process(token, context, metadata);
- } catch (IllegalArgumentException iaeE) {
- // token has not been recognized
- return false;
- }
- }
- }
- }
- }
- /** Process one XML data substructure token.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processXmlSubStructureToken(final ParseToken token) {
- if (COMMENT.equals(token.getName())) {
- return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
- } else {
- try {
- return token.getName() != null &&
- OemDataSubStructureKey.valueOf(token.getName()).process(token, this);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- }
- /** Process one data token in a KVN message.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processKvnDataToken(final ParseToken token) {
- if (currentBlock == null) {
- // OEM KVN file lack a DATA_START keyword, hence we can't call prepareData()
- // automatically before the first data token arrives
- prepareData();
- }
- inData();
- if (COMMENT.equals(token.getName())) {
- return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
- } else if (token.getType() == TokenType.RAW_LINE) {
- try {
- final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
- if (fields.length != 7 && fields.length != 10) {
- throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
- }
- stateVectorBlock = new StateVector();
- stateVectorBlock.setEpoch(context.getTimeSystem().getConverter(context).parse(fields[0]));
- stateVectorBlock.setP(0, Unit.KILOMETRE.toSI(Double.parseDouble(fields[1])));
- stateVectorBlock.setP(1, Unit.KILOMETRE.toSI(Double.parseDouble(fields[2])));
- stateVectorBlock.setP(2, Unit.KILOMETRE.toSI(Double.parseDouble(fields[3])));
- stateVectorBlock.setV(0, Units.KM_PER_S.toSI(Double.parseDouble(fields[4])));
- stateVectorBlock.setV(1, Units.KM_PER_S.toSI(Double.parseDouble(fields[5])));
- stateVectorBlock.setV(2, Units.KM_PER_S.toSI(Double.parseDouble(fields[6])));
- if (fields.length == 10) {
- stateVectorBlock.setA(0, Units.KM_PER_S2.toSI(Double.parseDouble(fields[7])));
- stateVectorBlock.setA(1, Units.KM_PER_S2.toSI(Double.parseDouble(fields[8])));
- stateVectorBlock.setA(2, Units.KM_PER_S2.toSI(Double.parseDouble(fields[9])));
- }
- return currentBlock.addData(stateVectorBlock.toTimeStampedPVCoordinates(),
- stateVectorBlock.hasAcceleration());
- } catch (NumberFormatException nfe) {
- throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- token.getLineNumber(), token.getFileName(), token.getRawContent());
- }
- } else {
- // not a raw line, it is most probably either the end of the data section or a covariance section
- return false;
- }
- }
- /** Process one state vector data token in a XML message.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processXmlStateVectorToken(final ParseToken token) {
- anticipateNext(this::processXmlSubStructureToken);
- try {
- return token.getName() != null &&
- StateVectorKey.valueOf(token.getName()).process(token, context, stateVectorBlock);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- /** Process one covariance token in a KVN message.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processKvnCovarianceToken(final ParseToken token) {
- anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
- if (token.getName() != null) {
- if (OemDataSubStructureKey.COVARIANCE.name().equals(token.getName()) ||
- OemDataSubStructureKey.covarianceMatrix.name().equals(token.getName())) {
- // we are entering/leaving covariance section
- inCovariance = token.getType() == TokenType.START;
- return true;
- } else if (!inCovariance) {
- // this is not a covariance token
- return false;
- } else {
- // named tokens in covariance section must be at the start, before the raw lines
- if (currentRow > 0) {
- // the previous covariance matrix was not completed
- throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE,
- token.getName(), token.getLineNumber(), token.getFileName());
- }
- if (currentCovariance == null) {
- // save the current metadata for later retrieval of reference frame
- final OdmCommonMetadata savedMetadata = metadata;
- currentCovariance = new CartesianCovariance(savedMetadata::getReferenceFrame);
- currentRow = 0;
- }
- // parse the token
- try {
- return CartesianCovarianceKey.valueOf(token.getName()).
- process(token, context, currentCovariance);
- } catch (IllegalArgumentException iae) {
- // token not recognized
- return false;
- }
- }
- } else {
- // this is a raw line
- try {
- final String[] fields = SPLIT_AT_BLANKS.split(token.getContentAsNormalizedString().trim());
- if (fields.length != currentRow + 1) {
- throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
- }
- for (int j = 0; j < fields.length; ++j) {
- currentCovariance.setCovarianceMatrixEntry(currentRow, j, 1.0e6 * Double.parseDouble(fields[j]));
- }
- if (++currentRow == 6) {
- // this was the last row
- currentBlock.addCovarianceMatrix(currentCovariance);
- currentCovariance = null;
- currentRow = -1;
- }
- return true;
- } catch (NumberFormatException nfe) {
- throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
- }
- }
- }
- /** Process one covariance matrix data token in a XML message.
- * @param token token to process
- * @return true if token was processed, false otherwise
- */
- private boolean processXmlCovarianceToken(final ParseToken token) {
- anticipateNext(this::processXmlSubStructureToken);
- try {
- return token.getName() != null &&
- CartesianCovarianceKey.valueOf(token.getName()).process(token, context, currentCovariance);
- } catch (IllegalArgumentException iae) {
- // token has not been recognized
- return false;
- }
- }
- }