Commit 44b534b8 authored by Luc Maisonobe's avatar Luc Maisonobe

Merge branch 'issue-474' into develop

parents 4b384b92 341994d0
Pipeline #1037 failed with stages
in 20 minutes and 53 seconds
......@@ -86,7 +86,7 @@
end
deactivate XyzLexicalAnalyzer
create NDMFile
MessageParser -> NDMFile : new
MessageParser -> NDMFile : build
MessageParser --> Main : NDMFile
deactivate MessageParser
deactivate Main
......
......@@ -109,6 +109,7 @@
class ParserBuilder {
+withConventions()
+withDataContext()
+withParsedUnitsBehavior()
+with...()
+buildOpmParser()
+buildOmmParser()
......@@ -119,6 +120,12 @@
+buildTdmParser()
}
enum ParsedUnitsBehavior {
+IGNORE_PARSED
+CONVERT_COMPATIBLE
+STRICT_COMPLIANCE
}
}
}
......@@ -140,5 +147,6 @@
XmlStructureProcessingState ..|> ProcessingState
ProcessingState <|.. OpmParser
ParserBuilder -right-> OpmParser : build
ParserBuilder o--> ParsedUnitsBehavior
@enduml
......@@ -16,8 +16,6 @@
*/
package org.orekit.files.ccsds.definitions;
import java.util.List;
import org.orekit.utils.units.Unit;
/**
......@@ -61,10 +59,10 @@ public class Units {
public static final Unit REV_PER_DAY = Unit.parse("rev/d");
/** Scaled revolutions per square day unit. */
public static final Unit REV_PER_DAY2_SCALED = Unit.parse("rev/d²").scale("2rev/d²", 2.0);
public static final Unit REV_PER_DAY2_SCALED = Unit.parse("2rev/d²");
/** Scaled revolutions per cubic day divieded by 6 unit. */
public static final Unit REV_PER_DAY3_SCALED = Unit.parse("rev/d³").scale("6rev/d³", 6.0);
public static final Unit REV_PER_DAY3_SCALED = Unit.parse("6rev/d³");
/** Degree per second unit. */
public static final Unit DEG_PER_S = Unit.parse("°/s");
......@@ -87,30 +85,4 @@ public class Units {
// nothing to do
}
/** Convert a list of units to a bracketed string.
* @param units lists to output (may be null or empty)
* @return bracketed string (null if units list is null or empty)
*/
public static String outputBracketed(final List<Unit> units) {
if (units == null || units.isEmpty()) {
// nothing to output
return null;
}
final StringBuilder builder = new StringBuilder();
builder.append('[');
boolean first = true;
for (final Unit unit : units) {
if (!first) {
builder.append(',');
}
builder.append(unit.getName());
first = false;
}
builder.append(']');
return builder.toString();
}
}
/* 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 org.hipparchus.util.Precision;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.utils.units.Unit;
/** Behavior adopted for units that have been parsed from a CCSDS message.
* @author Luc Maisonobe
* @since 11.0
*/
public enum ParsedUnitsBehavior {
/** Ignore parsed units, just relying on CCSDS standard.
* <p>
* When this behavior is selected having a unit parsed as second
* when CCSDS mandates kilometer will be accepted.
* </p>
*/
IGNORE_PARSED {
/** {@inheritDoc} */
@Override
public Unit select(final Unit message, final Unit standard) {
return standard;
}
},
/** Allow compatible units, performing conversion.
* <p>
* When this behavior is selected having a unit parsed as second
* when CCSDS mandates kilometer will be refused, but having a unit
* parsed as meter will be accepted, with proper conversion performed.
* Missing units (i.e. units parsed as {@link Unit#NONE}) are considered
* to be standard.
* </p>
*/
CONVERT_COMPATIBLE {
/** {@inheritDoc} */
@Override
public Unit select(final Unit message, final Unit standard) {
if (message == Unit.NONE) {
return standard;
} else if (message.sameDimension(standard)) {
return message;
} else {
throw new OrekitException(OrekitMessages.INCOMPATIBLE_UNITS,
message.getName(), standard.getName());
}
}
},
/** Enforce strict compliance with CCSDS standard.
* <p>
* When this behavior is selected having a unit parsed as second
* or as meter when CCSDS mandates kilometer will both be refused.
* Missing units (i.e. units parsed as {@link Unit#NONE}) are considered
* to be standard.
* </p>
*/
STRICT_COMPLIANCE {
/** {@inheritDoc} */
@Override
public Unit select(final Unit message, final Unit standard) {
if (message == Unit.NONE ||
(Precision.equals(message.getScale(), standard.getScale(), 1) &&
message.sameDimension(standard))) {
return standard;
} else {
throw new OrekitException(OrekitMessages.INCOMPATIBLE_UNITS,
message.getName(), standard.getName());
}
}
};
/** Select the unit to use for interpreting parsed value.
* @param message unit parsed in the CCSDS message
* @param standard unit mandated by the standard
* @return selected unit
*/
public abstract Unit select(Unit message, Unit standard);
}
......@@ -54,6 +54,9 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
/** Default interpolation degree. */
private final int defaultInterpolationDegree;
/** Behavior adopted for units that have been parsed from a CCSDS message. */
private final ParsedUnitsBehavior parsedUnitsBehavior;
/**
* Simple constructor.
* <p>
......@@ -66,6 +69,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
* <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
* <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
* <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
* <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
* </ul>
* </p>
*/
......@@ -85,12 +89,14 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
* <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
* <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
* <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
* <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
* </ul>
* </p>
* @param dataContext data context used to retrieve frames, time scales, etc.
*/
public ParserBuilder(final DataContext dataContext) {
this(IERSConventions.IERS_2010, dataContext, null, true, Double.NaN, Double.NaN, 1);
this(IERSConventions.IERS_2010, dataContext, null, true, Double.NaN,
Double.NaN, 1, ParsedUnitsBehavior.CONVERT_COMPATIBLE);
}
/** Complete constructor.
......@@ -101,16 +107,19 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
* @param mu gravitational coefficient
* @param defaultMass default mass
* @param defaultInterpolationDegree default interpolation degree
* @param parsedUnitsBehavior behavior to adopt for handling parsed units
*/
private ParserBuilder(final IERSConventions conventions, final DataContext dataContext,
final AbsoluteDate missionReferenceDate, final boolean simpleEOP,
final double mu, final double defaultMass,
final int defaultInterpolationDegree) {
final int defaultInterpolationDegree,
final ParsedUnitsBehavior parsedUnitsBehavior) {
super(conventions, dataContext, missionReferenceDate);
this.simpleEOP = simpleEOP;
this.mu = mu;
this.defaultMass = defaultMass;
this.defaultInterpolationDegree = defaultInterpolationDegree;
this.parsedUnitsBehavior = parsedUnitsBehavior;
}
/** {@inheritDoc} */
......@@ -118,7 +127,8 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
protected ParserBuilder create(final IERSConventions newConventions, final DataContext newDataContext,
final AbsoluteDate newMissionReferenceDate) {
return new ParserBuilder(newConventions, newDataContext, newMissionReferenceDate,
simpleEOP, mu, defaultMass, defaultInterpolationDegree);
simpleEOP, mu, defaultMass, defaultInterpolationDegree,
parsedUnitsBehavior);
}
/** Set up flag for ignoring tidal effects when interpolating EOP.
......@@ -127,7 +137,8 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
*/
public ParserBuilder withSimpleEOP(final boolean newSimpleEOP) {
return new ParserBuilder(getConventions(), getDataContext(), getMissionReferenceDate(),
newSimpleEOP, getMu(), getDefaultMass(), getDefaultInterpolationDegree());
newSimpleEOP, getMu(), getDefaultMass(),
getDefaultInterpolationDegree(), getParsedUnitsBehavior());
}
/** Check if tidal effects are ignored when interpolating EOP.
......@@ -144,7 +155,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
public ParserBuilder withMu(final double newMu) {
return new ParserBuilder(getConventions(), getDataContext(), getMissionReferenceDate(),
isSimpleEOP(), newMu, getDefaultMass(),
getDefaultInterpolationDegree());
getDefaultInterpolationDegree(), getParsedUnitsBehavior());
}
/** Get the gravitational coefficient.
......@@ -164,7 +175,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
public ParserBuilder withDefaultMass(final double newDefaultMass) {
return new ParserBuilder(getConventions(), getDataContext(), getMissionReferenceDate(),
isSimpleEOP(), getMu(), newDefaultMass,
getDefaultInterpolationDegree());
getDefaultInterpolationDegree(), getParsedUnitsBehavior());
}
/** Get the default mass.
......@@ -185,7 +196,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
public ParserBuilder withDefaultInterpolationDegree(final int newDefaultInterpolationDegree) {
return new ParserBuilder(getConventions(), getDataContext(), getMissionReferenceDate(),
isSimpleEOP(), getMu(), getDefaultMass(),
newDefaultInterpolationDegree);
newDefaultInterpolationDegree, getParsedUnitsBehavior());
}
/** Get the default interpolation degree.
......@@ -195,12 +206,29 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
return defaultInterpolationDegree;
}
/** Set up the behavior to adopt for handling parsed units.
* @param newParsedUnitsBehavior behavior to adopt for handling parsed units
* @return a new builder with updated configuration (the instance is not changed)
*/
public ParserBuilder withParsedUnitsBehavior(final ParsedUnitsBehavior newParsedUnitsBehavior) {
return new ParserBuilder(getConventions(), getDataContext(), getMissionReferenceDate(),
isSimpleEOP(), getMu(), getDefaultMass(),
getDefaultInterpolationDegree(), newParsedUnitsBehavior);
}
/** Get the behavior to adopt for handling parsed units.
* @return behavior to adopt for handling parsed units
*/
public ParsedUnitsBehavior getParsedUnitsBehavior() {
return parsedUnitsBehavior;
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.opm.OpmFile Orbit Parameters Messages}.
* @return a new parser
*/
public OpmParser buildOpmParser() {
return new OpmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
getMu(), getDefaultMass());
getMu(), getDefaultMass(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.opm.OmmFile Orbit Mean elements Messages}.
......@@ -208,7 +236,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
*/
public OmmParser buildOmmParser() {
return new OmmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
getMu(), getDefaultMass());
getMu(), getDefaultMass(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.oem.OemFile Orbit Ephemeris Messages}.
......@@ -216,21 +244,22 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
*/
public OemParser buildOemParser() {
return new OemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
getMu(), getDefaultInterpolationDegree());
getMu(), getDefaultInterpolationDegree(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.ocm.OcmFile Orbit Comprehensive Messages}.
* @return a new parser
*/
public OcmParser buildOcmParser() {
return new OcmParser(getConventions(), isSimpleEOP(), getDataContext(), getMu());
return new OcmParser(getConventions(), isSimpleEOP(), getDataContext(), getMu(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.apm.ApmFile Attitude Parameters Messages}.
* @return a new parser
*/
public ApmParser buildApmParser() {
return new ApmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate());
return new ApmParser(getConventions(), isSimpleEOP(), getDataContext(),
getMissionReferenceDate(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.aem.AemFile Attitude Ephemeris Messages}.
......@@ -238,7 +267,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
*/
public AemParser buildAemParser() {
return new AemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
getDefaultInterpolationDegree());
getDefaultInterpolationDegree(), getParsedUnitsBehavior());
}
/** Build a parser for {@link org.orekit.files.ccsds.ndm.tdm.TdmFile Tracking Data Messages}.
......@@ -247,7 +276,7 @@ public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
* @return a new parser
*/
public TdmParser buildTdmParser(final RangeUnitsConverter converter) {
return new TdmParser(getConventions(), isSimpleEOP(), getDataContext(), converter);
return new TdmParser(getConventions(), isSimpleEOP(), getDataContext(), getParsedUnitsBehavior(), converter);
}
}
......@@ -49,11 +49,11 @@ public class AdmMetadataWriter extends AbstractWriter {
generator.writeComments(metadata.getComments());
// object
generator.writeEntry(AdmMetadataKey.OBJECT_NAME.name(), metadata.getObjectName(), true);
generator.writeEntry(AdmMetadataKey.OBJECT_ID.name(), metadata.getObjectID(), true);
generator.writeEntry(AdmMetadataKey.OBJECT_NAME.name(), metadata.getObjectName(), null, true);
generator.writeEntry(AdmMetadataKey.OBJECT_ID.name(), metadata.getObjectID(), null, true);
// center
generator.writeEntry(AdmMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), true);
generator.writeEntry(AdmMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), null, true);
// time
generator.writeEntry(MetadataKey.TIME_SYSTEM.name(), metadata.getTimeSystem(), true);
......
......@@ -23,6 +23,7 @@ import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.ndm.NdmFile;
import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
import org.orekit.files.ccsds.utils.lexical.ParseToken;
import org.orekit.files.ccsds.utils.lexical.TokenType;
import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
......@@ -69,11 +70,12 @@ public abstract class AdmParser<T extends NdmFile<?, ?>, P extends AbstractMessa
* @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
*/
protected AdmParser(final String root, final String formatVersionKey,
final IERSConventions conventions, final boolean simpleEOP,
final DataContext dataContext, final AbsoluteDate missionReferenceDate) {
super(root, formatVersionKey, conventions, simpleEOP, dataContext);
protected AdmParser(final String root, final String formatVersionKey, final IERSConventions conventions,
final boolean simpleEOP, final DataContext dataContext,
final AbsoluteDate missionReferenceDate, final ParsedUnitsBehavior parsedUnitsBehavior) {
super(root, formatVersionKey, conventions, simpleEOP, dataContext, parsedUnitsBehavior);
this.missionReferenceDate = missionReferenceDate;
}
......
......@@ -19,6 +19,8 @@ package org.orekit.files.ccsds.ndm.adm;
import org.orekit.files.ccsds.utils.lexical.ParseToken;
import org.orekit.files.ccsds.utils.lexical.TokenType;
import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
import org.orekit.utils.units.Unit;
import org.orekit.utils.units.UnitsCache;
import org.xml.sax.Attributes;
/** Builder for rotation angles and rates.
......@@ -39,6 +41,15 @@ public class RotationXmlTokenBuilder implements XmlTokenBuilder {
/** Attribute name for units. */
private static final String UNITS = "units";
/** Cache for parsed units. */
private final UnitsCache cache;
/** Simple constructor.
*/
public RotationXmlTokenBuilder() {
this.cache = new UnitsCache();
}
/** {@inheritDoc} */
@Override
public ParseToken buildToken(final boolean startTag, final String qName,
......@@ -54,9 +65,11 @@ public class RotationXmlTokenBuilder implements XmlTokenBuilder {
// elaborate the token type
final TokenType type = (content == null) ? (startTag ? TokenType.START : TokenType.STOP) : TokenType.ENTRY;
// get units
final Unit units = cache.getUnits(attributes.getValue(UNITS));
// final build
return new ParseToken(type, name, content, attributes.getValue(UNITS),
lineNumber, fileName);
return new ParseToken(type, name, content, units, lineNumber, fileName);
}
......
......@@ -24,6 +24,7 @@ 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.ndm.ParsedUnitsBehavior;
import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
import org.orekit.files.ccsds.ndm.adm.AdmParser;
import org.orekit.files.ccsds.section.Header;
......@@ -95,11 +96,13 @@ public class AemParser extends AdmParser<AemFile, AemParser> implements Attitude
* @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
* (may be null if time system is absolute)
* @param defaultInterpolationDegree default interpolation degree
* @param parsedUnitsBehavior behavior to adopt for handling parsed units
*/
public AemParser(final IERSConventions conventions, final boolean simpleEOP,
final DataContext dataContext,
final AbsoluteDate missionReferenceDate, final int defaultInterpolationDegree) {
super(AemFile.ROOT, AemFile.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, missionReferenceDate);
final DataContext dataContext, final AbsoluteDate missionReferenceDate,
final int defaultInterpolationDegree, final ParsedUnitsBehavior parsedUnitsBehavior) {
super(AemFile.ROOT, AemFile.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
missionReferenceDate, parsedUnitsBehavior);
this.defaultInterpolationDegree = defaultInterpolationDegree;
}
......@@ -127,14 +130,14 @@ public class AemParser extends AdmParser<AemFile, AemParser> implements Attitude
reset(fileFormat, structureProcessor);
} else {
structureProcessor = new KvnStructureProcessingState(this);
reset(fileFormat, new HeaderProcessingState(getDataContext(), this));
reset(fileFormat, new HeaderProcessingState(this));
}
}
/** {@inheritDoc} */
@Override
public boolean prepareHeader() {
setFallback(new HeaderProcessingState(getDataContext(), this));
setFallback(new HeaderProcessingState(this));
return true;
}
......@@ -160,7 +163,8 @@ public class AemParser extends AdmParser<AemFile, AemParser> implements Attitude
}
metadata = new AemMetadata(defaultInterpolationDegree);
context = new ContextBinding(this::getConventions, this::isSimpleEOP,
this::getDataContext, this::getMissionReferenceDate,
this::getDataContext, this::getParsedUnitsBehavior,
this::getMissionReferenceDate,
metadata::getTimeSystem, () -> 0.0, () -> 1.0);
setFallback(this::processMetadataToken);
return true;
......
......@@ -24,6 +24,7 @@ import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.TimeConverter;
import org.orekit.files.ccsds.definitions.TimeSystem;
import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
import org.orekit.files.ccsds.ndm.adm.AttitudeType;
import org.orekit.files.ccsds.section.Header;
......@@ -259,7 +260,8 @@ public class AemWriter extends AbstractMessageWriter<Header, AemSegment, AemFile
final AbsoluteDate missionReferenceDate) {
super(AemFile.ROOT, AemFile.FORMAT_VERSION_KEY, CCSDS_AEM_VERS,
new ContextBinding(
() -> conventions, () -> true, () -> dataContext,
() -> conventions,
() -> true, () -> dataContext, () -> ParsedUnitsBehavior.STRICT_COMPLIANCE,
() -> missionReferenceDate, () -> TimeSystem.UTC,
() -> 0.0, () -> 1.0));
}
......@@ -292,6 +294,7 @@ public class AemWriter extends AbstractMessageWriter<Header, AemSegment, AemFile
setContext(new ContextBinding(oldContext::getConventions,
oldContext::isSimpleEOP,
oldContext::getDataContext,
oldContext::getParsedUnitsBehavior,
oldContext::getReferenceDate,
metadata::getTimeSystem,
oldContext::getClockCount,
......@@ -305,16 +308,16 @@ public class AemWriter extends AbstractMessageWriter<Header, AemSegment, AemFile
generator.writeComments(metadata.getComments());
// objects
generator.writeEntry(AdmMetadataKey.OBJECT_NAME.name(), metadata.getObjectName(), true);
generator.writeEntry(AdmMetadataKey.OBJECT_ID.name(), metadata.getObjectID(), true);
generator.writeEntry(AdmMetadataKey.OBJECT_NAME.name(), metadata.getObjectName(), null, true);
generator.writeEntry(AdmMetadataKey.OBJECT_ID.name(), metadata.getObjectID(), null, true);
if (metadata.getCenter() != null) {
generator.writeEntry(AdmMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), false);
generator.writeEntry(AdmMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), null, false);
}
// frames
generator.writeEntry(AemMetadataKey.REF_FRAME_A.name(), metadata.getEndpoints().getFrameA().getName(), true);
generator.writeEntry(AemMetadataKey.REF_FRAME_B.name(), metadata.getEndpoints().getFrameB().getName(), true);
generator.writeEntry(AemMetadataKey.ATTITUDE_DIR.name(), metadata.getEndpoints().isA2b() ? A_TO_B : B_TO_A, true);
generator.writeEntry(AemMetadataKey.REF_FRAME_A.name(), metadata.getEndpoints().getFrameA().getName(), null, true);
generator.writeEntry(AemMetadataKey.REF_FRAME_B.name(), metadata.getEndpoints().getFrameB().getName(), null, true);
generator.writeEntry(AemMetadataKey.ATTITUDE_DIR.name(), metadata.getEndpoints().isA2b() ? A_TO_B : B_TO_A, null, true);
// time
generator.writeEntry(MetadataKey.TIME_SYSTEM.name(), metadata.getTimeSystem(), true);
......@@ -329,11 +332,11 @@ public class AemWriter extends AbstractMessageWriter<Header, AemSegment, AemFile
// types
final AttitudeType attitudeType = metadata.getAttitudeType();
generator.writeEntry(AemMetadataKey.ATTITUDE_TYPE.name(), attitudeType.toString(), true);
generator.writeEntry(AemMetadataKey.ATTITUDE_TYPE.name(), attitudeType.toString(), null, true);
if (attitudeType == AttitudeType.QUATERNION ||
attitudeType == AttitudeType.QUATERNION_DERIVATIVE ||
attitudeType == AttitudeType.QUATERNION_RATE) {
generator.writeEntry(AemMetadataKey.QUATERNION_TYPE.name(), metadata.isFirst() ? FIRST : LAST, false);