AcmParser.java

  1. /* Copyright 2022-2025 Luc Maisonobe
  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.ndm.adm.acm;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.function.Function;
  23. import java.util.regex.Pattern;

  24. import org.orekit.data.DataContext;
  25. import org.orekit.data.DataSource;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitIllegalArgumentException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  30. import org.orekit.files.ccsds.ndm.adm.AdmHeader;
  31. import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
  32. import org.orekit.files.ccsds.ndm.adm.AdmParser;
  33. import org.orekit.files.ccsds.ndm.odm.UserDefined;
  34. import org.orekit.files.ccsds.ndm.odm.ocm.Ocm;
  35. import org.orekit.files.ccsds.section.HeaderProcessingState;
  36. import org.orekit.files.ccsds.section.KvnStructureProcessingState;
  37. import org.orekit.files.ccsds.section.MetadataKey;
  38. import org.orekit.files.ccsds.section.Segment;
  39. import org.orekit.files.ccsds.section.XmlStructureProcessingState;
  40. import org.orekit.files.ccsds.utils.ContextBinding;
  41. import org.orekit.files.ccsds.utils.FileFormat;
  42. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  43. import org.orekit.files.ccsds.utils.lexical.TokenType;
  44. import org.orekit.files.ccsds.utils.lexical.UserDefinedXmlTokenBuilder;
  45. import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
  46. import org.orekit.files.ccsds.utils.parsing.ProcessingState;
  47. import org.orekit.files.general.AttitudeEphemerisFileParser;
  48. import org.orekit.time.AbsoluteDate;
  49. import org.orekit.utils.IERSConventions;

  50. /** A parser for the CCSDS ACM (Attitude Comprehensive Message).
  51.  * @author Luc Maisonobe
  52.  * @since 12.0
  53.  */
  54. public class AcmParser extends AdmParser<Acm, AcmParser> implements AttitudeEphemerisFileParser<Acm> {

  55.     /** Pattern for splitting strings at blanks. */
  56.     private static final Pattern SPLIT_AT_BLANKS = Pattern.compile("\\s+");

  57.     /** File header. */
  58.     private AdmHeader header;

  59.     /** Metadata for current observation block. */
  60.     private AcmMetadata metadata;

  61.     /** Context binding valid for current metadata. */
  62.     private ContextBinding context;

  63.     /** Attitude state histories logical blocks. */
  64.     private List<AttitudeStateHistory> attitudeBlocks;

  65.     /** Current attitude state metadata. */
  66.     private AttitudeStateHistoryMetadata currentAttitudeStateHistoryMetadata;

  67.     /** Current attitude state time history being read. */
  68.     private List<AttitudeState> currentAttitudeStateHistory;

  69.     /** Physical properties logical block. */
  70.     private AttitudePhysicalProperties physicBlock;

  71.     /** Covariance logical blocks. */
  72.     private List<AttitudeCovarianceHistory> covarianceBlocks;

  73.     /** Current covariance metadata. */
  74.     private AttitudeCovarianceHistoryMetadata currentCovarianceHistoryMetadata;

  75.     /** Current covariance history being read. */
  76.     private List<AttitudeCovariance> currentCovarianceHistory;

  77.     /** Maneuver logical blocks. */
  78.     private List<AttitudeManeuver> maneuverBlocks;

  79.     /** Current maneuver history being read. */
  80.     private AttitudeManeuver currentManeuver;

  81.     /** Attitude determination logical block. */
  82.     private AttitudeDetermination attitudeDeterminationBlock;

  83.     /** Attitude determination sensor logical block. */
  84.     private AttitudeDeterminationSensor attitudeDeterminationSensorBlock;

  85.     /** User defined parameters logical block. */
  86.     private UserDefined userDefinedBlock;

  87.     /** Processor for global message structure. */
  88.     private ProcessingState structureProcessor;

  89.     /**
  90.      * Complete constructor.
  91.      * <p>
  92.      * Calling this constructor directly is not recommended. Users should rather use
  93.      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildAcmParser()
  94.      * parserBuilder.buildAcmParser()}.
  95.      * </p>
  96.      * @param conventions IERS Conventions
  97.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  98.      * @param dataContext used to retrieve frames, time scales, etc.
  99.      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
  100.      * @param filters filters to apply to parse tokens
  101.      * @since 12.0
  102.      */
  103.     public AcmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
  104.                      final ParsedUnitsBehavior parsedUnitsBehavior,
  105.                      final Function<ParseToken, List<ParseToken>>[] filters) {
  106.         super(Acm.ROOT, Acm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, null,
  107.               parsedUnitsBehavior, filters);
  108.     }

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {

  112.         final Map<String, XmlTokenBuilder> builders = super.getSpecialXmlElementsBuilders();

  113.         // special handling of user-defined parameters
  114.         builders.put(UserDefined.USER_DEFINED_XML_TAG, new UserDefinedXmlTokenBuilder());

  115.         return builders;

  116.     }

  117.     /** {@inheritDoc} */
  118.     @Override
  119.     public Acm parse(final DataSource source) {
  120.         return parseMessage(source);
  121.     }

  122.     /** {@inheritDoc} */
  123.     @Override
  124.     public AdmHeader getHeader() {
  125.         return header;
  126.     }

  127.     /** {@inheritDoc} */
  128.     @Override
  129.     public void reset(final FileFormat fileFormat) {
  130.         header                     = new AdmHeader();
  131.         metadata                   = null;
  132.         context                    = null;
  133.         attitudeBlocks             = null;
  134.         physicBlock                = null;
  135.         covarianceBlocks           = null;
  136.         maneuverBlocks             = null;
  137.         attitudeDeterminationBlock = null;
  138.         userDefinedBlock           = null;
  139.         if (fileFormat == FileFormat.XML) {
  140.             structureProcessor = new XmlStructureProcessingState(Acm.ROOT, this);
  141.             reset(fileFormat, structureProcessor);
  142.         } else {
  143.             structureProcessor = new KvnStructureProcessingState(this);
  144.             reset(fileFormat, new HeaderProcessingState(this));
  145.         }
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public boolean prepareHeader() {
  150.         anticipateNext(new HeaderProcessingState(this));
  151.         return true;
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public boolean inHeader() {
  156.         anticipateNext(structureProcessor);
  157.         return true;
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public boolean finalizeHeader() {
  162.         header.validate(header.getFormatVersion());
  163.         return true;
  164.     }

  165.     /** {@inheritDoc} */
  166.     @Override
  167.     public boolean prepareMetadata() {
  168.         if (metadata != null) {
  169.             return false;
  170.         }
  171.         metadata  = new AcmMetadata(getDataContext());
  172.         context   = new ContextBinding(this::getConventions, this::isSimpleEOP, this::getDataContext,
  173.                                        this::getParsedUnitsBehavior, metadata::getEpochT0, metadata::getTimeSystem,
  174.                                        () -> 0.0, () -> 1.0);
  175.         anticipateNext(this::processMetadataToken);
  176.         return true;
  177.     }

  178.     /** {@inheritDoc} */
  179.     @Override
  180.     public boolean inMetadata() {
  181.         anticipateNext(structureProcessor);
  182.         return true;
  183.     }

  184.     /** {@inheritDoc} */
  185.     @Override
  186.     public boolean finalizeMetadata() {
  187.         metadata.validate(header.getFormatVersion());
  188.         anticipateNext(this::processDataSubStructureToken);
  189.         return true;
  190.     }

  191.     /** {@inheritDoc} */
  192.     @Override
  193.     public boolean prepareData() {
  194.         anticipateNext(this::processDataSubStructureToken);
  195.         return true;
  196.     }

  197.     /** {@inheritDoc} */
  198.     @Override
  199.     public boolean inData() {
  200.         return true;
  201.     }

  202.     /** {@inheritDoc} */
  203.     @Override
  204.     public boolean finalizeData() {
  205.         return true;
  206.     }

  207.     /** Manage attitude state history section.
  208.      * @param starting if true, parser is entering the section
  209.      * otherwise it is leaving the section
  210.      * @return always return true
  211.      */
  212.     boolean manageAttitudeStateSection(final boolean starting) {
  213.         if (starting) {
  214.             if (attitudeBlocks == null) {
  215.                 // this is the first attitude block, we need to allocate the container
  216.                 attitudeBlocks = new ArrayList<>();
  217.             }
  218.             currentAttitudeStateHistoryMetadata = new AttitudeStateHistoryMetadata();
  219.             currentAttitudeStateHistory         = new ArrayList<>();
  220.             anticipateNext(this::processAttitudeStateToken);
  221.         } else {
  222.             anticipateNext(structureProcessor);
  223.             attitudeBlocks.add(new AttitudeStateHistory(currentAttitudeStateHistoryMetadata,
  224.                                                         currentAttitudeStateHistory));
  225.         }
  226.         return true;
  227.     }

  228.     /** Manage physical properties section.
  229.      * @param starting if true, parser is entering the section
  230.      * otherwise it is leaving the section
  231.      * @return always return true
  232.      */
  233.     boolean managePhysicalPropertiesSection(final boolean starting) {
  234.         if (starting) {
  235.             physicBlock = new AttitudePhysicalProperties();
  236.             anticipateNext(this::processPhysicalPropertyToken);
  237.         } else {
  238.             anticipateNext(structureProcessor);
  239.         }
  240.         return true;
  241.     }

  242.     /** Manage covariance history section.
  243.      * @param starting if true, parser is entering the section
  244.      * otherwise it is leaving the section
  245.      * @return always return true
  246.      */
  247.     boolean manageCovarianceHistorySection(final boolean starting) {
  248.         if (starting) {
  249.             if (covarianceBlocks == null) {
  250.                 // this is the first covariance block, we need to allocate the container
  251.                 covarianceBlocks = new ArrayList<>();
  252.             }
  253.             currentCovarianceHistoryMetadata = new AttitudeCovarianceHistoryMetadata();
  254.             currentCovarianceHistory         = new ArrayList<>();
  255.             anticipateNext(this::processCovarianceToken);
  256.         } else {
  257.             anticipateNext(structureProcessor);
  258.             covarianceBlocks.add(new AttitudeCovarianceHistory(currentCovarianceHistoryMetadata,
  259.                                                                currentCovarianceHistory));
  260.             currentCovarianceHistoryMetadata = null;
  261.             currentCovarianceHistory         = null;
  262.         }
  263.         return true;
  264.     }

  265.     /** Manage maneuvers section.
  266.      * @param starting if true, parser is entering the section
  267.      * otherwise it is leaving the section
  268.      * @return always return true
  269.      */
  270.     boolean manageManeuversSection(final boolean starting) {
  271.         if (starting) {
  272.             if (maneuverBlocks == null) {
  273.                 // this is the first maneuver block, we need to allocate the container
  274.                 maneuverBlocks = new ArrayList<>();
  275.             }
  276.             currentManeuver = new AttitudeManeuver();
  277.             anticipateNext(this::processManeuverToken);
  278.         } else {
  279.             anticipateNext(structureProcessor);
  280.             maneuverBlocks.add(currentManeuver);
  281.             currentManeuver = null;
  282.         }
  283.         return true;
  284.     }

  285.     /** Manage attitude determination section.
  286.      * @param starting if true, parser is entering the section
  287.      * otherwise it is leaving the section
  288.      * @return always return true
  289.      */
  290.     boolean manageAttitudeDeterminationSection(final boolean starting) {
  291.         if (starting) {
  292.             attitudeDeterminationBlock = new AttitudeDetermination();
  293.             anticipateNext(this::processAttitudeDeterminationToken);
  294.         } else {
  295.             anticipateNext(structureProcessor);
  296.         }
  297.         return true;
  298.     }

  299.     /** Manage attitude determination sensor section.
  300.      * @param starting if true, parser is entering the section
  301.      * otherwise it is leaving the section
  302.      * @return always return true
  303.      */
  304.     boolean manageAttitudeDeterminationSensorSection(final boolean starting) {
  305.         if (starting) {
  306.             attitudeDeterminationSensorBlock = new AttitudeDeterminationSensor();
  307.             anticipateNext(this::processAttitudeDeterminationSensorToken);
  308.         } else {
  309.             anticipateNext(this::processDataSubStructureToken);
  310.         }
  311.         return true;
  312.     }

  313.     /** Manage user-defined parameters section.
  314.      * @param starting if true, parser is entering the section
  315.      * otherwise it is leaving the section
  316.      * @return always return true
  317.      */
  318.     boolean manageUserDefinedParametersSection(final boolean starting) {
  319.         if (starting) {
  320.             userDefinedBlock = new UserDefined();
  321.             anticipateNext(this::processUserDefinedToken);
  322.         } else {
  323.             anticipateNext(structureProcessor);
  324.         }
  325.         return true;
  326.     }

  327.     /** {@inheritDoc} */
  328.     @Override
  329.     public Acm build() {

  330.         if (userDefinedBlock != null && userDefinedBlock.getParameters().isEmpty()) {
  331.             userDefinedBlock = null;
  332.         }

  333.         final AcmData data = new AcmData(attitudeBlocks, physicBlock, covarianceBlocks,
  334.                                          maneuverBlocks, attitudeDeterminationBlock, userDefinedBlock);
  335.         data.validate(header.getFormatVersion());

  336.         return new Acm(header, Collections.singletonList(new Segment<>(metadata, data)),
  337.                            getConventions(), getDataContext());

  338.     }

  339.     /** Process one metadata token.
  340.      * @param token token to process
  341.      * @return true if token was processed, false otherwise
  342.      */
  343.     private boolean processMetadataToken(final ParseToken token) {
  344.         inMetadata();
  345.         try {
  346.             return token.getName() != null &&
  347.                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
  348.         } catch (IllegalArgumentException iaeM) {
  349.             try {
  350.                 return AdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  351.             } catch (IllegalArgumentException iaeD) {
  352.                 try {
  353.                     return AcmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  354.                 } catch (IllegalArgumentException iaeC) {
  355.                     // token has not been recognized
  356.                     return false;
  357.                 }
  358.             }
  359.         }
  360.     }

  361.     /** Process one data substructure token.
  362.      * @param token token to process
  363.      * @return true if token was processed, false otherwise
  364.      */
  365.     private boolean processDataSubStructureToken(final ParseToken token) {
  366.         try {
  367.             return token.getName() != null &&
  368.                    AcmDataSubStructureKey.valueOf(token.getName()).process(token, this);
  369.         } catch (IllegalArgumentException iae) {
  370.             // token has not been recognized
  371.             return false;
  372.         }
  373.     }

  374.     /** Process one attitude state history data token.
  375.      * @param token token to process
  376.      * @return true if token was processed, false otherwise
  377.      */
  378.     private boolean processAttitudeStateToken(final ParseToken token) {
  379.         if (token.getName() != null && !token.getName().equals(Acm.ATT_LINE)) {
  380.             // we are in the section metadata part
  381.             try {
  382.                 return AttitudeStateHistoryMetadataKey.valueOf(token.getName()).
  383.                        process(token, context, currentAttitudeStateHistoryMetadata);
  384.             } catch (IllegalArgumentException iae) {
  385.                 // token has not been recognized
  386.                 return false;
  387.             }
  388.         } else {
  389.             // we are in the section data part
  390.             if (currentAttitudeStateHistory.isEmpty()) {
  391.                 // we are starting the real data section, we can now check metadata is complete
  392.                 currentAttitudeStateHistoryMetadata.validate(header.getFormatVersion());
  393.                 anticipateNext(this::processDataSubStructureToken);
  394.             }
  395.             if (token.getType() == TokenType.START || token.getType() == TokenType.STOP) {
  396.                 return true;
  397.             }
  398.             try {
  399.                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
  400.                 final AbsoluteDate epoch = context.getTimeSystem().getConverter(context).parse(fields[0]);
  401.                 return currentAttitudeStateHistory.add(new AttitudeState(currentAttitudeStateHistoryMetadata.getAttitudeType(),
  402.                                                                          currentAttitudeStateHistoryMetadata.getRateType(),
  403.                                                                          epoch, fields, 1));
  404.             } catch (NumberFormatException | OrekitIllegalArgumentException e) {
  405.                 throw new OrekitException(e, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  406.                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  407.             }
  408.         }
  409.     }

  410.     /** Process one physical property data token.
  411.      * @param token token to process
  412.      * @return true if token was processed, false otherwise
  413.      */
  414.     private boolean processPhysicalPropertyToken(final ParseToken token) {
  415.         anticipateNext(this::processDataSubStructureToken);
  416.         try {
  417.             return token.getName() != null &&
  418.                    AttitudePhysicalPropertiesKey.valueOf(token.getName()).process(token, context, physicBlock);
  419.         } catch (IllegalArgumentException iae) {
  420.             // token has not been recognized
  421.             return false;
  422.         }
  423.     }

  424.     /** Process one covariance history history data token.
  425.      * @param token token to process
  426.      * @return true if token was processed, false otherwise
  427.      */
  428.     private boolean processCovarianceToken(final ParseToken token) {
  429.         if (token.getName() != null && !token.getName().equals(Ocm.COV_LINE)) {
  430.             // we are in the section metadata part
  431.             try {
  432.                 return AttitudeCovarianceHistoryMetadataKey.valueOf(token.getName()).
  433.                        process(token, context, currentCovarianceHistoryMetadata);
  434.             } catch (IllegalArgumentException iae) {
  435.                 // token has not been recognized
  436.                 return false;
  437.             }
  438.         } else {
  439.             // we are in the section data part
  440.             if (currentCovarianceHistory.isEmpty()) {
  441.                 // we are starting the real data section, we can now check metadata is complete
  442.                 currentCovarianceHistoryMetadata.validate(header.getFormatVersion());
  443.                 anticipateNext(this::processDataSubStructureToken);
  444.             }
  445.             if (token.getType() == TokenType.START || token.getType() == TokenType.STOP) {
  446.                 return true;
  447.             }
  448.             try {
  449.                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
  450.                 currentCovarianceHistory.add(new AttitudeCovariance(currentCovarianceHistoryMetadata.getCovType(),
  451.                                                                     context.getTimeSystem().getConverter(context).parse(fields[0]),
  452.                                                                     fields, 1));
  453.                 return true;
  454.             } catch (NumberFormatException nfe) {
  455.                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  456.                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  457.             }
  458.         }
  459.     }

  460.     /** Process one maneuver data token.
  461.      * @param token token to process
  462.      * @return true if token was processed, false otherwise
  463.      */
  464.     private boolean processManeuverToken(final ParseToken token) {
  465.         anticipateNext(this::processDataSubStructureToken);
  466.         try {
  467.             return token.getName() != null &&
  468.                    AttitudeManeuverKey.valueOf(token.getName()).process(token, context, currentManeuver);
  469.         } catch (IllegalArgumentException iae) {
  470.             // token has not been recognized
  471.             return false;
  472.         }
  473.     }

  474.     /** Process one attitude determination data token.
  475.      * @param token token to process
  476.      * @return true if token was processed, false otherwise
  477.      */
  478.     private boolean processAttitudeDeterminationToken(final ParseToken token) {
  479.         anticipateNext(attitudeDeterminationSensorBlock != null ?
  480.                        this::processAttitudeDeterminationSensorToken :
  481.                        this::processDataSubStructureToken);
  482.         if (token.getName() == null) {
  483.             return false;
  484.         }
  485.         try {
  486.             return AttitudeDeterminationKey.valueOf(token.getName()).process(token, this, context, attitudeDeterminationBlock);
  487.         } catch (IllegalArgumentException iae1) {
  488.             // token has not been recognized
  489.             return false;
  490.         }
  491.     }

  492.     /** Process one attitude determination sensor data token.
  493.      * @param token token to process
  494.      * @return true if token was processed, false otherwise
  495.      */
  496.     private boolean processAttitudeDeterminationSensorToken(final ParseToken token) {
  497.         anticipateNext(this::processAttitudeDeterminationToken);
  498.         if (token.getName() == null) {
  499.             return false;
  500.         }
  501.         try {
  502.             return AttitudeDeterminationSensorKey.valueOf(token.getName()).process(token, context, attitudeDeterminationSensorBlock);
  503.         } catch (IllegalArgumentException iae1) {
  504.             // token has not been recognized
  505.             attitudeDeterminationBlock.addSensor(attitudeDeterminationSensorBlock);
  506.             attitudeDeterminationSensorBlock = null;
  507.             return false;
  508.         }
  509.     }

  510.     /** Process one user-defined parameter data token.
  511.      * @param token token to process
  512.      * @return true if token was processed, false otherwise
  513.      */
  514.     private boolean processUserDefinedToken(final ParseToken token) {
  515.         anticipateNext(this::processDataSubStructureToken);
  516.         if ("COMMENT".equals(token.getName())) {
  517.             return token.getType() == TokenType.ENTRY ? userDefinedBlock.addComment(token.getContentAsNormalizedString()) : true;
  518.         } else if (token.getName().startsWith(UserDefined.USER_DEFINED_PREFIX)) {
  519.             if (token.getType() == TokenType.ENTRY) {
  520.                 userDefinedBlock.addEntry(token.getName().substring(UserDefined.USER_DEFINED_PREFIX.length()),
  521.                                           token.getContentAsNormalizedString());
  522.             }
  523.             return true;
  524.         } else {
  525.             // the token was not processed
  526.             return false;
  527.         }
  528.     }

  529. }