1   /* Copyright 2002-2024 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  
19  import java.util.Arrays;
20  
21  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
25  import org.orekit.files.ccsds.section.CommentsContainer;
26  
27  /**
28   * Container for {@link Euler Euler rotations} entries.
29   * @author Bryan Cazabonne
30   * @since 10.2
31   */
32  public class Euler extends CommentsContainer {
33  
34      /** Key for angles in ADM V1.
35       * @since 12.0
36       */
37      private static final String KEY_ANGLES_V1 = "{X|Y|Z}_ANGLE";
38  
39      /** Key for angles in ADM V2.
40       * @since 12.0
41       */
42      private static final String KEY_ANGLES_V2 = "ANGLE_{1|2|3}";
43  
44      /** Key for rates in ADM V1.
45       * @since 12.0
46       */
47      private static final String KEY_RATES_V1 = "{X|Y|Z}_RATE";
48  
49      /** Key for rates in ADM V2.
50       * @since 12.0
51       */
52      private static final String KEY_RATES_V2 = "ANGLE_{1|2|3}_DOT";
53  
54      /** Endpoints (i.e. frames A, B and their relationship). */
55      private final AttitudeEndpoints endpoints;
56  
57      /** Rotation order of the Euler angles. */
58      private RotationOrder eulerRotSeq;
59  
60      /** The frame in which rates are specified. */
61      private Boolean rateFrameIsA;
62  
63      /** Euler angles [rad]. */
64      private double[] rotationAngles;
65  
66      /** Rotation rate [rad/s]. */
67      private double[] rotationRates;
68  
69      /** Indicator for rotation angles. */
70      private boolean inRotationAngles;
71  
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  
83      /** {@inheritDoc} */
84      @Override
85      public void validate(final double version) {
86  
87          super.validate(version);
88          if (version < 2.0) {
89              endpoints.checkMandatoryEntriesExceptExternalFrame(version,
90                                                                 EulerKey.EULER_FRAME_A,
91                                                                 EulerKey.EULER_FRAME_B,
92                                                                 EulerKey.EULER_DIR);
93              endpoints.checkExternalFrame(EulerKey.EULER_FRAME_A, EulerKey.EULER_FRAME_B);
94          } else {
95              endpoints.checkMandatoryEntriesExceptExternalFrame(version,
96                                                                 EulerKey.REF_FRAME_A,
97                                                                 EulerKey.REF_FRAME_B,
98                                                                 EulerKey.EULER_DIR);
99              endpoints.checkExternalFrame(EulerKey.REF_FRAME_A, EulerKey.REF_FRAME_B);
100         }
101         checkNotNull(eulerRotSeq, EulerKey.EULER_ROT_SEQ.name());
102 
103         if (!hasAngles()) {
104             // if at least one angle is missing, all must be NaN (i.e. not initialized)
105             for (final double ra : rotationAngles) {
106                 if (!Double.isNaN(ra)) {
107                     throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY,
108                                               version < 2.0 ? KEY_ANGLES_V1 : KEY_ANGLES_V2);
109                 }
110             }
111         }
112 
113         if (!hasRates()) {
114             // if at least one rate is missing, all must be NaN (i.e. not initialized)
115             for (final double rr : rotationRates) {
116                 if (!Double.isNaN(rr)) {
117                     throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY,
118                                               version < 2.0 ? KEY_RATES_V1 : KEY_RATES_V2);
119                 }
120             }
121         }
122 
123         if (version < 2.0) {
124             // in ADM V1, either angles or rates must be specified
125             // (angles may be missing in the quaternion/Euler rate case)
126             if (!hasAngles() && !hasRates()) {
127                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, KEY_ANGLES_V1 + "/" + KEY_RATES_V1);
128             }
129         } else {
130             // in ADM V2, angles are mandatory
131             if (!hasAngles()) {
132                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, KEY_ANGLES_V2);
133             }
134         }
135 
136     }
137 
138     /** Get the endpoints (i.e. frames A, B and their relationship).
139      * @return endpoints
140      */
141     public AttitudeEndpoints getEndpoints() {
142         return endpoints;
143     }
144 
145     /**
146      * Get the rotation order of Euler angles.
147      * @return rotation order
148      */
149     public RotationOrder getEulerRotSeq() {
150         return eulerRotSeq;
151     }
152 
153     /**
154      * Set the rotation order for Euler angles.
155      * @param eulerRotSeq order to be set
156      */
157     public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
158         refuseFurtherComments();
159         this.eulerRotSeq = eulerRotSeq;
160     }
161 
162     /** Check if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}.
163      * @return true if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
164      */
165     public boolean rateFrameIsA() {
166         return rateFrameIsA == null ? false : rateFrameIsA;
167     }
168 
169     /** Set the frame in which rates are specified.
170      * @param rateFrameIsA if true, rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
171      */
172     public void setRateFrameIsA(final boolean rateFrameIsA) {
173         refuseFurtherComments();
174         this.rateFrameIsA = rateFrameIsA;
175     }
176 
177     /** Check if rates are specified in spacecraft body frame.
178      * <p>
179      * {@link #validate(double) Mandatory entries} must have been
180      * initialized properly to non-null values before this method is called,
181      * otherwise {@code NullPointerException} will be thrown.
182      * </p>
183      * @return true if rates are specified in spacecraft body frame
184      */
185     public boolean isSpacecraftBodyRate() {
186         return rateFrameIsA() ^ endpoints.getFrameA().asSpacecraftBodyFrame() == null;
187     }
188 
189     /**
190      * Get the coordinates of the Euler angles.
191      * @return rotation angles (rad)
192      */
193     public double[] getRotationAngles() {
194         return rotationAngles.clone();
195     }
196 
197     /**
198      * Set the Euler angle about axis.
199      * @param axis rotation axis
200      * @param angle angle to set (rad)
201      */
202     public void setLabeledRotationAngle(final char axis, final double angle) {
203         if (eulerRotSeq != null) {
204             for (int i = 0; i < rotationAngles.length; ++i) {
205                 if (eulerRotSeq.name().charAt(i) == axis && Double.isNaN(rotationAngles[i])) {
206                     setIndexedRotationAngle(i, angle);
207                     return;
208                 }
209             }
210         }
211     }
212 
213     /**
214      * Set the Euler angle about axis.
215      * @param axis rotation axis
216      * @param angle angle to set (rad)
217      * @since 12.0
218      */
219     public void setIndexedRotationAngle(final int axis, final double angle) {
220         refuseFurtherComments();
221         rotationAngles[axis] = angle;
222     }
223 
224     /**
225      * Get the rates of the Euler angles.
226      * @return rotation rates (rad/s)
227      */
228     public double[] getRotationRates() {
229         return rotationRates.clone();
230     }
231 
232     /**
233      * Set the rate of Euler angle about axis.
234      * @param axis rotation axis
235      * @param rate angle rate to set (rad/s)
236      */
237     public void setLabeledRotationRate(final char axis, final double rate) {
238         if (eulerRotSeq != null) {
239             for (int i = 0; i < rotationRates.length; ++i) {
240                 if (eulerRotSeq.name().charAt(i) == axis && Double.isNaN(rotationRates[i])) {
241                     setIndexedRotationRate(i, rate);
242                     return;
243                 }
244             }
245         }
246     }
247 
248     /**
249      * Set the rate of Euler angle about axis.
250      * @param axis rotation axis
251      * @param rate angle rate to set (rad/s)
252      * @since 12.0
253      */
254     public void setIndexedRotationRate(final int axis, final double rate) {
255         refuseFurtherComments();
256         rotationRates[axis] = rate;
257     }
258 
259     /** Check if we are in the rotationAngles part of XML files.
260      * @return true if we are in the rotationAngles part of XML files
261      */
262     boolean inRotationAngles() {
263         return inRotationAngles;
264     }
265 
266     /** Set flag for rotation angle parsing.
267      * @param inRotationAngles if true, we are in the rotationAngles part of XML files
268      */
269     public void setInRotationAngles(final boolean inRotationAngles) {
270         refuseFurtherComments();
271         this.inRotationAngles = inRotationAngles;
272     }
273 
274     /** Check if the logical block includes angles.
275      * <p>
276      * This can be false only for ADM V1, as angles are mandatory since ADM V2.
277      * </p>
278      * @return true if logical block includes angles
279      * @since 12.0
280      */
281     public boolean hasAngles() {
282         return !Double.isNaN(rotationAngles[0] + rotationAngles[1] + rotationAngles[2]);
283     }
284 
285     /** Check if the logical block includes rates.
286      * @return true if logical block includes rates
287      */
288     public boolean hasRates() {
289         return !Double.isNaN(rotationRates[0] + rotationRates[1] + rotationRates[2]);
290     }
291 
292 }