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 }