AEMAttitudeType.java

/* Copyright 2002-2020 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;

import java.util.HashMap;
import java.util.Map;

import org.hipparchus.analysis.differentiation.DSFactory;
import org.hipparchus.analysis.differentiation.DerivativeStructure;
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.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.TimeStampedAngularCoordinates;

/** Enumerate for AEM attitude type.
 * @author Bryan Cazabonne
 * @since 10.2
 */
public enum AEMAttitudeType {

    /** Quaternion. */
    QUATERNION("QUATERNION") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // 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
            final Rotation rotation  = coordinates.getRotation();
            data[quaternionIndex[0]] = rotation.getQ0();
            data[quaternionIndex[1]] = rotation.getQ1();
            data[quaternionIndex[2]] = rotation.getQ2();
            data[quaternionIndex[3]] = rotation.getQ3();

            // Return
            return data;
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // Data index
            final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};

            // Build the needed objects
            final Rotation rotation = new Rotation(data[quaternionIndex[0]],
                                                   data[quaternionIndex[1]],
                                                   data[quaternionIndex[2]],
                                                   data[quaternionIndex[3]],
                                                   false);

            // Return
            return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
        }

    },

    /** Quaternion and derivatives. */
    QUATERNION_DERIVATIVE("QUATERNION DERIVATIVE") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // Initialize the array of attitude data
            final double[] data = new double[8];

            final FieldRotation<UnivariateDerivative1> fieldRotation = coordinates.toUnivariateDerivative1Rotation();
            // Quaternion components
            final double q0    = fieldRotation.getQ0().getValue();
            final double q1    = fieldRotation.getQ1().getValue();
            final double q2    = fieldRotation.getQ2().getValue();
            final double q3    = fieldRotation.getQ3().getValue();
            final double q0Dot = fieldRotation.getQ0().getFirstDerivative();
            final double q1Dot = fieldRotation.getQ1().getFirstDerivative();
            final double q2Dot = fieldRotation.getQ2().getFirstDerivative();
            final double q3Dot = fieldRotation.getQ3().getFirstDerivative();

            // 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]] = q0;
            data[quaternionIndex[1]] = q1;
            data[quaternionIndex[2]] = q2;
            data[quaternionIndex[3]] = q3;
            data[quaternionIndex[4]] = q0Dot;
            data[quaternionIndex[5]] = q1Dot;
            data[quaternionIndex[6]] = q2Dot;
            data[quaternionIndex[7]] = q3Dot;

            // Return
            return data;
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // 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};

            // Quaternion components
            final DSFactory factory = new DSFactory(1, 1);
            final DerivativeStructure q0DS = factory.build(data[quaternionIndex[0]], data[quaternionIndex[4]]);
            final DerivativeStructure q1DS = factory.build(data[quaternionIndex[1]], data[quaternionIndex[5]]);
            final DerivativeStructure q2DS = factory.build(data[quaternionIndex[2]], data[quaternionIndex[6]]);
            final DerivativeStructure q3DS = factory.build(data[quaternionIndex[3]], data[quaternionIndex[7]]);

            // Rotation
            final FieldRotation<DerivativeStructure> fieldRotation = new FieldRotation<>(q0DS, q1DS, q2DS, q3DS, false);

            // Return
            return new TimeStampedAngularCoordinates(date, fieldRotation);
        }

    },

    /** Quaternion and rotation rate. */
    QUATERNION_RATE("QUATERNION RATE") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // 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 Rotation rotation     = coordinates.getRotation();
            final Vector3D rotationRate = coordinates.getRotationRate();

            // Fill the array
            data[quaternionIndex[0]] = rotation.getQ0();
            data[quaternionIndex[1]] = rotation.getQ1();
            data[quaternionIndex[2]] = rotation.getQ2();
            data[quaternionIndex[3]] = rotation.getQ3();
            data[4] = FastMath.toDegrees(rotationRate.getX());
            data[5] = FastMath.toDegrees(rotationRate.getY());
            data[6] = FastMath.toDegrees(rotationRate.getZ());

            // Return
            return data;
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // Data index
            final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};

            // Quaternion components
            final double q0    = data[quaternionIndex[0]];
            final double q1    = data[quaternionIndex[1]];
            final double q2    = data[quaternionIndex[2]];
            final double q3    = data[quaternionIndex[3]];

            // Rotation rate in radians
            final double xRate = FastMath.toRadians(data[4]);
            final double yRate = FastMath.toRadians(data[5]);
            final double zRate = FastMath.toRadians(data[6]);

            // Build the needed objects
            final Rotation rotation     = new Rotation(q0, q1, q2, q3, false);
            final Vector3D rotationRate = new Vector3D(xRate, yRate, zRate);

            // Return
            return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
        }

    },

    /** Euler angles. */
    EULER_ANGLE("EULER ANGLE") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // Initialize the array of attitude data
            final double[] data = new double[3];

            // Attitude
            final Rotation rotation = coordinates.getRotation();
            final double[] angles   = rotation.getAngles(order, RotationConvention.FRAME_TRANSFORM);

            // Fill the array
            data[0] = FastMath.toDegrees(angles[0]);
            data[1] = FastMath.toDegrees(angles[1]);
            data[2] = FastMath.toDegrees(angles[2]);

            // Return
            return data;
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // Euler angles. They are given in degrees in CCSDS AEM files
            final double alpha1 = FastMath.toRadians(data[0]);
            final double alpha2 = FastMath.toRadians(data[1]);
            final double alpha3 = FastMath.toRadians(data[2]);

            // Build the needed objects
            final Rotation rotation = new Rotation(order, RotationConvention.FRAME_TRANSFORM,
                                                   alpha1, alpha2, alpha3);
            // Return
            return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
        }

    },

    /** Euler angles and rotation rate. */
    EULER_ANGLE_RATE("EULER ANGLE RATE") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // Initialize the array of attitude data
            final double[] data = new double[6];

            // Attitude
            final Rotation rotation     = coordinates.getRotation();
            final Vector3D rotationRate = coordinates.getRotationRate();
            final double[] angles       = rotation.getAngles(order, RotationConvention.FRAME_TRANSFORM);

            // Fill the array
            data[0] = FastMath.toDegrees(angles[0]);
            data[1] = FastMath.toDegrees(angles[1]);
            data[2] = FastMath.toDegrees(angles[2]);
            data[3] = FastMath.toDegrees(rotationRate.getX());
            data[4] = FastMath.toDegrees(rotationRate.getY());
            data[5] = FastMath.toDegrees(rotationRate.getZ());

            // Return
            return data;
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // Euler angles
            final double alpha1 = FastMath.toRadians(data[0]);
            final double alpha2 = FastMath.toRadians(data[1]);
            final double alpha3 = FastMath.toRadians(data[2]);
            // Rotation rate
            final double xRate = FastMath.toRadians(data[3]);
            final double yRate = FastMath.toRadians(data[4]);
            final double zRate = FastMath.toRadians(data[5]);

            // Build the needed objects
            final Rotation rotation     = new Rotation(order, RotationConvention.FRAME_TRANSFORM,
                                                   alpha1, alpha2, alpha3);
            final Vector3D rotationRate = new Vector3D(xRate, yRate, zRate);
            // Return
            return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
        }

    },

    /** Spin. */
    SPIN("SPIN") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // 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, getName());
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // 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, getName());
        }

    },

    /** Spin and nutation. */
    SPIN_NUTATION("SPIN NUTATION") {

        @Override
        public double[] getAttitudeData(final TimeStampedAngularCoordinates coordinates,
                                        final boolean isFirst, final RotationOrder order) {
            // 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, getName());
        }

        @Override
        public TimeStampedAngularCoordinates getAngularCoordinates(final AbsoluteDate date, final double[] data,
                                                                   final boolean isFirst, final RotationOrder order) {
            // 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, getName());
        }

    };

    /** Codes map. */
    private static final Map<String, AEMAttitudeType> CODES_MAP = new HashMap<String, AEMAttitudeType>();
    static {
        for (final AEMAttitudeType type : values()) {
            CODES_MAP.put(type.getName(), type);
        }
    }

    /** Name of the attitude type. */
    private final String name;

    /**
     * Constructor.
     * @param name name of the attitude type
     */
    AEMAttitudeType(final String name) {
        this.name = name;
    }

    /**
     * Get the name of the attitude type.
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * Get the attitude type corresponding to the given name.
     * @param name given name
     * @return attitude type
     */
    public static AEMAttitudeType getAttitudeType(final String name) {
        final AEMAttitudeType type = CODES_MAP.get(name);
        if (type == null) {
            // An exception is thrown if the attitude type is null
            throw new OrekitException(OrekitMessages.CCSDS_AEM_NULL_ATTITUDE_TYPE, name);
        }
        return type;
    }

    /**
     * Get the attitude data corresponding to the attitude type.
     * <p>
     * Note that, according to the CCSDS ADM documentation, angles values
     * are given in degrees.
     * </p>
     * @param attitude angular coordinates
     * @param isFirst true if QC is the first element in the attitude data
     * @param order rotation order of the Euler angles
     * @return the attitude data (see 4-4)
     */
    public abstract double[] getAttitudeData(TimeStampedAngularCoordinates attitude, boolean isFirst,
                                             RotationOrder order);

    /**
     * Get the angular coordinates corresponding to the attitude data.
     * <p>
     * Note that, according to the CCSDS ADM documentation, angles values
     * must be given in degrees.
     * </p>
     * @param date coordinates date
     * @param attitudeData attitude data
     * @param isFirst true if QC is the first element in the attitude data
     * @param order rotation order of the Euler angles
     * @return the angular coordinates
     */
    public abstract TimeStampedAngularCoordinates getAngularCoordinates(AbsoluteDate date, double[] attitudeData,
                                                                        boolean isFirst, RotationOrder order);

}