AttitudeEndpoints.java
/* Copyright 2002-2024 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;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeBuilder;
import org.orekit.attitudes.FieldAttitude;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.FrameFacade;
import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
import org.orekit.frames.Frame;
import org.orekit.utils.AngularCoordinates;
import org.orekit.utils.FieldAngularCoordinates;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.FieldPVCoordinatesProvider;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedAngularCoordinates;
import org.orekit.utils.TimeStampedFieldAngularCoordinates;
/** Endpoints for attitude definition.
* <p>
* This class provides a bridge between two different views of attitude definition.
* In both views, there is an external frame, based on either celestial body or orbit-relative
* and there is a spacecraft body frame.
* <ul>
* <li>CCSDS ADM view: frames are labeled as A and B but nothing tells which is which
* and attitude can be defined in any direction</li>
* <li>{@link Attitude Orekit attitude} view: attitude is always from external to
* spacecraft body</li>
* </ul>
* @author Luc Maisonobe
* @since 11.0
*/
public class AttitudeEndpoints implements AttitudeBuilder {
/** Constant for A → B diraction. */
public static final String A2B = "A2B";
/** Constant for A ← B direction. */
public static final String B2A = "B2A";
/** Frame A. */
private FrameFacade frameA;
/** Frame B. */
private FrameFacade frameB;
/** Flag for frames direction. */
private Boolean a2b;
/** Empty constructor.
* <p>
* This constructor is not strictly necessary, but it prevents spurious
* javadoc warnings with JDK 18 and later.
* </p>
* @since 12.0
*/
public AttitudeEndpoints() {
// nothing to do
}
/** Complain if a field is null.
* @param field field to check
* @param key key associated with the field
*/
private void checkNotNull(final Object field, final Enum<?> key) {
if (field == null) {
throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, key.name());
}
}
/** Check external frame is properly initialized.
* @param aKey key for frame A
* @param bKey key for frame B
*/
public void checkExternalFrame(final Enum<?> aKey, final Enum<?> bKey) {
checkNotNull(frameA, aKey);
checkNotNull(frameB, bKey);
if (frameA.asSpacecraftBodyFrame() != null && frameB.asSpacecraftBodyFrame() != null) {
// we cannot have two spacecraft body frames
throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
}
}
/** Check is mandatory entries <em>except external frame</em> 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
* @param aKey key for frame A
* @param bKey key for frame B
* @param dirKey key for direction
*/
public void checkMandatoryEntriesExceptExternalFrame(final double version,
final Enum<?> aKey, final Enum<?> bKey,
final Enum<?> dirKey) {
if (frameA == null) {
if (frameB == null || frameB.asSpacecraftBodyFrame() == null) {
throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, aKey.name());
}
} else if (frameA.asSpacecraftBodyFrame() == null) {
if (frameB == null) {
throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, bKey.name());
} else if (frameB.asSpacecraftBodyFrame() == null) {
// at least one of the frame must be a spacecraft body frame
throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
}
}
if (version < 2.0) {
// in ADM version 1, direction is mandatory
checkNotNull(a2b, dirKey);
} else if (!isA2b()) {
// in ADM version 2, direction is always A → B
throw new OrekitException(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION,
dirKey, version);
}
}
/** Set frame A.
* @param frameA frame A
*/
public void setFrameA(final FrameFacade frameA) {
this.frameA = frameA;
}
/** Get frame A.
* @return frame A
*/
public FrameFacade getFrameA() {
return frameA;
}
/** Set frame B.
* @param frameB frame B
*/
public void setFrameB(final FrameFacade frameB) {
this.frameB = frameB;
}
/** Get frame B.
* @return frame B
*/
public FrameFacade getFrameB() {
return frameB;
}
/** Set rotation direction.
* @param a2b if true, rotation is from {@link #getFrameA() frame A}
* to {@link #getFrameB() frame B}
*/
public void setA2b(final boolean a2b) {
this.a2b = a2b;
}
/** Check if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}.
* @return true if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}
*/
public boolean isA2b() {
return a2b == null ? true : a2b;
}
/** Get the external frame.
* @return external frame
*/
public FrameFacade getExternalFrame() {
return frameA.asSpacecraftBodyFrame() == null ? frameA : frameB;
}
/** Get the spacecraft body frame.
* @return spacecraft body frame
*/
public FrameFacade getSpacecraftBodyFrame() {
return frameA.asSpacecraftBodyFrame() == null ? frameB : frameA;
}
/** Check if attitude is from external frame to spacecraft body frame.
* <p>
* {@link #checkMandatoryEntriesExceptExternalFrame(double, Enum, Enum, Enum)
* 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 attitude is from external frame to spacecraft body frame
*/
public boolean isExternal2SpacecraftBody() {
return isA2b() ^ frameB.asSpacecraftBodyFrame() == null;
}
/** Check if a endpoint is compatible with another one.
* <p>
* Endpoins are compatible if they refer o the same frame names,
* in the same order and in the same direction.
* </p>
* @param other other endpoints to check against
* @return true if both endpoints are compatible with each other
*/
public boolean isCompatibleWith(final AttitudeEndpoints other) {
return frameA.getName().equals(other.frameA.getName()) &&
frameB.getName().equals(other.frameB.getName()) &&
a2b.equals(other.a2b);
}
/** {@inheritDoc} */
@Override
public Attitude build(final Frame frame, final PVCoordinatesProvider pvProv,
final TimeStampedAngularCoordinates rawAttitude) {
// attitude converted to Orekit conventions
final TimeStampedAngularCoordinates att =
isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();
final FrameFacade external = getExternalFrame();
final OrbitRelativeFrame orf = external.asOrbitRelativeFrame();
if (orf != null) {
// this is an orbit-relative attitude
if (orf.getLofType() == null) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
}
// construction of the local orbital frame, using PV from reference frame
final PVCoordinates pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
final AngularCoordinates frame2Lof =
orf.isQuasiInertial() ?
new AngularCoordinates(orf.getLofType().rotationFromInertial(pv), Vector3D.ZERO) :
orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();
// compose with APM
return new Attitude(frame, att.addOffset(frame2Lof));
} else {
// this is an absolute attitude
if (external.asFrame() == null) {
// unknown frame
throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
}
final Attitude attitude = new Attitude(external.asFrame(), att);
return frame == null ? attitude : attitude.withReferenceFrame(frame);
}
}
/** {@inheritDoc} */
@Override
public <T extends CalculusFieldElement<T>>
FieldAttitude<T> build(final Frame frame, final FieldPVCoordinatesProvider<T> pvProv,
final TimeStampedFieldAngularCoordinates<T> rawAttitude) {
// attitude converted to Orekit conventions
final TimeStampedFieldAngularCoordinates<T> att =
isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();
final FrameFacade external = getExternalFrame();
final OrbitRelativeFrame orf = external.asOrbitRelativeFrame();
if (orf != null) {
// this is an orbit-relative attitude
if (orf.getLofType() == null) {
throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
}
// construction of the local orbital frame, using PV from reference frame
final FieldPVCoordinates<T> pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
final Field<T> field = rawAttitude.getDate().getField();
final FieldAngularCoordinates<T> referenceToLof =
orf.isQuasiInertial() ?
new FieldAngularCoordinates<>(orf.getLofType().rotationFromInertial(field, pv),
FieldVector3D.getZero(field)) :
orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();
// compose with APM
return new FieldAttitude<>(frame, att.addOffset(referenceToLof));
} else {
// this is an absolute attitude
if (external.asFrame() == null) {
// this should never happen as all CelestialBodyFrame have an Orekit mapping
throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
}
final FieldAttitude<T> attitude = new FieldAttitude<>(external.asFrame(), att);
return frame == null ? attitude : attitude.withReferenceFrame(frame);
}
}
/** {@inheritDoc} */
@Override
public String toString() {
return frameA.getName() + (isA2b() ? " → " : " ← ") + frameB.getName();
}
}