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