Commit b3b6a5e6 authored by Luc Maisonobe's avatar Luc Maisonobe
Browse files

Merge branch 'issue-474' into develop

parents 43a4ad08 e864a711
Pipeline #1068 passed with stages
in 30 minutes and 10 seconds
......@@ -21,6 +21,9 @@
</properties>
<body>
<release version="11.0" date="TBD" description="TBD">
<action dev="luc" type="add">
Added support for reading and writing CCSDS NDM composite messages.
</action>
<action dev="luc" type="fix" issue="776">
Fixed associativity in units parsing.
</action>
......
......@@ -40,7 +40,7 @@
participant BufferedInputStream
participant XyzLexicalAnalyzer
participant ParseToken
participant NDMFile
participant NdmConstituent
activate Main
create DataSource
......@@ -85,9 +85,9 @@
deactivate MessageParser
end
deactivate XyzLexicalAnalyzer
create NDMFile
MessageParser -> NDMFile : build
MessageParser --> Main : NDMFile
create NdmConstituent
MessageParser -> NdmConstituent : build
MessageParser --> Main : NdmConstituent
deactivate MessageParser
deactivate Main
......
......@@ -37,6 +37,7 @@
-formatVersion
-creationDate
-originator
-messageId
}
class Metadata {
-timeSystem
......@@ -93,12 +94,14 @@
OMM, OCM, TDM, APM, and AEM
end note
abstract class NdmFile
abstract class NdmConstituent
class NdmFile
NdmFile <|-- OemFile
NdmFile <|-- OpmFile
Header "1" <--o NdmFile
Segment "*" <--o NdmFile
NdmConstituent <|-- OemFile
NdmConstituent <|-- OpmFile
Header "1" <--o NdmConstituent
Segment "*" <--o NdmConstituent
NdmConstituent "*" <--o NdmFile
}
}
......
......@@ -322,7 +322,8 @@ public enum OrekitMessages implements Localizable {
UNINITIALIZED_VALUE_FOR_KEY("value for key {0} has not been initialized"),
UNKNOWN_UNIT("unknown unit {0}"),
INCOMPATIBLE_UNITS("units {0} and {1} are not compatible"),
MISSING_VELOCITY("missing velocity data");
MISSING_VELOCITY("missing velocity data"),
ATTEMPT_TO_GENERATE_MALFORMED_FILE("attempt to generate file {0} with a formatting error");
// CHECKSTYLE: resume JavadocVariable check
......
......@@ -23,10 +23,12 @@ import org.orekit.files.ccsds.ndm.adm.apm.ApmParser;
import org.orekit.files.ccsds.ndm.odm.oem.OemParser;
import org.orekit.files.ccsds.ndm.odm.omm.OmmParser;
import org.orekit.files.ccsds.ndm.odm.opm.OpmParser;
import org.orekit.files.ccsds.ndm.tdm.RangeUnits;
import org.orekit.files.ccsds.ndm.tdm.RangeUnitsConverter;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
/** Abstract builder for all {@link NdmFile CCSDS Message} files parsers/writers.
/** Abstract builder for all {@link NdmConstituent CCSDS Message} files parsers/writers.
* @param <T> type of the builder
* @author Luc Maisonobe
* @since 11.0
......@@ -45,19 +47,25 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
/** Spin axis in spacecraft body frame. */
private final Vector3D spinAxis;
/** Converter for {@link RangeUnits#RU Range Units}. */
private final RangeUnitsConverter rangeUnitsConverter;
/**
* Complete constructor.
* @param conventions IERS Conventions
* @param dataContext used to retrieve frames, time scales, etc.
* @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
* @param spinAxis spin axis in spacecraft body frame
* @param rangeUnitsConverter converter for {@link RangeUnits#RU Range Units}
*/
protected AbstractBuilder(final IERSConventions conventions, final DataContext dataContext,
final AbsoluteDate missionReferenceDate, final Vector3D spinAxis) {
final AbsoluteDate missionReferenceDate, final Vector3D spinAxis,
final RangeUnitsConverter rangeUnitsConverter) {
this.conventions = conventions;
this.dataContext = dataContext;
this.missionReferenceDate = missionReferenceDate;
this.spinAxis = spinAxis;
this.rangeUnitsConverter = rangeUnitsConverter;
}
/** Build an instance.
......@@ -65,17 +73,20 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
* @param newDataContext used to retrieve frames, time scales, etc.
* @param newMissionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
* @param newSpinAxis spin axis in spacecraft body frame
* @param newRangeUnitsConverter converter for {@link RangeUnits#RU Range Units}
* @return new instance
*/
protected abstract T create(IERSConventions newConventions, DataContext newDataContext,
AbsoluteDate newMissionReferenceDate, Vector3D newSpinAxis);
AbsoluteDate newMissionReferenceDate, Vector3D newSpinAxis,
RangeUnitsConverter newRangeUnitsConverter);
/** Set up IERS conventions.
* @param newConventions IERS Conventions
* @return a new builder with updated configuration (the instance is not changed)
*/
public T withConventions(final IERSConventions newConventions) {
return create(newConventions, getDataContext(), getMissionReferenceDate(), getSpinAxis());
return create(newConventions, getDataContext(), getMissionReferenceDate(),
getSpinAxis(), getRangeUnitsConverter());
}
/** Get the IERS conventions.
......@@ -90,7 +101,8 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
* @return a new builder with updated configuration (the instance is not changed)
*/
public T withDataContext(final DataContext newDataContext) {
return create(getConventions(), newDataContext, getMissionReferenceDate(), getSpinAxis());
return create(getConventions(), newDataContext, getMissionReferenceDate(),
getSpinAxis(), getRangeUnitsConverter());
}
/** Get the data context.
......@@ -111,7 +123,8 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
* @return a new builder with updated configuration (the instance is not changed)
*/
public T withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
return create(getConventions(), getDataContext(), newMissionReferenceDate, getSpinAxis());
return create(getConventions(), getDataContext(), newMissionReferenceDate,
getSpinAxis(), getRangeUnitsConverter());
}
/** Get the mission reference date or Mission Elapsed Time or Mission Relative Time time systems.
......@@ -129,7 +142,8 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
* @return a new builder with updated configuration (the instance is not changed)
*/
public T withSpinAxis(final Vector3D newSpinAxis) {
return create(getConventions(), getDataContext(), getMissionReferenceDate(), newSpinAxis);
return create(getConventions(), getDataContext(), getMissionReferenceDate(),
newSpinAxis, getRangeUnitsConverter());
}
/**
......@@ -140,4 +154,20 @@ public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
return spinAxis;
}
/** Set up the converter for {@link RangeUnits#RU Range Units}.
* @param newRangeUnitsConverter converter for {@link RangeUnits#RU Range Units}
* @return a new builder with updated configuration (the instance is not changed)
*/
public T withRangeUnitsConverter(final RangeUnitsConverter newRangeUnitsConverter) {
return create(getConventions(), getDataContext(), getMissionReferenceDate(),
getSpinAxis(), rangeUnitsConverter);
}
/** Get the converter for {@link RangeUnits#RU Range Units}.
* @return converter for {@link RangeUnits#RU Range Units}
*/
public RangeUnitsConverter getRangeUnitsConverter() {
return rangeUnitsConverter;
}
}
/* Copyright 2002-2021 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;
import java.util.Collections;
import java.util.List;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.section.Header;
import org.orekit.files.ccsds.section.Segment;
import org.orekit.utils.IERSConventions;
/**
* Constituents of a CCSDS Navigation Data Message.
* Constituents may be Attitude Data Message (ADM), Orbit Data Message (ODM),
* Tracking Data Message (TDM)…
* Each constituent has its own header and a list of segments.
* @param <H> type of the header
* @param <S> type of the segments
* @author Bryan Cazabonne
* @since 10.2
*/
public abstract class NdmConstituent<H extends Header, S extends Segment<?, ?>> {
/** Header. */
private final H header;
/** segments list. */
private final List<S> segments;
/** IERS conventions used. */
private final IERSConventions conventions;
/** Data context. */
private final DataContext dataContext;
/**
* Constructor.
* @param header file header
* @param segments file segments
* @param conventions IERS conventions
* @param dataContext used for creating frames, time scales, etc.
*/
protected NdmConstituent(final H header, final List<S> segments,
final IERSConventions conventions, final DataContext dataContext) {
this.header = header;
this.segments = segments;
this.conventions = conventions;
this.dataContext = dataContext;
}
/**
* Get the header.
* @return header
* @since 11.0
*/
public H getHeader() {
return header;
}
/**
* Get the segments.
* @return segments
* @since 11.0
*/
public List<S> getSegments() {
return Collections.unmodifiableList(segments);
}
/**
* Get IERS conventions.
* @return IERS conventions
*/
public IERSConventions getConventions() {
if (conventions != null) {
return conventions;
} else {
throw new OrekitException(OrekitMessages.CCSDS_UNKNOWN_CONVENTIONS);
}
}
/**
* Get the data context.
* @return the data context used for creating frames, time scales, etc.
*/
public DataContext getDataContext() {
return dataContext;
}
}
......@@ -19,89 +19,40 @@ package org.orekit.files.ccsds.ndm;
import java.util.Collections;
import java.util.List;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.section.Header;
import org.orekit.files.ccsds.section.Segment;
import org.orekit.utils.IERSConventions;
/**
* The NDMFile (Navigation Data Message) class represents the navigation
* messages used by the CCSDS format, (i.e. the Attitude Data Message (ADM),
* the Orbit Data Message (ODM) and the Tracking Data Message (TDM)).
* It contains the information of the message's header and configuration data
* (set in the parser).
* @param <H> type of the header
* @param <S> type of the segments
* @author Bryan Cazabonne
* @since 10.2
/** CCSDS Navigation Data Message.
* This class is a container for comments and {@link NdmConstituent constituents}.
* @author Luc Maisonobe
* @since 11.0
*/
public abstract class NdmFile<H extends Header, S extends Segment<?, ?>> {
/** Header. */
private final H header;
/** segments list. */
private final List<S> segments;
public class NdmFile {
/** IERS conventions used. */
private final IERSConventions conventions;
/** File comments. */
private final List<String> comments;
/** Data context. */
private final DataContext dataContext;
/**
* Constructor.
* @param header file header
* @param segments file segments
* @param conventions IERS conventions
* @param dataContext used for creating frames, time scales, etc.
*/
protected NdmFile(final H header, final List<S> segments,
final IERSConventions conventions, final DataContext dataContext) {
this.header = header;
this.segments = segments;
this.conventions = conventions;
this.dataContext = dataContext;
}
/**
* Get the header.
* @return header
* @since 11.0
*/
public H getHeader() {
return header;
}
/** Constituents of the message. */
private final List<NdmConstituent<?, ?>> constituents;
/**
* Get the segments.
* @return segments
* @since 11.0
/** Simple constructor.
* @param comments file comments
* @param constituents constituents of the message
*/
public List<S> getSegments() {
return Collections.unmodifiableList(segments);
public NdmFile(final List<String> comments, final List<NdmConstituent<?, ?>> constituents) {
this.comments = comments;
this.constituents = constituents;
}
/**
* Get IERS conventions.
* @return IERS conventions
/** Get an unmodifiable view of the comments.
* @return unmodifiable view of the comment
*/
public IERSConventions getConventions() {
if (conventions != null) {
return conventions;
} else {
throw new OrekitException(OrekitMessages.CCSDS_UNKNOWN_CONVENTIONS);
}
public List<String> getComments() {
return Collections.unmodifiableList(comments);
}
/**
* Get the data context.
* @return the data context used for creating frames, time scales, etc.
/** Get an unmodifiable view of the constituents.
* @return unmodifiable view of the constituents
*/
public DataContext getDataContext() {
return dataContext;
public List<NdmConstituent<?, ?>> getConstituents() {
return Collections.unmodifiableList(constituents);
}
}
/* Copyright 2002-2021 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.section.CommentsContainer;
import org.orekit.files.ccsds.utils.FileFormat;
import org.orekit.files.ccsds.utils.lexical.ParseToken;
import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
import org.orekit.files.ccsds.utils.parsing.AbstractMessageParser;
/** A parser for the CCSDS NDM (Navigation Data Message).
* @author Luc Maisonobe
* @since 11.0
*/
public class NdmParser extends AbstractMessageParser<NdmFile> {
/** Builder for the constituents parsers. */
private final ParserBuilder builder;
/** Current constituent parser. */
private AbstractMessageParser<? extends NdmConstituent<?, ?>> constituentParser;
/** Container for comments. */
private CommentsContainer comments;
/** Container for constituents. */
private List<NdmConstituent<?, ?>> constituents;
/** Simple constructor.
* <p>
* Calling this constructor directly is not recommended. Users should rather use
* {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildNdmParser()
* parserBuilder.buildNdmParser()}.
* </p>
* @param builder builder for the constituents parsers
*/
public NdmParser(final ParserBuilder builder) {
super(NdmStructureKey.ndm.name(), null);
this.builder = builder;
}
/** {@inheritDoc} */
@Override
public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {
final Map<String, XmlTokenBuilder> builders = super.getSpecialXmlElementsBuilders();
// special handling of root elements for all constituents
builders.putAll(builder.buildTdmParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildOpmParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildOmmParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildOemParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildOcmParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildApmParser().getSpecialXmlElementsBuilders());
builders.putAll(builder.buildAemParser().getSpecialXmlElementsBuilders());
return builders;
}
/** {@inheritDoc} */
@Override
public void reset(final FileFormat fileFormat) {
reset(fileFormat, this::processToken);
constituentParser = null;
comments = new CommentsContainer();
constituents = new ArrayList<>();
}
/** {@inheritDoc} */
@Override
public NdmFile build() {
// build the file from parsed comments and constituents
return new NdmFile(comments.getComments(), constituents);
}
/**
* Add comment.
* <p>
* Comments are accepted only at start. Once
* other content is stored in the same section, comments are refused.
* </p>
* @param comment comment line
* @return true if comment was accepted
*/
public boolean addComment(final String comment) {
return comments.addComment(comment);
}
/** Prepare parsing of a TDM constituent.
* @return always return true
*/
boolean manageTdmConstituent() {
return manageConstituent(builder::buildTdmParser);
}
/** Prepare parsing of an OPM constituent.
* @return always return true
*/
boolean manageOpmConstituent() {
return manageConstituent(builder::buildOpmParser);
}
/** Prepare parsing of an OMM constituent.
* @return always return true
*/
boolean manageOmmConstituent() {
return manageConstituent(builder::buildOmmParser);
}
/** Prepare parsing of an OEM constituent.
* @return always return true
*/
boolean manageOemConstituent() {
return manageConstituent(builder::buildOemParser);
}
/** Prepare parsing of an OCM constituent.
* @return always return true
*/
boolean manageOcmConstituent() {
return manageConstituent(builder::buildOcmParser);
}
/** Prepare parsing of an APM constituent.
* @return always return true
*/
boolean manageApmConstituent() {
return manageConstituent(builder::buildApmParser);
}
/** Prepare parsing of a AEM constituent.
* @return always return true
*/
boolean manageAemConstituent() {
return manageConstituent(builder::buildAemParser);
}
/** Prepare parsing of a constituent.
* @param parserSupplier supplier for constituent parser
* @return always return true
*/
boolean manageConstituent(final Supplier<AbstractMessageParser<? extends NdmConstituent<?, ?>>> parserSupplier) {
// as we have started parsing constituents, we cannot accept any further comments
comments.refuseFurtherComments();
<