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.aem;
18
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.definitions.FrameFacade;
23 import org.orekit.files.ccsds.ndm.adm.AdmMetadata;
24 import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
25 import org.orekit.files.ccsds.ndm.adm.AttitudeType;
26 import org.orekit.time.AbsoluteDate;
27
28 /** This class gathers the meta-data present in the Attitude Data Message (ADM).
29 * @author Bryan Cazabonne
30 * @since 10.2
31 */
32 public class AemMetadata extends AdmMetadata {
33
34 /** Endpoints (i.e. frames A, B and their relationship). */
35 private final AttitudeEndpoints endpoints;
36
37 /** Start of total time span covered by attitude data. */
38 private AbsoluteDate startTime;
39
40 /** End of total time span covered by attitude data. */
41 private AbsoluteDate stopTime;
42
43 /** Start of useable time span covered by attitude data. */
44 private AbsoluteDate useableStartTime;
45
46 /** End of useable time span covered by attitude data. */
47 private AbsoluteDate useableStopTime;
48
49 /** The format of the data lines in the message. */
50 private AttitudeType attitudeType;
51
52 /** The placement of the scalar portion of the quaternion (QC) in the attitude data. */
53 private Boolean isFirst;
54
55 /** The rotation sequence of the Euler angles. */
56 private RotationOrder eulerRotSeq;
57
58 /** The frame in which rates are specified (only for ADM V1). */
59 private Boolean rateFrameIsA;
60
61 /** The frame in which angular velocities are specified.
62 * @since 12.0
63 */
64 private FrameFacade angvelFrame;
65
66 /** The interpolation method to be used. */
67 private String interpolationMethod;
68
69 /** The interpolation degree. */
70 private int interpolationDegree;
71
72 /** Simple constructor.
73 * @param defaultInterpolationDegree default interpolation degree
74 */
75 public AemMetadata(final int defaultInterpolationDegree) {
76 endpoints = new AttitudeEndpoints();
77 interpolationDegree = defaultInterpolationDegree;
78 }
79
80 /** {@inheritDoc} */
81 @Override
82 public void validate(final double version) {
83
84 super.validate(version);
85
86 checkMandatoryEntriesExceptDatesAndExternalFrame(version);
87 endpoints.checkExternalFrame(AemMetadataKey.REF_FRAME_A, AemMetadataKey.REF_FRAME_B);
88
89 checkNotNull(startTime, AemMetadataKey.START_TIME.name());
90 checkNotNull(stopTime, AemMetadataKey.STOP_TIME.name());
91
92 if (version >= 2.0 && isFirst()) {
93 throw new OrekitException(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION,
94 AemMetadataKey.QUATERNION_TYPE, version);
95 }
96
97 }
98
99 /** Check is mandatory entries EXCEPT DATES AND EXTERNAL FRAME have been initialized.
100 * <p>
101 * Either frame A or frame B must be initialized with a {@link
102 * org.orekit.files.ccsds.definitions.SpacecraftBodyFrame spacecraft body frame}.
103 * </p>
104 * <p>
105 * This method should throw an exception if some mandatory entry is missing
106 * </p>
107 * @param version format version
108 */
109 void checkMandatoryEntriesExceptDatesAndExternalFrame(final double version) {
110
111 super.validate(version);
112
113 endpoints.checkMandatoryEntriesExceptExternalFrame(version,
114 AemMetadataKey.REF_FRAME_A,
115 AemMetadataKey.REF_FRAME_B,
116 AemMetadataKey.ATTITUDE_DIR);
117
118 checkNotNull(attitudeType, AemMetadataKey.ATTITUDE_TYPE.name());
119 if (version < 2.0) {
120 if (attitudeType == AttitudeType.QUATERNION ||
121 attitudeType == AttitudeType.QUATERNION_DERIVATIVE) {
122 checkNotNull(isFirst, AemMetadataKey.QUATERNION_TYPE.name());
123 }
124 if (attitudeType == AttitudeType.EULER_ANGLE_DERIVATIVE) {
125 checkNotNull(rateFrameIsA, AemMetadataKey.RATE_FRAME.name());
126 }
127 } else {
128 if (attitudeType == AttitudeType.QUATERNION_ANGVEL) {
129 checkNotNull(angvelFrame, AemMetadataKey.ANGVEL_FRAME.name());
130 }
131 }
132
133 if (attitudeType == AttitudeType.EULER_ANGLE ||
134 attitudeType == AttitudeType.EULER_ANGLE_DERIVATIVE) {
135 checkNotNull(eulerRotSeq, AemMetadataKey.EULER_ROT_SEQ.name());
136 }
137
138 }
139
140 /** Get the endpoints (i.e. frames A, B and their relationship).
141 * @return endpoints
142 */
143 public AttitudeEndpoints getEndpoints() {
144 return endpoints;
145 }
146
147 /** Check if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}.
148 * @return true if rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
149 */
150 public boolean rateFrameIsA() {
151 return rateFrameIsA == null ? false : rateFrameIsA;
152 }
153
154 /** Set the frame in which rates are specified.
155 * @param rateFrameIsA if true, rates are specified in {@link AttitudeEndpoints#getFrameA() frame A}
156 */
157 public void setRateFrameIsA(final boolean rateFrameIsA) {
158 refuseFurtherComments();
159 this.rateFrameIsA = rateFrameIsA;
160 }
161
162 /** Set frame in which angular velocities are specified.
163 * @param angvelFrame frame in which angular velocities are specified
164 * @since 12.0
165 */
166 public void setAngvelFrame(final FrameFacade angvelFrame) {
167 this.angvelFrame = angvelFrame;
168 }
169
170 /** Get frame in which angular velocities are specified.
171 * @return frame in which angular velocities are specified
172 * @since 12.0
173 */
174 public FrameFacade getFrameAngvelFrame() {
175 return angvelFrame;
176 }
177
178 /** Check if rates are specified in spacecraft body frame.
179 * <p>
180 * {@link #validate(double) Mandatory entries} must have been
181 * initialized properly to non-null values before this method is called,
182 * otherwise {@code NullPointerException} will be thrown.
183 * </p>
184 * @return true if rates are specified in spacecraft body frame
185 */
186 public boolean isSpacecraftBodyRate() {
187 return rateFrameIsA() ^ endpoints.getFrameA().asSpacecraftBodyFrame() == null;
188 }
189
190 /**
191 * Get the format of the data lines in the message.
192 *
193 * @return the format of the data lines in the message
194 */
195 public AttitudeType getAttitudeType() {
196 return attitudeType;
197 }
198
199 /**
200 * Set the format of the data lines in the message.
201 * @param type format to be set
202 */
203 public void setAttitudeType(final AttitudeType type) {
204 refuseFurtherComments();
205 this.attitudeType = type;
206 }
207
208 /**
209 * Get the flag for the placement of the quaternion QC in the attitude data.
210 *
211 * @return true if QC is the first element in the attitude data,
212 * false if not initialized
213 */
214 public Boolean isFirst() {
215 return isFirst == null ? Boolean.FALSE : isFirst;
216 }
217
218 /**
219 * Set the flag for the placement of the quaternion QC in the attitude data.
220 * @param isFirst true if QC is the first element in the attitude data
221 */
222 public void setIsFirst(final boolean isFirst) {
223 refuseFurtherComments();
224 this.isFirst = isFirst;
225 }
226
227 /**
228 * Get the rotation order of Euler angles.
229 * @return rotation order
230 */
231 public RotationOrder getEulerRotSeq() {
232 return eulerRotSeq;
233 }
234
235 /**
236 * Set the rotation order for Euler angles.
237 * @param eulerRotSeq order to be set
238 */
239 public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
240 refuseFurtherComments();
241 this.eulerRotSeq = eulerRotSeq;
242 }
243
244 /**
245 * Get start of total time span covered by attitude data.
246 * @return the start time
247 */
248 public AbsoluteDate getStartTime() {
249 return startTime;
250 }
251
252 /**
253 * Set start of total time span covered by attitude data.
254 * @param startTime the time to be set
255 */
256 public void setStartTime(final AbsoluteDate startTime) {
257 refuseFurtherComments();
258 this.startTime = startTime;
259 }
260
261 /**
262 * Get end of total time span covered by attitude data.
263 * @return the stop time
264 */
265 public AbsoluteDate getStopTime() {
266 return stopTime;
267 }
268
269 /**
270 * Set end of total time span covered by attitude data.
271 * @param stopTime the time to be set
272 */
273 public void setStopTime(final AbsoluteDate stopTime) {
274 refuseFurtherComments();
275 this.stopTime = stopTime;
276 }
277
278 /**
279 * Get start of useable time span covered by attitude data.
280 * @return the useable start time
281 */
282 public AbsoluteDate getUseableStartTime() {
283 return useableStartTime;
284 }
285
286 /**
287 * Set start of useable time span covered by attitude data.
288 * @param useableStartTime the time to be set
289 */
290 public void setUseableStartTime(final AbsoluteDate useableStartTime) {
291 refuseFurtherComments();
292 this.useableStartTime = useableStartTime;
293 }
294
295 /**
296 * Get end of useable time span covered by ephemerides data.
297 * @return the useable stop time
298 */
299 public AbsoluteDate getUseableStopTime() {
300 return useableStopTime;
301 }
302
303 /**
304 * Set end of useable time span covered by ephemerides data.
305 * @param useableStopTime the time to be set
306 */
307 public void setUseableStopTime(final AbsoluteDate useableStopTime) {
308 refuseFurtherComments();
309 this.useableStopTime = useableStopTime;
310 }
311
312 /**
313 * Get the start date of this ephemeris segment.
314 *
315 * @return ephemeris segment start date.
316 */
317 public AbsoluteDate getStart() {
318 // usable start time overrides start time if it is set
319 final AbsoluteDate start = this.getUseableStartTime();
320 if (start != null) {
321 return start;
322 } else {
323 return this.getStartTime();
324 }
325 }
326
327 /**
328 * Get the end date of this ephemeris segment.
329 *
330 * @return ephemeris segment end date.
331 */
332 public AbsoluteDate getStop() {
333 // useable stop time overrides stop time if it is set
334 final AbsoluteDate stop = this.getUseableStopTime();
335 if (stop != null) {
336 return stop;
337 } else {
338 return this.getStopTime();
339 }
340 }
341
342 /**
343 * Get the interpolation method to be used.
344 *
345 * @return the interpolation method
346 */
347 public String getInterpolationMethod() {
348 return interpolationMethod;
349 }
350
351 /**
352 * Set the interpolation method to be used.
353 * @param interpolationMethod the interpolation method to be set
354 */
355 public void setInterpolationMethod(final String interpolationMethod) {
356 refuseFurtherComments();
357 this.interpolationMethod = interpolationMethod;
358 }
359
360 /**
361 * Get the interpolation degree.
362 * @return the interpolation degree
363 */
364 public int getInterpolationDegree() {
365 return interpolationDegree;
366 }
367
368 /**
369 * Set the interpolation degree.
370 * @param interpolationDegree the interpolation degree to be set
371 */
372 public void setInterpolationDegree(final int interpolationDegree) {
373 refuseFurtherComments();
374 this.interpolationDegree = interpolationDegree;
375 }
376
377 /**
378 * Get the number of samples to use in interpolation.
379 *
380 * @return the number of points to use for interpolation.
381 */
382 public int getInterpolationSamples() {
383 // From the standard it is not entirely clear how to interpret the degree.
384 return getInterpolationDegree() + 1;
385 }
386
387 /** Copy the instance, making sure mandatory fields have been initialized.
388 * @param version format version
389 * @return a new copy
390 */
391 AemMetadata copy(final double version) {
392
393 checkMandatoryEntriesExceptDatesAndExternalFrame(version);
394
395 // allocate new instance
396 final AemMetadata copy = new AemMetadata(getInterpolationDegree());
397
398 // copy comments
399 for (String comment : getComments()) {
400 copy.addComment(comment);
401 }
402
403 // copy object
404 copy.setObjectName(getObjectName());
405 copy.setObjectID(getObjectID());
406 if (getCenter() != null) {
407 copy.setCenter(getCenter());
408 }
409
410 // copy frames (we may copy null references here)
411 copy.getEndpoints().setFrameA(getEndpoints().getFrameA());
412 copy.getEndpoints().setFrameB(getEndpoints().getFrameB());
413 copy.getEndpoints().setA2b(getEndpoints().isA2b());
414 copy.setRateFrameIsA(rateFrameIsA());
415
416 // copy time system only (ignore times themselves)
417 copy.setTimeSystem(getTimeSystem());
418
419 // copy attitude definitions
420 copy.setAttitudeType(getAttitudeType());
421 if (isFirst() != null) {
422 copy.setIsFirst(isFirst());
423 }
424 if (getEulerRotSeq() != null) {
425 copy.setEulerRotSeq(getEulerRotSeq());
426 }
427
428 // copy interpolation (degree has already been set up at construction)
429 if (getInterpolationMethod() != null) {
430 copy.setInterpolationMethod(getInterpolationMethod());
431 }
432
433 return copy;
434
435 }
436
437 }