AttitudeType.java
/* Copyright 2002-2022 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 java.util.regex.Pattern;
import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
import org.hipparchus.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.RotationConvention;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.orekit.attitudes.Attitude;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.Units;
import org.orekit.files.ccsds.utils.ContextBinding;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.AccurateFormatter;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
import org.orekit.utils.units.Unit;
/** Enumerate for ADM attitude type.
* @author Bryan Cazabonne
* @since 10.2
*/
public enum AttitudeType {
/** Quaternion. */
QUATERNION("QUATERNION", AngularDerivativesFilter.USE_R,
Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Initialize the array of attitude data
final double[] data = new double[4];
// Data index
final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};
// Fill the array
Rotation rotation = coordinates.getRotation();
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
data[quaternionIndex[0]] = rotation.getQ0();
data[quaternionIndex[1]] = rotation.getQ1();
data[quaternionIndex[2]] = rotation.getQ2();
data[quaternionIndex[3]] = rotation.getQ3();
// Convert units and format
return QUATERNION.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
Rotation rotation = isFirst ?
new Rotation(components[0], components[1], components[2], components[3], true) :
new Rotation(components[3], components[0], components[1], components[2], true);
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
// Return
return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
}
},
/** Quaternion and derivatives. */
QUATERNION_DERIVATIVE("QUATERNION/DERIVATIVE", AngularDerivativesFilter.USE_RR,
Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Initialize the array of attitude data
final double[] data = new double[8];
FieldRotation<UnivariateDerivative1> rotation = coordinates.toUnivariateDerivative1Rotation();
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
// Data index
final int[] quaternionIndex = isFirst ?
new int[] {0, 1, 2, 3, 4, 5, 6, 7} :
new int[] {3, 0, 1, 2, 7, 4, 5, 6};
// Fill the array
data[quaternionIndex[0]] = rotation.getQ0().getValue();
data[quaternionIndex[1]] = rotation.getQ1().getValue();
data[quaternionIndex[2]] = rotation.getQ2().getValue();
data[quaternionIndex[3]] = rotation.getQ3().getValue();
data[quaternionIndex[4]] = rotation.getQ0().getFirstDerivative();
data[quaternionIndex[5]] = rotation.getQ1().getFirstDerivative();
data[quaternionIndex[6]] = rotation.getQ2().getFirstDerivative();
data[quaternionIndex[7]] = rotation.getQ3().getFirstDerivative();
// Convert units and format
return QUATERNION_DERIVATIVE.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
FieldRotation<UnivariateDerivative1> rotation =
isFirst ?
new FieldRotation<>(new UnivariateDerivative1(components[0], components[4]),
new UnivariateDerivative1(components[1], components[5]),
new UnivariateDerivative1(components[2], components[6]),
new UnivariateDerivative1(components[3], components[7]),
true) :
new FieldRotation<>(new UnivariateDerivative1(components[3], components[7]),
new UnivariateDerivative1(components[0], components[4]),
new UnivariateDerivative1(components[1], components[5]),
new UnivariateDerivative1(components[2], components[6]),
true);
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
return new TimeStampedAngularCoordinates(date, rotation);
}
},
/** Quaternion and rotation rate. */
QUATERNION_RATE("QUATERNION/RATE", AngularDerivativesFilter.USE_RR,
Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Initialize the array of attitude data
final double[] data = new double[7];
// Data index
final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};
// Attitude
final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
final Vector3D rotationRate = QUATERNION_RATE.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());
// Fill the array
data[quaternionIndex[0]] = c.getRotation().getQ0();
data[quaternionIndex[1]] = c.getRotation().getQ1();
data[quaternionIndex[2]] = c.getRotation().getQ2();
data[quaternionIndex[3]] = c.getRotation().getQ3();
data[4] = rotationRate.getX();
data[5] = rotationRate.getY();
data[6] = rotationRate.getZ();
// Convert units and format
return QUATERNION_RATE.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
// Build the needed objects
final Rotation rotation = isFirst ?
new Rotation(components[0], components[1], components[2], components[3], true) :
new Rotation(components[3], components[0], components[1], components[2], true);
final Vector3D rotationRate = QUATERNION_RATE.orekitRate(isSpacecraftBodyRate,
new Vector3D(components[4],
components[5],
components[6]),
rotation);
// Return
final TimeStampedAngularCoordinates ac =
new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
return isExternal2SpacecraftBody ? ac : ac.revert();
}
},
/** Euler angles. */
EULER_ANGLE("EULER ANGLE", AngularDerivativesFilter.USE_R,
Unit.DEGREE, Unit.DEGREE, Unit.DEGREE) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Attitude
Rotation rotation = coordinates.getRotation();
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
final double[] data = rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);
// Convert units and format
return EULER_ANGLE.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
// Build the needed objects
Rotation rotation = new Rotation(eulerRotSequence, RotationConvention.FRAME_TRANSFORM,
components[0], components[1], components[2]);
if (!isExternal2SpacecraftBody) {
rotation = rotation.revert();
}
// Return
return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
}
},
/** Euler angles and rotation rate. */
EULER_ANGLE_RATE("EULER ANGLE/RATE", AngularDerivativesFilter.USE_RR,
Unit.DEGREE, Unit.DEGREE, Unit.DEGREE,
Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Initialize the array of attitude data
final double[] data = new double[6];
// Attitude
final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
final Vector3D rotationRate = EULER_ANGLE_RATE.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());
final double[] angles = c.getRotation().getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);
// Fill the array
data[0] = angles[0];
data[1] = angles[1];
data[2] = angles[2];
data[3] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA1());
data[4] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA2());
data[5] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA3());
// Convert units and format
return EULER_ANGLE_RATE.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
// Build the needed objects
final Rotation rotation = new Rotation(eulerRotSequence,
RotationConvention.FRAME_TRANSFORM,
components[0],
components[1],
components[2]);
final Vector3D rotationRate = EULER_ANGLE_RATE.orekitRate(isSpacecraftBodyRate,
new Vector3D(components[3], eulerRotSequence.getA1(),
components[4], eulerRotSequence.getA2(),
components[5], eulerRotSequence.getA3()),
rotation);
// Return
final TimeStampedAngularCoordinates ac =
new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
return isExternal2SpacecraftBody ? ac : ac.revert();
}
},
/** Spin.
* <p>
* CCSDS enforces that spin axis is +Z, so if {@link #createDataFields(boolean, boolean, RotationOrder, boolean,
* TimeStampedAngularCoordinates) createDataFields} is called with {@code coordinates} with {@link
* TimeStampedAngularCoordinates#getRotationRate() rotation rate} that is not along the Z axis, result is
* undefined.
* </p>
*/
SPIN("SPIN", AngularDerivativesFilter.USE_RR,
Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Initialize the array of attitude data
final double[] data = new double[4];
// Attitude
final double[] angles = coordinates.getRotation().getAngles(RotationOrder.ZYZ, RotationConvention.FRAME_TRANSFORM);
// Fill the array
data[0] = angles[0];
data[1] = 0.5 * FastMath.PI - angles[1];
data[2] = angles[2];
data[3] = coordinates.getRotationRate().getZ();
// Convert units and format
return SPIN.formatData(data);
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
// Build the needed objects
final Rotation rotation = new Rotation(RotationOrder.ZYZ,
RotationConvention.FRAME_TRANSFORM,
components[0],
0.5 * FastMath.PI - components[1],
components[2]);
final Vector3D rotationRate = new Vector3D(0, 0, components[3]);
// Return
return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
}
},
/** Spin and nutation. */
SPIN_NUTATION("SPIN/NUTATION", AngularDerivativesFilter.USE_RR,
Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S,
Unit.DEGREE, Unit.SECOND, Unit.DEGREE) {
/** {@inheritDoc} */
@Override
public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final TimeStampedAngularCoordinates coordinates) {
// Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
// are optional in CCSDS AEM format. Support for this attitude type is not implemented
// yet in Orekit.
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, name());
}
/** {@inheritDoc} */
@Override
public TimeStampedAngularCoordinates build(final boolean isFirst,
final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence,
final boolean isSpacecraftBodyRate,
final AbsoluteDate date,
final double... components) {
// Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
// are optional in CCSDS AEM format. Support for this attitude type is not implemented
// yet in Orekit.
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, name());
}
};
/** Pattern for normalizing attitude types. */
private static final Pattern TYPE_SEPARATORS = Pattern.compile("[ _/]+");
/** CCSDS name of the attitude type. */
private final String ccsdsName;
/** Derivatives filter. */
private final AngularDerivativesFilter filter;
/** Components units (used only for parsing). */
private final Unit[] units;
/** Private constructor.
* @param ccsdsName CCSDS name of the attitude type
* @param filter derivative filter
* @param units components units (used only for parsing)
*/
AttitudeType(final String ccsdsName, final AngularDerivativesFilter filter, final Unit... units) {
this.ccsdsName = ccsdsName;
this.filter = filter;
this.units = units.clone();
}
/** {@inheritDoc} */
@Override
public String toString() {
return ccsdsName;
}
/** Parse an attitude type.
* @param type unnormalized type name
* @return parsed type
*/
public static AttitudeType parseType(final String type) {
return AttitudeType.valueOf(TYPE_SEPARATORS.matcher(type).replaceAll("_"));
}
/**
* Get the attitude data fields corresponding to the attitude type.
* <p>
* This method returns the components in CCSDS units (i.e. degrees, degrees per seconds…).
* </p>
* @param isFirst if true the first quaternion component is the scalar component
* @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
* @param eulerRotSequence sequance of Euler angles
* @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
* @param attitude angular coordinates, using {@link Attitude Attitude} convention
* (i.e. from inertial frame to spacecraft frame)
* @return the attitude data in CCSDS units
*/
public abstract String[] createDataFields(boolean isFirst, boolean isExternal2SpacecraftBody,
RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
TimeStampedAngularCoordinates attitude);
/**
* Get the angular coordinates corresponding to the attitude data.
* <p>
* This method assumes the text fields are in CCSDS units and will convert to SI units.
* </p>
* @param isFirst if true the first quaternion component is the scalar component
* @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
* @param eulerRotSequence sequance of Euler angles
* @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
* @param context context binding
* @param fields raw data fields
* @return the angular coordinates, using {@link Attitude Attitude} convention
* (i.e. from inertial frame to spacecraft frame)
*/
public TimeStampedAngularCoordinates parse(final boolean isFirst, final boolean isExternal2SpacecraftBody,
final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
final ContextBinding context, final String[] fields) {
// parse the text fields
final AbsoluteDate date = context.getTimeSystem().getConverter(context).parse(fields[0]);
final double[] components = new double[fields.length - 1];
for (int i = 0; i < components.length; ++i) {
components[i] = units[i].toSI(Double.parseDouble(fields[i + 1]));
}
// build the coordinates
return build(isFirst, isExternal2SpacecraftBody, eulerRotSequence, isSpacecraftBodyRate,
date, components);
}
/** Get the angular coordinates corresponding to the attitude data.
* @param isFirst if true the first quaternion component is the scalar component
* @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
* @param eulerRotSequence sequance of Euler angles
* @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
* @param date entry date
* @param components entry components with CCSDS units (i.e. angles
* <em>must</em> still be in degrees here), semantic depends on attitude type
* @return the angular coordinates, using {@link Attitude Attitude} convention
* (i.e. from inertial frame to spacecraft frame)
*/
public abstract TimeStampedAngularCoordinates build(boolean isFirst, boolean isExternal2SpacecraftBody,
RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
AbsoluteDate date, double... components);
/**
* Get the angular derivative filter corresponding to the attitude data.
* @return the angular derivative filter corresponding to the attitude data
*/
public AngularDerivativesFilter getAngularDerivativesFilter() {
return filter;
}
private String[] formatData(final double[] data) {
final String[] fields = new String[data.length];
for (int i = 0; i < data.length; ++i) {
fields[i] = AccurateFormatter.format(units[i].fromSI(data[i]));
}
return fields;
}
/** Convert a rotation rate for Orekit convention to metadata convention.
* @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
* @param rate rotation rate from Orekit attitude
* @param rotation corresponding rotation
* @return rotation rate in metadata convention
*/
private Vector3D metadataRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
return isSpacecraftBodyRate ? rate : rotation.applyInverseTo(rate);
}
/** Convert a rotation rate for metadata convention to Orekit convention.
* @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
* @param rate rotation rate read from the data line
* @param rotation corresponding rotation
* @return rotation rate in Orekit convention (i.e. in spacecraft body local frame)
*/
private Vector3D orekitRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
return isSpacecraftBodyRate ? rate : rotation.applyTo(rate);
}
}