AemMetadata.java
/* Copyright 2002-2023 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.aem;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.FrameFacade;
import org.orekit.files.ccsds.ndm.adm.AdmMetadata;
import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
import org.orekit.files.ccsds.ndm.adm.AttitudeType;
import org.orekit.time.AbsoluteDate;
/** This class gathers the meta-data present in the Attitude Data Message (ADM).
* @author Bryan Cazabonne
* @since 10.2
*/
public class AemMetadata extends AdmMetadata {
/** Endpoints (i.e. frames A, B and their relationship). */
private final AttitudeEndpoints endpoints;
/** Start of total time span covered by attitude data. */
private AbsoluteDate startTime;
/** End of total time span covered by attitude data. */
private AbsoluteDate stopTime;
/** Start of useable time span covered by attitude data. */
private AbsoluteDate useableStartTime;
/** End of useable time span covered by attitude data. */
private AbsoluteDate useableStopTime;
/** The format of the data lines in the message. */
private AttitudeType attitudeType;
/** The placement of the scalar portion of the quaternion (QC) in the attitude data. */
private Boolean isFirst;
/** The rotation sequence of the Euler angles. */
private RotationOrder eulerRotSeq;
/** The frame in which rates are specified (only for ADM V1). */
private Boolean rateFrameIsA;
/** The frame in which angular velocities are specified.
* @since 12.0
*/
private FrameFacade angvelFrame;
/** The interpolation method to be used. */
private String interpolationMethod;
/** The interpolation degree. */
private int interpolationDegree;
/** Simple constructor.
* @param defaultInterpolationDegree default interpolation degree
*/
public AemMetadata(final int defaultInterpolationDegree) {
endpoints = new AttitudeEndpoints();
interpolationDegree = defaultInterpolationDegree;
}
/** {@inheritDoc} */
@Override
public void validate(final double version) {
super.validate(version);
checkMandatoryEntriesExceptDatesAndExternalFrame(version);
endpoints.checkExternalFrame(AemMetadataKey.REF_FRAME_A, AemMetadataKey.REF_FRAME_B);
checkNotNull(startTime, AemMetadataKey.START_TIME.name());
checkNotNull(stopTime, AemMetadataKey.STOP_TIME.name());
if (version >= 2.0 && isFirst()) {
throw new OrekitException(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION,
AemMetadataKey.QUATERNION_TYPE, version);
}
}
/** Check is mandatory entries EXCEPT DATES AND EXTERNAL FRAME have been initialized.
* <p>
* Either frame A or frame B must be initialized with a {@link
* org.orekit.files.ccsds.definitions.SpacecraftBodyFrame spacecraft body frame}.
* </p>
* <p>
* This method should throw an exception if some mandatory entry is missing
* </p>
* @param version format version
*/
void checkMandatoryEntriesExceptDatesAndExternalFrame(final double version) {
super.validate(version);
endpoints.checkMandatoryEntriesExceptExternalFrame(version,
AemMetadataKey.REF_FRAME_A,
AemMetadataKey.REF_FRAME_B,
AemMetadataKey.ATTITUDE_DIR);
checkNotNull(attitudeType, AemMetadataKey.ATTITUDE_TYPE.name());
if (version < 2.0) {
if (attitudeType == AttitudeType.QUATERNION ||
attitudeType == AttitudeType.QUATERNION_DERIVATIVE) {
checkNotNull(isFirst, AemMetadataKey.QUATERNION_TYPE.name());
}
if (attitudeType == AttitudeType.EULER_ANGLE_DERIVATIVE) {
checkNotNull(rateFrameIsA, AemMetadataKey.RATE_FRAME.name());
}
} else {
if (attitudeType == AttitudeType.QUATERNION_ANGVEL) {
checkNotNull(angvelFrame, AemMetadataKey.ANGVEL_FRAME.name());
}
}
if (attitudeType == AttitudeType.EULER_ANGLE ||
attitudeType == AttitudeType.EULER_ANGLE_DERIVATIVE) {
checkNotNull(eulerRotSeq, AemMetadataKey.EULER_ROT_SEQ.name());
}
}
/** Get the endpoints (i.e. frames A, B and their relationship).
* @return endpoints
*/
public AttitudeEndpoints getEndpoints() {
return endpoints;
}
/** Check if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}.
* @return true if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
*/
public boolean rateFrameIsA() {
return rateFrameIsA == null ? false : rateFrameIsA;
}
/** Set the frame in which rates are specified.
* @param rateFrameIsA if true, rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
*/
public void setRateFrameIsA(final boolean rateFrameIsA) {
refuseFurtherComments();
this.rateFrameIsA = rateFrameIsA;
}
/** Set frame in which angular velocities are specified.
* @param angvelFrame frame in which angular velocities are specified
* @since 12.0
*/
public void setAngvelFrame(final FrameFacade angvelFrame) {
this.angvelFrame = angvelFrame;
}
/** Get frame in which angular velocities are specified.
* @return frame in which angular velocities are specified
* @since 12.0
*/
public FrameFacade getFrameAngvelFrame() {
return angvelFrame;
}
/** Check if rates are specified in spacecraft body frame.
* <p>
* {@link #validate(double) Mandatory entries} must have been
* initialized properly to non-null values before this method is called,
* otherwise {@code NullPointerException} will be thrown.
* </p>
* @return true if rates are specified in spacecraft body frame
*/
public boolean isSpacecraftBodyRate() {
return rateFrameIsA() ^ endpoints.getFrameA().asSpacecraftBodyFrame() == null;
}
/**
* Get the format of the data lines in the message.
*
* @return the format of the data lines in the message
*/
public AttitudeType getAttitudeType() {
return attitudeType;
}
/**
* Set the format of the data lines in the message.
* @param type format to be set
*/
public void setAttitudeType(final AttitudeType type) {
refuseFurtherComments();
this.attitudeType = type;
}
/**
* Get the flag for the placement of the quaternion QC in the attitude data.
*
* @return true if QC is the first element in the attitude data,
* false if not initialized
*/
public Boolean isFirst() {
return isFirst == null ? Boolean.FALSE : isFirst;
}
/**
* Set the flag for the placement of the quaternion QC in the attitude data.
* @param isFirst true if QC is the first element in the attitude data
*/
public void setIsFirst(final boolean isFirst) {
refuseFurtherComments();
this.isFirst = isFirst;
}
/**
* Get the rotation order of Euler angles.
* @return rotation order
*/
public RotationOrder getEulerRotSeq() {
return eulerRotSeq;
}
/**
* Set the rotation order for Euler angles.
* @param eulerRotSeq order to be set
*/
public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
refuseFurtherComments();
this.eulerRotSeq = eulerRotSeq;
}
/**
* Get start of total time span covered by attitude data.
* @return the start time
*/
public AbsoluteDate getStartTime() {
return startTime;
}
/**
* Set start of total time span covered by attitude data.
* @param startTime the time to be set
*/
public void setStartTime(final AbsoluteDate startTime) {
refuseFurtherComments();
this.startTime = startTime;
}
/**
* Get end of total time span covered by attitude data.
* @return the stop time
*/
public AbsoluteDate getStopTime() {
return stopTime;
}
/**
* Set end of total time span covered by attitude data.
* @param stopTime the time to be set
*/
public void setStopTime(final AbsoluteDate stopTime) {
refuseFurtherComments();
this.stopTime = stopTime;
}
/**
* Get start of useable time span covered by attitude data.
* @return the useable start time
*/
public AbsoluteDate getUseableStartTime() {
return useableStartTime;
}
/**
* Set start of useable time span covered by attitude data.
* @param useableStartTime the time to be set
*/
public void setUseableStartTime(final AbsoluteDate useableStartTime) {
refuseFurtherComments();
this.useableStartTime = useableStartTime;
}
/**
* Get end of useable time span covered by ephemerides data.
* @return the useable stop time
*/
public AbsoluteDate getUseableStopTime() {
return useableStopTime;
}
/**
* Set end of useable time span covered by ephemerides data.
* @param useableStopTime the time to be set
*/
public void setUseableStopTime(final AbsoluteDate useableStopTime) {
refuseFurtherComments();
this.useableStopTime = useableStopTime;
}
/**
* Get the start date of this ephemeris segment.
*
* @return ephemeris segment start date.
*/
public AbsoluteDate getStart() {
// usable start time overrides start time if it is set
final AbsoluteDate start = this.getUseableStartTime();
if (start != null) {
return start;
} else {
return this.getStartTime();
}
}
/**
* Get the end date of this ephemeris segment.
*
* @return ephemeris segment end date.
*/
public AbsoluteDate getStop() {
// useable stop time overrides stop time if it is set
final AbsoluteDate stop = this.getUseableStopTime();
if (stop != null) {
return stop;
} else {
return this.getStopTime();
}
}
/**
* Get the interpolation method to be used.
*
* @return the interpolation method
*/
public String getInterpolationMethod() {
return interpolationMethod;
}
/**
* Set the interpolation method to be used.
* @param interpolationMethod the interpolation method to be set
*/
public void setInterpolationMethod(final String interpolationMethod) {
refuseFurtherComments();
this.interpolationMethod = interpolationMethod;
}
/**
* Get the interpolation degree.
* @return the interpolation degree
*/
public int getInterpolationDegree() {
return interpolationDegree;
}
/**
* Set the interpolation degree.
* @param interpolationDegree the interpolation degree to be set
*/
public void setInterpolationDegree(final int interpolationDegree) {
refuseFurtherComments();
this.interpolationDegree = interpolationDegree;
}
/**
* Get the number of samples to use in interpolation.
*
* @return the number of points to use for interpolation.
*/
public int getInterpolationSamples() {
// From the standard it is not entirely clear how to interpret the degree.
return getInterpolationDegree() + 1;
}
/** Copy the instance, making sure mandatory fields have been initialized.
* @param version format version
* @return a new copy
*/
AemMetadata copy(final double version) {
checkMandatoryEntriesExceptDatesAndExternalFrame(version);
// allocate new instance
final AemMetadata copy = new AemMetadata(getInterpolationDegree());
// copy comments
for (String comment : getComments()) {
copy.addComment(comment);
}
// copy object
copy.setObjectName(getObjectName());
copy.setObjectID(getObjectID());
if (getCenter() != null) {
copy.setCenter(getCenter());
}
// copy frames (we may copy null references here)
copy.getEndpoints().setFrameA(getEndpoints().getFrameA());
copy.getEndpoints().setFrameB(getEndpoints().getFrameB());
copy.getEndpoints().setA2b(getEndpoints().isA2b());
copy.setRateFrameIsA(rateFrameIsA());
// copy time system only (ignore times themselves)
copy.setTimeSystem(getTimeSystem());
// copy attitude definitions
copy.setAttitudeType(getAttitudeType());
if (isFirst() != null) {
copy.setIsFirst(isFirst());
}
if (getEulerRotSeq() != null) {
copy.setEulerRotSeq(getEulerRotSeq());
}
// copy interpolation (degree has already been set up at construction)
if (getInterpolationMethod() != null) {
copy.setInterpolationMethod(getInterpolationMethod());
}
return copy;
}
}