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
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22
23 import org.hipparchus.complex.Quaternion;
24 import org.orekit.attitudes.Attitude;
25 import org.orekit.errors.OrekitException;
26 import org.orekit.errors.OrekitMessages;
27 import org.orekit.files.ccsds.ndm.adm.AttitudeType;
28 import org.orekit.files.ccsds.section.CommentsContainer;
29 import org.orekit.files.ccsds.section.Data;
30 import org.orekit.frames.Frame;
31 import org.orekit.time.AbsoluteDate;
32 import org.orekit.utils.PVCoordinatesProvider;
33 import org.orekit.utils.TimeStampedAngularCoordinates;
34
35 /**
36 * Container for Attitude Parameter Message data.
37 * @author Bryan Cazabonne
38 * @since 10.2
39 */
40 public class ApmData implements Data {
41
42 /** General comments block. */
43 private final CommentsContainer commentsBlock;
44
45 /** Epoch of the data. */
46 private final AbsoluteDate epoch;
47
48 /** Quaternion block. */
49 private final ApmQuaternion quaternionBlock;
50
51 /** Euler angles block. */
52 private final Euler eulerBlock;
53
54 /** Angular velocity block.
55 * @since 12.0
56 */
57 private final AngularVelocity angularVelocityBlock;
58
59 /** Spin-stabilized block. */
60 private final SpinStabilized spinStabilizedBlock;
61
62 /** Inertia block. */
63 private final Inertia inertia;
64
65 /** Maneuvers. */
66 private final List<Maneuver> maneuvers;
67
68 /** Simple constructor.
69 * @param commentsBlock general comments block
70 * @param epoch epoch of the data
71 * @param quaternionBlock quaternion logical block (may be null in ADM V2 or later)
72 * @param eulerBlock Euler angles logicial block (may be null)
73 * @param angularVelocityBlock angular velocity block (may be null)
74 * @param spinStabilizedBlock spin-stabilized logical block (may be null)
75 * @param inertia inertia logical block (may be null)
76 */
77 public ApmData(final CommentsContainer commentsBlock,
78 final AbsoluteDate epoch,
79 final ApmQuaternion quaternionBlock,
80 final Euler eulerBlock,
81 final AngularVelocity angularVelocityBlock,
82 final SpinStabilized spinStabilizedBlock,
83 final Inertia inertia) {
84 this.commentsBlock = commentsBlock;
85 this.epoch = epoch;
86 this.quaternionBlock = quaternionBlock;
87 this.eulerBlock = eulerBlock;
88 this.angularVelocityBlock = angularVelocityBlock;
89 this.spinStabilizedBlock = spinStabilizedBlock;
90 this.inertia = inertia;
91 this.maneuvers = new ArrayList<>();
92 }
93
94 /** {@inheritDoc} */
95 @Override
96 public void validate(final double version) {
97
98 if (version < 2.0) {
99 // quaternion block is mandatory in ADM V1
100 if (quaternionBlock == null) {
101 // generate a dummy entry just for triggering the exception
102 new ApmQuaternion().validate(version);
103 }
104 } else {
105 // at least one logical block is mandatory in ADM V2
106 if (quaternionBlock == null && eulerBlock == null && angularVelocityBlock == null &&
107 spinStabilizedBlock == null && inertia == null) {
108 throw new OrekitException(OrekitMessages.CCSDS_INCOMPLETE_DATA);
109 }
110 }
111
112 if (quaternionBlock != null) {
113 quaternionBlock.validate(version);
114 }
115 if (eulerBlock != null) {
116 eulerBlock.validate(version);
117 }
118 if (angularVelocityBlock != null) {
119 angularVelocityBlock.validate(version);
120 }
121 if (spinStabilizedBlock != null) {
122 spinStabilizedBlock.validate(version);
123 }
124 if (inertia != null) {
125 inertia.validate(version);
126 }
127 for (final Maneuver maneuver : maneuvers) {
128 maneuver.validate(version);
129 }
130
131 }
132
133 /** Get the comments.
134 * @return comments
135 */
136 public List<String> getComments() {
137 return commentsBlock.getComments();
138 }
139
140 /**
141 * Get the epoch of the data.
142 * @return epoch the epoch
143 * @since 12.0
144 */
145 public AbsoluteDate getEpoch() {
146 return epoch;
147 }
148
149 /** Get the quaternion logical block.
150 * @return quaternion block
151 */
152 public ApmQuaternion getQuaternionBlock() {
153 return quaternionBlock;
154 }
155
156 /** Get the Euler angles logical block.
157 * @return Euler angles block (may be null)
158 */
159 public Euler getEulerBlock() {
160 return eulerBlock;
161 }
162
163 /** Get the angular velocity logical block.
164 * @return angular velocity block (may be null)
165 * @since 12.0
166 */
167 public AngularVelocity getAngularVelocityBlock() {
168 return angularVelocityBlock;
169 }
170
171 /** Get the spin-stabilized logical block.
172 * @return spin-stabilized block (may be null)
173 */
174 public SpinStabilized getSpinStabilizedBlock() {
175 return spinStabilizedBlock;
176 }
177
178 /** Get the inertia logical block.
179 * @return inertia block (may be null)
180 */
181 public Inertia getInertiaBlock() {
182 return inertia;
183 }
184
185 /**
186 * Get the number of maneuvers present in the APM.
187 * @return the number of maneuvers
188 */
189 public int getNbManeuvers() {
190 return maneuvers.size();
191 }
192
193 /**
194 * Get a list of all maneuvers.
195 * @return unmodifiable list of all maneuvers.
196 */
197 public List<Maneuver> getManeuvers() {
198 return Collections.unmodifiableList(maneuvers);
199 }
200
201 /**
202 * Get a maneuver.
203 * @param index maneuver index, counting from 0
204 * @return maneuver
205 */
206 public Maneuver getManeuver(final int index) {
207 return maneuvers.get(index);
208 }
209
210 /**
211 * Add a maneuver.
212 * @param maneuver maneuver to be set
213 */
214 public void addManeuver(final Maneuver maneuver) {
215 maneuvers.add(maneuver);
216 }
217
218 /**
219 * Get boolean testing whether the APM contains at least one maneuver.
220 * @return true if APM contains at least one maneuver
221 * false otherwise
222 */
223 public boolean hasManeuvers() {
224 return !maneuvers.isEmpty();
225 }
226
227 /** Get the attitude.
228 * @param frame reference frame with respect to which attitude must be defined,
229 * (may be null if attitude is <em>not</em> orbit-relative and one wants
230 * attitude in the same frame as used in the attitude message)
231 * @param pvProvider provider for spacecraft position and velocity
232 * (may be null if attitude is <em>not</em> orbit-relative)
233 * @return attitude
234 * @since 12.0
235 */
236 public Attitude getAttitude(final Frame frame, final PVCoordinatesProvider pvProvider) {
237
238 if (quaternionBlock != null) {
239 // we have a quaternion
240 final Quaternion q = quaternionBlock.getQuaternion();
241
242 final TimeStampedAngularCoordinates tac;
243 if (quaternionBlock.hasRates()) {
244 // quaternion logical block includes everything we need
245 final Quaternion qDot = quaternionBlock.getQuaternionDot();
246 tac = AttitudeType.QUATERNION_DERIVATIVE.build(true, quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
247 null, true, epoch,
248 q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
249 qDot.getQ0(), qDot.getQ1(), qDot.getQ2(), qDot.getQ3());
250 } else if (angularVelocityBlock != null) {
251 // get derivatives from the angular velocity logical block
252 tac = AttitudeType.QUATERNION_ANGVEL.build(true,
253 quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
254 null, true, epoch,
255 q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
256 angularVelocityBlock.getAngVelX(),
257 angularVelocityBlock.getAngVelY(),
258 angularVelocityBlock.getAngVelZ());
259 } else if (eulerBlock != null && eulerBlock.hasRates()) {
260 // get derivatives from the Euler logical block
261 final double[] rates = eulerBlock.getRotationRates();
262 if (eulerBlock.hasAngles()) {
263 // the Euler block has everything we need
264 final double[] angles = eulerBlock.getRotationAngles();
265 tac = AttitudeType.EULER_ANGLE_DERIVATIVE.build(true,
266 eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
267 eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
268 angles[0], angles[1], angles[2],
269 rates[0], rates[1], rates[2]);
270 } else {
271 // the Euler block has only the rates (we are certainly using an ADM V1 message)
272 // we need to rebuild the rotation from the quaternion
273 tac = AttitudeType.QUATERNION_EULER_RATES.build(true,
274 eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
275 eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
276 q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3(),
277 rates[0], rates[1], rates[2]);
278 }
279
280 } else {
281 // we rely only on the quaternion logical block, despite it doesn't include rates
282 tac = AttitudeType.QUATERNION.build(true, quaternionBlock.getEndpoints().isExternal2SpacecraftBody(),
283 null, true, epoch,
284 q.getQ0(), q.getQ1(), q.getQ2(), q.getQ3());
285 }
286
287 // build the attitude
288 return quaternionBlock.getEndpoints().build(frame, pvProvider, tac);
289
290 } else if (eulerBlock != null) {
291 // we have Euler angles
292 final double[] angles = eulerBlock.getRotationAngles();
293
294 final TimeStampedAngularCoordinates tac;
295 if (eulerBlock.hasRates()) {
296 // the Euler block has everything we need
297 final double[] rates = eulerBlock.getRotationRates();
298 tac = AttitudeType.EULER_ANGLE_DERIVATIVE.build(true,
299 eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
300 eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
301 angles[0], angles[1], angles[2],
302 rates[0], rates[1], rates[2]);
303 } else if (angularVelocityBlock != null) {
304 // get derivatives from the angular velocity logical block
305 tac = AttitudeType.EULER_ANGLE_ANGVEL.build(true,
306 eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
307 eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
308 angles[0], angles[1], angles[2],
309 angularVelocityBlock.getAngVelX(),
310 angularVelocityBlock.getAngVelY(),
311 angularVelocityBlock.getAngVelZ());
312 } else {
313 // we rely only on the Euler logical block, despite it doesn't include rates
314 tac = AttitudeType.EULER_ANGLE.build(true,
315 eulerBlock.getEndpoints().isExternal2SpacecraftBody(),
316 eulerBlock.getEulerRotSeq(), eulerBlock.isSpacecraftBodyRate(), epoch,
317 angles[0], angles[1], angles[2]);
318 }
319
320 // build the attitude
321 return eulerBlock.getEndpoints().build(frame, pvProvider, tac);
322
323 } else if (spinStabilizedBlock != null) {
324 // we have a spin block
325
326 final TimeStampedAngularCoordinates tac;
327 if (spinStabilizedBlock.hasNutation()) {
328 // we rely only on nutation
329 tac = AttitudeType.SPIN_NUTATION.build(true, true, null, true, epoch,
330 spinStabilizedBlock.getSpinAlpha(),
331 spinStabilizedBlock.getSpinDelta(),
332 spinStabilizedBlock.getSpinAngle(),
333 spinStabilizedBlock.getSpinAngleVel(),
334 spinStabilizedBlock.getNutation(),
335 spinStabilizedBlock.getNutationPeriod(),
336 spinStabilizedBlock.getNutationPhase());
337 } else if (spinStabilizedBlock.hasMomentum()) {
338 // we rely only on momentum
339 tac = AttitudeType.SPIN_NUTATION_MOMENTUM.build(true, true, null, true, epoch,
340 spinStabilizedBlock.getSpinAlpha(),
341 spinStabilizedBlock.getSpinDelta(),
342 spinStabilizedBlock.getSpinAngle(),
343 spinStabilizedBlock.getSpinAngleVel(),
344 spinStabilizedBlock.getMomentumAlpha(),
345 spinStabilizedBlock.getMomentumDelta(),
346 spinStabilizedBlock.getNutationVel());
347 } else {
348 // we rely only on the spin logical block, despite it doesn't include rates
349 tac = AttitudeType.SPIN.build(true, true, null, true, epoch,
350 spinStabilizedBlock.getSpinAlpha(),
351 spinStabilizedBlock.getSpinDelta(),
352 spinStabilizedBlock.getSpinAngle(),
353 spinStabilizedBlock.getSpinAngleVel());
354 }
355
356 // build the attitude
357 return spinStabilizedBlock.getEndpoints().build(frame, pvProvider, tac);
358
359 } else {
360 throw new OrekitException(OrekitMessages.CCSDS_INCOMPLETE_DATA);
361 }
362
363 }
364
365 }