ApmData.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.apm;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hipparchus.complex.Quaternion;
import org.orekit.attitudes.Attitude;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.ndm.adm.AttitudeType;
import org.orekit.files.ccsds.section.CommentsContainer;
import org.orekit.files.ccsds.section.Data;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedAngularCoordinates;

/**
 * Container for Attitude Parameter Message data.
 * @author Bryan Cazabonne
 * @since 10.2
 */
public class ApmData implements Data {

    /** General comments block. */
    private final CommentsContainer commentsBlock;

    /** Epoch of the data. */
    private final AbsoluteDate epoch;

    /** Quaternion block. */
    private final ApmQuaternion quaternionBlock;

    /** Euler angles block. */
    private final Euler eulerBlock;

    /** Angular velocity block.
     * @since 12.0
     */
    private final AngularVelocity angularVelocityBlock;

    /** Spin-stabilized block. */
    private final SpinStabilized spinStabilizedBlock;

    /** Inertia block. */
    private final Inertia inertia;

    /** Maneuvers. */
    private final List<Maneuver> maneuvers;

    /** Simple constructor.
     * @param commentsBlock general comments block
     * @param epoch epoch of the data
     * @param quaternionBlock quaternion logical block (may be null in ADM V2 or later)
     * @param eulerBlock Euler angles logicial block (may be null)
     * @param angularVelocityBlock angular velocity block (may be null)
     * @param spinStabilizedBlock spin-stabilized logical block (may be null)
     * @param inertia inertia logical block (may be null)
     */
    public ApmData(final CommentsContainer commentsBlock,
                   final AbsoluteDate epoch,
                   final ApmQuaternion quaternionBlock,
                   final Euler eulerBlock,
                   final AngularVelocity angularVelocityBlock,
                   final SpinStabilized spinStabilizedBlock,
                   final Inertia inertia) {
        this.commentsBlock        = commentsBlock;
        this.epoch                = epoch;
        this.quaternionBlock      = quaternionBlock;
        this.eulerBlock           = eulerBlock;
        this.angularVelocityBlock = angularVelocityBlock;
        this.spinStabilizedBlock  = spinStabilizedBlock;
        this.inertia              = inertia;
        this.maneuvers            = new ArrayList<>();
    }

    /** {@inheritDoc} */
    @Override
    public void validate(final double version) {

        if (version < 2.0) {
            // quaternion block is mandatory in ADM V1
            if (quaternionBlock == null) {
                // generate a dummy entry just for triggering the exception
                new ApmQuaternion().validate(version);
            }
        } else {
            // at least one logical block is mandatory in ADM V2
            if (quaternionBlock == null && eulerBlock == null && angularVelocityBlock == null &&
                spinStabilizedBlock == null && inertia == null) {
                throw new OrekitException(OrekitMessages.CCSDS_INCOMPLETE_DATA);
            }
        }

        if (quaternionBlock != null) {
            quaternionBlock.validate(version);
        }
        if (eulerBlock != null) {
            eulerBlock.validate(version);
        }
        if (angularVelocityBlock != null) {
            angularVelocityBlock.validate(version);
        }
        if (spinStabilizedBlock != null) {
            spinStabilizedBlock.validate(version);
        }
        if (inertia != null) {
            inertia.validate(version);
        }
        for (final Maneuver maneuver : maneuvers) {
            maneuver.validate(version);
        }

    }

    /** Get the comments.
     * @return comments
     */
    public List<String> getComments() {
        return commentsBlock.getComments();
    }

    /**
     * Get the epoch of the data.
     * @return epoch the epoch
     * @since 12.0
     */
    public AbsoluteDate getEpoch() {
        return epoch;
    }

    /** Get the quaternion logical block.
     * @return quaternion block
     */
    public ApmQuaternion getQuaternionBlock() {
        return quaternionBlock;
    }

    /** Get the Euler angles logical block.
     * @return Euler angles block (may be null)
     */
    public Euler getEulerBlock() {
        return eulerBlock;
    }

    /** Get the angular velocity logical block.
     * @return angular velocity block (may be null)
     * @since 12.0
     */
    public AngularVelocity getAngularVelocityBlock() {
        return angularVelocityBlock;
    }

    /** Get the spin-stabilized logical block.
     * @return spin-stabilized block (may be null)
     */
    public SpinStabilized getSpinStabilizedBlock() {
        return spinStabilizedBlock;
    }

    /** Get the inertia logical block.
     * @return inertia block (may be null)
     */
    public Inertia getInertiaBlock() {
        return inertia;
    }

    /**
     * Get the number of maneuvers present in the APM.
     * @return the number of maneuvers
     */
    public int getNbManeuvers() {
        return maneuvers.size();
    }

    /**
     * Get a list of all maneuvers.
     * @return unmodifiable list of all maneuvers.
     */
    public List<Maneuver> getManeuvers() {
        return Collections.unmodifiableList(maneuvers);
    }

    /**
     * Get a maneuver.
     * @param index maneuver index, counting from 0
     * @return maneuver
     */
    public Maneuver getManeuver(final int index) {
        return maneuvers.get(index);
    }

    /**
     * Add a maneuver.
     * @param maneuver maneuver to be set
     */
    public void addManeuver(final Maneuver maneuver) {
        maneuvers.add(maneuver);
    }

    /**
     * Get boolean testing whether the APM contains at least one maneuver.
     * @return true if APM contains at least one maneuver
     *         false otherwise
     */
    public boolean hasManeuvers() {
        return !maneuvers.isEmpty();
    }

