Euler.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.files.ccsds.ndm.adm.apm;

  18. import java.util.Arrays;

  19. import org.hipparchus.geometry.euclidean.threed.RotationOrder;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
  23. import org.orekit.files.ccsds.section.CommentsContainer;

  24. /**
  25.  * Container for {@link Euler Euler rotations} entries.
  26.  * <p>
  27.  * Beware that the Orekit getters and setters all rely on SI units. The parsers
  28.  * and writers take care of converting these SI units into CCSDS mandatory units.
  29.  * The {@link org.orekit.utils.units.Unit Unit} class provides useful
  30.  * {@link org.orekit.utils.units.Unit#fromSI(double) fromSi} and
  31.  * {@link org.orekit.utils.units.Unit#toSI(double) toSI} methods in case the callers
  32.  * already use CCSDS units instead of the API SI units. The general-purpose
  33.  * {@link org.orekit.utils.units.Unit Unit} class (without an 's') and the
  34.  * CCSDS-specific {@link org.orekit.files.ccsds.definitions.Units Units} class
  35.  * (with an 's') also provide some predefined units. These predefined units and the
  36.  * {@link org.orekit.utils.units.Unit#fromSI(double) fromSi} and
  37.  * {@link org.orekit.utils.units.Unit#toSI(double) toSI} conversion methods are indeed
  38.  * what the parsers and writers use for the conversions.
  39.  * </p>
  40.  * @author Bryan Cazabonne
  41.  * @since 10.2
  42.  */
  43. public class Euler extends CommentsContainer {

  44.     /** Key for angles in ADM V1.
  45.      * @since 12.0
  46.      */
  47.     private static final String KEY_ANGLES_V1 = "{X|Y|Z}_ANGLE";

  48.     /** Key for angles in ADM V2.
  49.      * @since 12.0
  50.      */
  51.     private static final String KEY_ANGLES_V2 = "ANGLE_{1|2|3}";

  52.     /** Key for rates in ADM V1.
  53.      * @since 12.0
  54.      */
  55.     private static final String KEY_RATES_V1 = "{X|Y|Z}_RATE";

  56.     /** Key for rates in ADM V2.
  57.      * @since 12.0
  58.      */
  59.     private static final String KEY_RATES_V2 = "ANGLE_{1|2|3}_DOT";

  60.     /** Endpoints (i.e. frames A, B and their relationship). */
  61.     private final AttitudeEndpoints endpoints;

  62.     /** Rotation order of the Euler angles. */
  63.     private RotationOrder eulerRotSeq;

  64.     /** The frame in which rates are specified. */
  65.     private Boolean rateFrameIsA;

  66.     /** Euler angles [rad]. */
  67.     private final double[] rotationAngles;

  68.     /** Rotation rate [rad/s]. */
  69.     private final double[] rotationRates;

  70.     /** Indicator for rotation angles. */
  71.     private boolean inRotationAngles;

  72.     /** Simple constructor.
  73.      */
  74.     public Euler() {
  75.         this.endpoints        = new AttitudeEndpoints();
  76.         this.rotationAngles   = new double[3];
  77.         this.rotationRates    = new double[3];
  78.         this.inRotationAngles = false;
  79.         Arrays.fill(rotationAngles, Double.NaN);
  80.         Arrays.fill(rotationRates,  Double.NaN);
  81.     }

  82.     /** {@inheritDoc} */
  83.     @Override
  84.     public void validate(final double version) {

  85.         super.validate(version);
  86.         if (version < 2.0) {
  87.             endpoints.checkMandatoryEntriesExceptExternalFrame(version,
  88.                                                                EulerKey.EULER_FRAME_A,
  89.                                                                EulerKey.EULER_FRAME_B,
  90.                                                                EulerKey.EULER_DIR);
  91.             endpoints.checkExternalFrame(EulerKey.EULER_FRAME_A, EulerKey.EULER_FRAME_B);
  92.         } else {
  93.             endpoints.checkMandatoryEntriesExceptExternalFrame(version,
  94.                                                                EulerKey.REF_FRAME_A,
  95.                                                                EulerKey.REF_FRAME_B,
  96.                                                                EulerKey.EULER_DIR);
  97.             endpoints.checkExternalFrame(EulerKey.REF_FRAME_A, EulerKey.REF_FRAME_B);
  98.         }
  99.         checkNotNull(eulerRotSeq, EulerKey.EULER_ROT_SEQ.name());

  100.         if (!hasAngles()) {
  101.             // if at least one angle is missing, all must be NaN (i.e. not initialized)
  102.             for (final double ra : rotationAngles) {
  103.                 if (!Double.isNaN(ra)) {
  104.                     throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY,
  105.                                               version < 2.0 ? KEY_ANGLES_V1 : KEY_ANGLES_V2);
  106.                 }
  107.             }
  108.         }

  109.         if (!hasRates()) {
  110.             // if at least one rate is missing, all must be NaN (i.e. not initialized)
  111.             for (final double rr : rotationRates) {
  112.                 if (!Double.isNaN(rr)) {
  113.                     throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY,
  114.                                               version < 2.0 ? KEY_RATES_V1 : KEY_RATES_V2);
  115.                 }
  116.             }
  117.         }

  118.         if (version < 2.0) {
  119.             // in ADM V1, either angles or rates must be specified
  120.             // (angles may be missing in the quaternion/Euler rate case)
  121.             if (!hasAngles() && !hasRates()) {
  122.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, KEY_ANGLES_V1 + "/" + KEY_RATES_V1);
  123.             }
  124.         } else {
  125.             // in ADM V2, angles are mandatory
  126.             if (!hasAngles()) {
  127.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, KEY_ANGLES_V2);
  128.             }
  129.         }

  130.     }

  131.     /** Get the endpoints (i.e. frames A, B and their relationship).
  132.      * @return endpoints
  133.      */
  134.     public AttitudeEndpoints getEndpoints() {
  135.         return endpoints;
  136.     }

  137.     /**
  138.      * Get the rotation order of Euler angles.
  139.      * @return rotation order
  140.      */
  141.     public RotationOrder getEulerRotSeq() {
  142.         return eulerRotSeq;
  143.     }

  144.     /**
  145.      * Set the rotation order for Euler angles.
  146.      * @param eulerRotSeq order to be set
  147.      */
  148.     public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
  149.         refuseFurtherComments();
  150.         this.eulerRotSeq = eulerRotSeq;
  151.     }

  152.     /** Check if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}.
  153.      * @return true if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
  154.      */
  155.     public boolean rateFrameIsA() {
  156.         return rateFrameIsA == null ? false : rateFrameIsA;
  157.     }

  158.     /** Set the frame in which rates are specified.
  159.      * @param rateFrameIsA if true, rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
  160.      */
  161.     public void setRateFrameIsA(final boolean rateFrameIsA) {
  162.         refuseFurtherComments();
  163.         this.rateFrameIsA = rateFrameIsA;
  164.     }

  165.     /** Check if rates are specified in spacecraft body frame.
  166.      * <p>
  167.      * {@link #validate(double) Mandatory entries} must have been
  168.      * initialized properly to non-null values before this method is called,
  169.      * otherwise {@code NullPointerException} will be thrown.
  170.      * </p>
  171.      * @return true if rates are specified in spacecraft body frame
  172.      */
  173.     public boolean isSpacecraftBodyRate() {
  174.         return rateFrameIsA() ^ endpoints.getFrameA().asSpacecraftBodyFrame() == null;
  175.     }

  176.     /**
  177.      * Get the coordinates of the Euler angles.
  178.      * @return rotation angles (rad)
  179.      */
  180.     public double[] getRotationAngles() {
  181.         return rotationAngles.clone();
  182.     }

  183.     /**
  184.      * Set the Euler angle about axis.
  185.      * @param axis rotation axis
  186.      * @param angle angle to set (rad)
  187.      */
  188.     public void setLabeledRotationAngle(final char axis, final double angle) {
  189.         if (eulerRotSeq != null) {
  190.             for (int i = 0; i < rotationAngles.length; ++i) {
  191.                 if (eulerRotSeq.name().charAt(i) == axis && Double.isNaN(rotationAngles[i])) {
  192.                     setIndexedRotationAngle(i, angle);
  193.                     return;
  194.                 }
  195.             }
  196.         }
  197.     }

  198.     /**
  199.      * Set the Euler angle about axis.
  200.      * @param axis rotation axis
  201.      * @param angle angle to set (rad)
  202.      * @since 12.0
  203.      */
  204.     public void setIndexedRotationAngle(final int axis, final double angle) {
  205.         refuseFurtherComments();
  206.         rotationAngles[axis] = angle;
  207.     }

  208.     /**
  209.      * Get the rates of the Euler angles.
  210.      * @return rotation rates (rad/s)
  211.      */
  212.     public double[] getRotationRates() {
  213.         return rotationRates.clone();
  214.     }

  215.     /**
  216.      * Set the rate of Euler angle about axis.
  217.      * @param axis rotation axis
  218.      * @param rate angle rate to set (rad/s)
  219.      */
  220.     public void setLabeledRotationRate(final char axis, final double rate) {
  221.         if (eulerRotSeq != null) {
  222.             for (int i = 0; i < rotationRates.length; ++i) {
  223.                 if (eulerRotSeq.name().charAt(i) == axis && Double.isNaN(rotationRates[i])) {
  224.                     setIndexedRotationRate(i, rate);
  225.                     return;
  226.                 }
  227.             }
  228.         }
  229.     }

  230.     /**
  231.      * Set the rate of Euler angle about axis.
  232.      * @param axis rotation axis
  233.      * @param rate angle rate to set (rad/s)
  234.      * @since 12.0
  235.      */
  236.     public void setIndexedRotationRate(final int axis, final double rate) {
  237.         refuseFurtherComments();
  238.         rotationRates[axis] = rate;
  239.     }

  240.     /** Check if we are in the rotationAngles part of XML files.
  241.      * @return true if we are in the rotationAngles part of XML files
  242.      */
  243.     boolean inRotationAngles() {
  244.         return inRotationAngles;
  245.     }

  246.     /** Set flag for rotation angle parsing.
  247.      * @param inRotationAngles if true, we are in the rotationAngles part of XML files
  248.      */
  249.     public void setInRotationAngles(final boolean inRotationAngles) {
  250.         refuseFurtherComments();
  251.         this.inRotationAngles = inRotationAngles;
  252.     }

  253.     /** Check if the logical block includes angles.
  254.      * <p>
  255.      * This can be false only for ADM V1, as angles are mandatory since ADM V2.
  256.      * </p>
  257.      * @return true if logical block includes angles
  258.      * @since 12.0
  259.      */
  260.     public boolean hasAngles() {
  261.         return !Double.isNaN(rotationAngles[0] + rotationAngles[1] + rotationAngles[2]);
  262.     }

  263.     /** Check if the logical block includes rates.
  264.      * @return true if logical block includes rates
  265.      */
  266.     public boolean hasRates() {
  267.         return !Double.isNaN(rotationRates[0] + rotationRates[1] + rotationRates[2]);
  268.     }

  269. }