    /** Get the attitude.
     * @param frame reference frame with respect to which attitude must be defined,
     * (may be null if attitude is <em>not</em> orbit-relative and one wants
     * attitude in the same frame as used in the attitude message)
     * @param pvProvider provider for spacecraft position and velocity
     * (may be null if attitude is <em>not</em> orbit-relative)
     * @return attitude
     * @since 12.0
     */
    public Attitude getAttitude(final Frame frame, final PVCoordinatesProvider pvProvider) {

        if (quaternionBlock != null) {
            // we have a quaternion
            final Quaternion q = quaternionBlock.getQuaternion();

            final TimeStampedAngularCoordinates tac;
            if (quaternionBlock.hasRates()) {
                // quaternion logical block includes everything we need
                final Quaternion qDot = quaternionBlock.getQuaternionDot();
                tac = AttitudeType.QUATERNION_DERIVATIVE.build(true, quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                               null, true, epoch,
                                                               q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
                                                               qDot.getQ0(), qDot.getQ1(), qDot.getQ2(), qDot.getQ3());
            } else if (angularVelocityBlock != null) {
                // get derivatives from the angular velocity logical block
                tac = AttitudeType.QUATERNION_ANGVEL.build(true,
                                                           quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                           null, true, epoch,
                                                           q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
                                                           angularVelocityBlock.getAngVelX(),
                                                           angularVelocityBlock.getAngVelY(),
                                                           angularVelocityBlock.getAngVelZ());
            } else if (eulerBlock != null && eulerBlock.hasRates()) {
                // get derivatives from the Euler logical block
                final double[] rates = eulerBlock.getRotationRates();
                if (eulerBlock.hasAngles()) {
                    // the Euler block has everything we need
                    final double[] angles = eulerBlock.getRotationAngles();
                    tac = AttitudeType.EULER_ANGLE_DERIVATIVE.build(true,
                                                                    eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                                    eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
                                                                    angles[0], angles[1], angles[2],
                                                                    rates[0], rates[1], rates[2]);
                } else {
                    // the Euler block has only the rates (we are certainly using an ADM V1 message)
                    // we need to rebuild the rotation from the quaternion
                    tac = AttitudeType.QUATERNION_EULER_RATES.build(true,
                                                                    eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                                    eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
                                                                    q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
                                                                    rates[0], rates[1], rates[2]);
                }

            } else {
                // we rely only on the quaternion logical block, despite it doesn't include rates
                tac = AttitudeType.QUATERNION.build(true, quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                    null, true, epoch,
                                                    q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3());
            }

            // build the attitude
            return quaternionBlock.getEndpoints().build(frame, pvProvider, tac);

        } else if (eulerBlock != null) {
            // we have Euler angles
            final double[] angles = eulerBlock.getRotationAngles();

            final TimeStampedAngularCoordinates tac;
            if (eulerBlock.hasRates()) {
                // the Euler block has everything we need
                final double[] rates = eulerBlock.getRotationRates();
                tac = AttitudeType.EULER_ANGLE_DERIVATIVE.build(true,
                                                                eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                                eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
                                                                angles[0], angles[1], angles[2],
                                                                rates[0], rates[1], rates[2]);
            } else if (angularVelocityBlock != null) {
                // get derivatives from the angular velocity logical block
                tac = AttitudeType.EULER_ANGLE_ANGVEL.build(true,
                                                            eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                            eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
                                                            angles[0], angles[1], angles[2],
                                                            angularVelocityBlock.getAngVelX(),
                                                            angularVelocityBlock.getAngVelY(),
                                                            angularVelocityBlock.getAngVelZ());
            } else {
                // we rely only on the Euler logical block, despite it doesn't include rates
                tac = AttitudeType.EULER_ANGLE.build(true,
                                                     eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
                                                     eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
                                                     angles[0], angles[1], angles[2]);
            }

            // build the attitude
            return eulerBlock.getEndpoints().build(frame, pvProvider, tac);

        } else if (spinStabilizedBlock != null) {
            // we have a spin block

            final TimeStampedAngularCoordinates tac;
            if (spinStabilizedBlock.hasNutation()) {
                // we rely only on nutation
                tac = AttitudeType.SPIN_NUTATION.build(true, true, null, true, epoch,
                                                       spinStabilizedBlock.getSpinAlpha(),
                                                       spinStabilizedBlock.getSpinDelta(),
                                                       spinStabilizedBlock.getSpinAngle(),
                                                       spinStabilizedBlock.getSpinAngleVel(),
                                                       spinStabilizedBlock.getNutation(),
                                                       spinStabilizedBlock.getNutationPeriod(),
                                                       spinStabilizedBlock.getNutationPhase());
            } else if (spinStabilizedBlock.hasMomentum()) {
                // we rely only on momentum
                tac = AttitudeType.SPIN_NUTATION_MOMENTUM.build(true, true, null, true, epoch,
                                                                spinStabilizedBlock.getSpinAlpha(),
                                                                spinStabilizedBlock.getSpinDelta(),
                                                                spinStabilizedBlock.getSpinAngle(),
                                                                spinStabilizedBlock.getSpinAngleVel(),
                                                                spinStabilizedBlock.getMomentumAlpha(),
                                                                spinStabilizedBlock.getMomentumDelta(),
                                                                spinStabilizedBlock.getNutationVel());
            } else {
                // we rely only on the spin logical block, despite it doesn't include rates
                tac = AttitudeType.SPIN.build(true, true, null, true, epoch,
                                              spinStabilizedBlock.getSpinAlpha(),
                                              spinStabilizedBlock.getSpinDelta(),
                                              spinStabilizedBlock.getSpinAngle(),
                                              spinStabilizedBlock.getSpinAngleVel());
            }

            // build the attitude
            return spinStabilizedBlock.getEndpoints().build(frame, pvProvider, tac);

        } else {
            throw new OrekitException(OrekitMessages.CCSDS_INCOMPLETE_DATA);
        }

    }

}