1 /* Copyright 2002-2022 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.propagation;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.stream.Stream;
25
26 import org.hipparchus.analysis.interpolation.HermiteInterpolator;
27 import org.hipparchus.exception.LocalizedCoreFormats;
28 import org.hipparchus.exception.MathIllegalStateException;
29 import org.hipparchus.geometry.euclidean.threed.Rotation;
30 import org.hipparchus.geometry.euclidean.threed.Vector3D;
31 import org.hipparchus.util.FastMath;
32 import org.orekit.attitudes.Attitude;
33 import org.orekit.attitudes.LofOffset;
34 import org.orekit.errors.OrekitException;
35 import org.orekit.errors.OrekitIllegalArgumentException;
36 import org.orekit.errors.OrekitIllegalStateException;
37 import org.orekit.errors.OrekitMessages;
38 import org.orekit.frames.Frame;
39 import org.orekit.frames.LOFType;
40 import org.orekit.frames.Transform;
41 import org.orekit.orbits.Orbit;
42 import org.orekit.time.AbsoluteDate;
43 import org.orekit.time.TimeInterpolable;
44 import org.orekit.time.TimeShiftable;
45 import org.orekit.time.TimeStamped;
46 import org.orekit.utils.AbsolutePVCoordinates;
47 import org.orekit.utils.DoubleArrayDictionary;
48 import org.orekit.utils.TimeStampedAngularCoordinates;
49 import org.orekit.utils.TimeStampedPVCoordinates;
50
51 /** This class is the representation of a complete state holding orbit, attitude
52 * and mass information at a given date.
53 *
54 * <p>It contains an {@link Orbit orbital state} at a current
55 * {@link AbsoluteDate} both handled by an {@link Orbit}, plus the current
56 * mass and attitude. Orbit and state are guaranteed to be consistent in terms
57 * of date and reference frame. The spacecraft state may also contain additional
58 * states, which are simply named double arrays which can hold any user-defined
59 * data.
60 * </p>
61 * <p>
62 * The state can be slightly shifted to close dates. This shift is based on
63 * a simple Keplerian model for orbit, a linear extrapolation for attitude
64 * taking the spin rate into account and no mass change. It is <em>not</em>
65 * intended as a replacement for proper orbit and attitude propagation but
66 * should be sufficient for either small time shifts or coarse accuracy.
67 * </p>
68 * <p>
69 * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
70 * </p>
71 * @see org.orekit.propagation.numerical.NumericalPropagator
72 * @author Fabien Maussion
73 * @author Véronique Pommier-Maurussane
74 * @author Luc Maisonobe
75 */
76 public class SpacecraftState
77 implements TimeStamped, TimeShiftable<SpacecraftState>, TimeInterpolable<SpacecraftState>, Serializable {
78
79 /** Serializable UID. */
80 private static final long serialVersionUID = 20211119L;
81
82 /** Default mass. */
83 private static final double DEFAULT_MASS = 1000.0;
84
85 /**
86 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
87 * corresponds to sub-mm accuracy at LEO orbital velocities.
88 */
89 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
90
91 /** Orbital state. */
92 private final Orbit orbit;
93
94 /** Trajectory state, when it is not an orbit. */
95 private final AbsolutePVCoordinates absPva;
96
97 /** Attitude. */
98 private final Attitude attitude;
99
100 /** Current mass (kg). */
101 private final double mass;
102
103 /** Additional states. */
104 private final DoubleArrayDictionary additional;
105
106 /** Additional states derivatives.
107 * @since 11.1
108 */
109 private final DoubleArrayDictionary additionalDot;
110
111 /** Build a spacecraft state from orbit only.
112 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
113 * @param orbit the orbit
114 */
115 public SpacecraftState(final Orbit orbit) {
116 this(orbit,
117 new LofOffset(orbit.getFrame(),
118 LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
119 DEFAULT_MASS, (DoubleArrayDictionary) null);
120 }
121
122 /** Build a spacecraft state from orbit and attitude.
123 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
124 * @param orbit the orbit
125 * @param attitude attitude
126 * @exception IllegalArgumentException if orbit and attitude dates
127 * or frames are not equal
128 */
129 public SpacecraftState(final Orbit orbit, final Attitude attitude)
130 throws IllegalArgumentException {
131 this(orbit, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null);
132 }
133
134 /** Create a new instance from orbit and mass.
135 * <p>Attitude law is set to an unspecified default attitude.</p>
136 * @param orbit the orbit
137 * @param mass the mass (kg)
138 */
139 public SpacecraftState(final Orbit orbit, final double mass) {
140 this(orbit,
141 new LofOffset(orbit.getFrame(),
142 LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
143 mass, (DoubleArrayDictionary) null);
144 }
145
146 /** Build a spacecraft state from orbit, attitude and mass.
147 * @param orbit the orbit
148 * @param attitude attitude
149 * @param mass the mass (kg)
150 * @exception IllegalArgumentException if orbit and attitude dates
151 * or frames are not equal
152 */
153 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
154 throws IllegalArgumentException {
155 this(orbit, attitude, mass, (DoubleArrayDictionary) null);
156 }
157
158 /** Build a spacecraft state from orbit and additional states.
159 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
160 * @param orbit the orbit
161 * @param additional additional states
162 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, DoubleArrayDictionary)}
163 */
164 @Deprecated
165 public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional) {
166 this(orbit, new DoubleArrayDictionary(additional));
167 }
168
169 /** Build a spacecraft state from orbit and additional states.
170 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
171 * @param orbit the orbit
172 * @param additional additional states
173 * @since 11.1
174 */
175 public SpacecraftState(final Orbit orbit, final DoubleArrayDictionary additional) {
176 this(orbit,
177 new LofOffset(orbit.getFrame(),
178 LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
179 DEFAULT_MASS, additional);
180 }
181
182 /** Build a spacecraft state from orbit, attitude and additional states.
183 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
184 * @param orbit the orbit
185 * @param attitude attitude
186 * @param additional additional states
187 * @exception IllegalArgumentException if orbit and attitude dates
188 * or frames are not equal
189 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, Attitude, DoubleArrayDictionary)}
190 */
191 @Deprecated
192 public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
193 throws IllegalArgumentException {
194 this(orbit, attitude, new DoubleArrayDictionary(additional));
195 }
196
197 /** Build a spacecraft state from orbit, attitude and additional states.
198 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
199 * @param orbit the orbit
200 * @param attitude attitude
201 * @param additional additional states
202 * @exception IllegalArgumentException if orbit and attitude dates
203 * or frames are not equal
204 * @since 11.1
205 */
206 public SpacecraftState(final Orbit orbit, final Attitude attitude, final DoubleArrayDictionary additional)
207 throws IllegalArgumentException {
208 this(orbit, attitude, DEFAULT_MASS, additional);
209 }
210
211 /** Create a new instance from orbit, mass and additional states.
212 * <p>Attitude law is set to an unspecified default attitude.</p>
213 * @param orbit the orbit
214 * @param mass the mass (kg)
215 * @param additional additional states
216 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, double, DoubleArrayDictionary)}
217 */
218 @Deprecated
219 public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional) {
220 this(orbit, mass, new DoubleArrayDictionary(additional));
221 }
222
223 /** Create a new instance from orbit, mass and additional states.
224 * <p>Attitude law is set to an unspecified default attitude.</p>
225 * @param orbit the orbit
226 * @param mass the mass (kg)
227 * @param additional additional states
228 * @since 11.1
229 */
230 public SpacecraftState(final Orbit orbit, final double mass, final DoubleArrayDictionary additional) {
231 this(orbit,
232 new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
233 mass, additional);
234 }
235
236 /** Build a spacecraft state from orbit, attitude, mass and additional states.
237 * @param orbit the orbit
238 * @param attitude attitude
239 * @param mass the mass (kg)
240 * @param additional additional states (may be null if no additional states are available)
241 * @exception IllegalArgumentException if orbit and attitude dates
242 * or frames are not equal
243 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, Attitude, double, DoubleArrayDictionary)}
244 */
245 @Deprecated
246 public SpacecraftState(final Orbit orbit, final Attitude attitude,
247 final double mass, final Map<String, double[]> additional)
248 throws IllegalArgumentException {
249 this(orbit, attitude, mass, new DoubleArrayDictionary(additional));
250 }
251
252 /** Build a spacecraft state from orbit, attitude, mass and additional states.
253 * @param orbit the orbit
254 * @param attitude attitude
255 * @param mass the mass (kg)
256 * @param additional additional states (may be null if no additional states are available)
257 * @exception IllegalArgumentException if orbit and attitude dates
258 * or frames are not equal
259 * @since 11.1
260 */
261 public SpacecraftState(final Orbit orbit, final Attitude attitude,
262 final double mass, final DoubleArrayDictionary additional)
263 throws IllegalArgumentException {
264 this(orbit, attitude, mass, additional, null);
265 }
266
267 /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
268 * @param orbit the orbit
269 * @param attitude attitude
270 * @param mass the mass (kg)
271 * @param additional additional states (may be null if no additional states are available)
272 * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
273 * @exception IllegalArgumentException if orbit and attitude dates
274 * or frames are not equal
275 * @since 11.1
276 */
277 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
278 final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
279 throws IllegalArgumentException {
280 checkConsistency(orbit, attitude);
281 this.orbit = orbit;
282 this.absPva = null;
283 this.attitude = attitude;
284 this.mass = mass;
285 if (additional == null) {
286 this.additional = new DoubleArrayDictionary();
287 } else {
288 this.additional = additional;
289 }
290 if (additionalDot == null) {
291 this.additionalDot = new DoubleArrayDictionary();
292 } else {
293 this.additionalDot = new DoubleArrayDictionary(additionalDot);
294 }
295 }
296
297
298
299 /** Build a spacecraft state from position-velocity-acceleration only.
300 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
301 * @param absPva position-velocity-acceleration
302 */
303 public SpacecraftState(final AbsolutePVCoordinates absPva) {
304 this(absPva,
305 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
306 DEFAULT_MASS, (DoubleArrayDictionary) null);
307 }
308
309 /** Build a spacecraft state from position-velocity-acceleration and attitude.
310 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
311 * @param absPva position-velocity-acceleration
312 * @param attitude attitude
313 * @exception IllegalArgumentException if orbit and attitude dates
314 * or frames are not equal
315 */
316 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
317 throws IllegalArgumentException {
318 this(absPva, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null);
319 }
320
321 /** Create a new instance from position-velocity-acceleration and mass.
322 * <p>Attitude law is set to an unspecified default attitude.</p>
323 * @param absPva position-velocity-acceleration
324 * @param mass the mass (kg)
325 */
326 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
327 this(absPva,
328 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
329 mass, (DoubleArrayDictionary) null);
330 }
331
332 /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
333 * @param absPva position-velocity-acceleration
334 * @param attitude attitude
335 * @param mass the mass (kg)
336 * @exception IllegalArgumentException if orbit and attitude dates
337 * or frames are not equal
338 */
339 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
340 throws IllegalArgumentException {
341 this(absPva, attitude, mass, (DoubleArrayDictionary) null);
342 }
343
344 /** Build a spacecraft state from position-velocity-acceleration and additional states.
345 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
346 * @param absPva position-velocity-acceleration
347 * @param additional additional states
348 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, DoubleArrayDictionary)}
349 */
350 @Deprecated
351 public SpacecraftState(final AbsolutePVCoordinates absPva, final Map<String, double[]> additional) {
352 this(absPva, new DoubleArrayDictionary(additional));
353 }
354
355 /** Build a spacecraft state from position-velocity-acceleration and additional states.
356 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
357 * @param absPva position-velocity-acceleration
358 * @param additional additional states
359 * @since 11.1
360 */
361 public SpacecraftState(final AbsolutePVCoordinates absPva, final DoubleArrayDictionary additional) {
362 this(absPva,
363 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
364 DEFAULT_MASS, additional);
365 }
366
367 /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states.
368 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
369 * @param absPva position-velocity-acceleration
370 * @param attitude attitude
371 * @param additional additional states
372 * @exception IllegalArgumentException if orbit and attitude dates
373 * or frames are not equal
374 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, Attitude, DoubleArrayDictionary)}
375 */
376 @Deprecated
377 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final Map<String, double[]> additional)
378 throws IllegalArgumentException {
379 this(absPva, attitude, new DoubleArrayDictionary(additional));
380 }
381
382 /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states.
383 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
384 * @param absPva position-velocity-acceleration
385 * @param attitude attitude
386 * @param additional additional states
387 * @exception IllegalArgumentException if orbit and attitude dates
388 * or frames are not equal
389 * @since 11.1
390 */
391 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final DoubleArrayDictionary additional)
392 throws IllegalArgumentException {
393 this(absPva, attitude, DEFAULT_MASS, additional);
394 }
395
396 /** Create a new instance from position-velocity-acceleration, mass and additional states.
397 * <p>Attitude law is set to an unspecified default attitude.</p>
398 * @param absPva position-velocity-acceleration
399 * @param mass the mass (kg)
400 * @param additional additional states
401 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, double, DoubleArrayDictionary)}
402 */
403 @Deprecated
404 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final Map<String, double[]> additional) {
405 this(absPva, mass, new DoubleArrayDictionary(additional));
406 }
407
408 /** Create a new instance from position-velocity-acceleration, mass and additional states.
409 * <p>Attitude law is set to an unspecified default attitude.</p>
410 * @param absPva position-velocity-acceleration
411 * @param mass the mass (kg)
412 * @param additional additional states
413 * @since 11.1
414 */
415 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final DoubleArrayDictionary additional) {
416 this(absPva,
417 new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
418 mass, additional);
419 }
420
421 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states.
422 * @param absPva position-velocity-acceleration
423 * @param attitude attitude
424 * @param mass the mass (kg)
425 * @param additional additional states (may be null if no additional states are available)
426 * @exception IllegalArgumentException if orbit and attitude dates
427 * or frames are not equal
428 * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, Attitude, double, DoubleArrayDictionary)}
429 */
430 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
431 final double mass, final Map<String, double[]> additional)
432 throws IllegalArgumentException {
433 this(absPva, attitude, mass, new DoubleArrayDictionary(additional));
434 }
435
436 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states.
437 * @param absPva position-velocity-acceleration
438 * @param attitude attitude
439 * @param mass the mass (kg)
440 * @param additional additional states (may be null if no additional states are available)
441 * @exception IllegalArgumentException if orbit and attitude dates
442 * or frames are not equal
443 * @since 11.1
444 */
445 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
446 final double mass, final DoubleArrayDictionary additional)
447 throws IllegalArgumentException {
448 this(absPva, attitude, mass, additional, null);
449 }
450
451 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
452 * @param absPva position-velocity-acceleration
453 * @param attitude attitude
454 * @param mass the mass (kg)
455 * @param additional additional states (may be null if no additional states are available)
456 * @param additionalDot additional states derivatives(may be null if no additional states derivativesare available)
457 * @exception IllegalArgumentException if orbit and attitude dates
458 * or frames are not equal
459 * @since 11.1
460 */
461 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
462 final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
463 throws IllegalArgumentException {
464 checkConsistency(absPva, attitude);
465 this.orbit = null;
466 this.absPva = absPva;
467 this.attitude = attitude;
468 this.mass = mass;
469 if (additional == null) {
470 this.additional = new DoubleArrayDictionary();
471 } else {
472 this.additional = new DoubleArrayDictionary(additional);
473 }
474 if (additionalDot == null) {
475 this.additionalDot = new DoubleArrayDictionary();
476 } else {
477 this.additionalDot = new DoubleArrayDictionary(additionalDot);
478 }
479 }
480
481 /** Add an additional state.
482 * <p>
483 * {@link SpacecraftState SpacecraftState} instances are immutable,
484 * so this method does <em>not</em> change the instance, but rather
485 * creates a new instance, which has the same orbit, attitude, mass
486 * and additional states as the original instance, except it also
487 * has the specified state. If the original instance already had an
488 * additional state with the same name, it will be overridden. If it
489 * did not have any additional state with that name, the new instance
490 * will have one more additional state than the original instance.
491 * </p>
492 * @param name name of the additional state (names containing "orekit"
493 * with any case are reserved for the library internal use)
494 * @param value value of the additional state
495 * @return a new instance, with the additional state added
496 * @see #hasAdditionalState(String)
497 * @see #getAdditionalState(String)
498 * @see #getAdditionalStates()
499 */
500 public SpacecraftState addAdditionalState(final String name, final double... value) {
501 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additional);
502 newDict.put(name, value.clone());
503 if (absPva == null) {
504 return new SpacecraftState(orbit, attitude, mass, newDict, additionalDot);
505 } else {
506 return new SpacecraftState(absPva, attitude, mass, newDict, additionalDot);
507 }
508 }
509
510 /** Add an additional state derivative.
511 * <p>
512 * {@link SpacecraftState SpacecraftState} instances are immutable,
513 * so this method does <em>not</em> change the instance, but rather
514 * creates a new instance, which has the same components as the original
515 * instance, except it also has the specified state derivative. If the
516 * original instance already had an additional state derivative with the
517 * same name, it will be overridden. If it did not have any additional
518 * state derivative with that name, the new instance will have one more
519 * additional state derivative than the original instance.
520 * </p>
521 * @param name name of the additional state derivative (names containing "orekit"
522 * with any case are reserved for the library internal use)
523 * @param value value of the additional state derivative
524 * @return a new instance, with the additional state added
525 * @see #hasAdditionalStateDerivative(String)
526 * @see #getAdditionalStateDerivative(String)
527 * @see #getAdditionalStatesDerivatives()
528 * @since 11.1
529 */
530 public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
531 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
532 newDict.put(name, value.clone());
533 if (absPva == null) {
534 return new SpacecraftState(orbit, attitude, mass, additional, newDict);
535 } else {
536 return new SpacecraftState(absPva, attitude, mass, additional, newDict);
537 }
538 }
539
540 /** Check orbit and attitude dates are equal.
541 * @param orbit the orbit
542 * @param attitude attitude
543 * @exception IllegalArgumentException if orbit and attitude dates
544 * are not equal
545 */
546 private static void checkConsistency(final Orbit orbit, final Attitude attitude)
547 throws IllegalArgumentException {
548 if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
549 DATE_INCONSISTENCY_THRESHOLD) {
550 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
551 orbit.getDate(), attitude.getDate());
552 }
553 if (orbit.getFrame() != attitude.getReferenceFrame()) {
554 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
555 orbit.getFrame().getName(),
556 attitude.getReferenceFrame().getName());
557 }
558 }
559
560 /** Check if the state contains an orbit part.
561 * <p>
562 * A state contains either an {@link AbsolutePVCoordinates absolute
563 * position-velocity-acceleration} or an {@link Orbit orbit}.
564 * </p>
565 * @return true if state contains an orbit (in which case {@link #getOrbit()}
566 * will not throw an exception), or false if the state contains an
567 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
568 * will not throw an exception)
569 */
570 public boolean isOrbitDefined() {
571 return orbit != null;
572 }
573
574 /** Check AbsolutePVCoordinates and attitude dates are equal.
575 * @param absPva position-velocity-acceleration
576 * @param attitude attitude
577 * @exception IllegalArgumentException if orbit and attitude dates
578 * are not equal
579 */
580 private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
581 throws IllegalArgumentException {
582 if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) >
583 DATE_INCONSISTENCY_THRESHOLD) {
584 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
585 absPva.getDate(), attitude.getDate());
586 }
587 if (absPva.getFrame() != attitude.getReferenceFrame()) {
588 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
589 absPva.getFrame().getName(),
590 attitude.getReferenceFrame().getName());
591 }
592 }
593
594 /** Get a time-shifted state.
595 * <p>
596 * The state can be slightly shifted to close dates. This shift is based on
597 * simple models. For orbits, the model is a Keplerian one if no derivatives
598 * are available in the orbit, or Keplerian plus quadratic effect of the
599 * non-Keplerian acceleration if derivatives are available. For attitude,
600 * a polynomial model is used. Neither mass nor additional states change.
601 * Shifting is <em>not</em> intended as a replacement for proper orbit
602 * and attitude propagation but should be sufficient for small time shifts
603 * or coarse accuracy.
604 * </p>
605 * <p>
606 * As a rough order of magnitude, the following table shows the extrapolation
607 * errors obtained between this simple shift method and an {@link
608 * org.orekit.propagation.numerical.NumericalPropagator numerical
609 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
610 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
611 * Beware that these results will be different for other orbits.
612 * </p>
613 * <table border="1">
614 * <caption>Extrapolation Error</caption>
615 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
616 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
617 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr>
618 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr>
619 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
620 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
621 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
622 * </table>
623 * @param dt time shift in seconds
624 * @return a new state, shifted with respect to the instance (which is immutable)
625 * except for the mass and additional states which stay unchanged
626 */
627 public SpacecraftState shiftedBy(final double dt) {
628 if (absPva == null) {
629 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
630 mass, shiftAdditional(dt), additionalDot);
631 } else {
632 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
633 mass, shiftAdditional(dt), additionalDot);
634 }
635 }
636
637 /** Shift additional states.
638 * @param dt time shift in seconds
639 * @return shifted additional states
640 * @since 11.1.1
641 */
642 private DoubleArrayDictionary shiftAdditional(final double dt) {
643
644 // fast handling when there are no derivatives at all
645 if (additionalDot.size() == 0) {
646 return additional;
647 }
648
649 // there are derivatives, we need to take them into account in the additional state
650 final DoubleArrayDictionary shifted = new DoubleArrayDictionary(additional);
651 for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
652 final DoubleArrayDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
653 if (entry != null) {
654 entry.scaledIncrement(dt, dotEntry);
655 }
656 }
657
658 return shifted;
659
660 }
661
662 /** {@inheritDoc}
663 * <p>
664 * The additional states that are interpolated are the ones already present
665 * in the instance. The sample instances must therefore have at least the same
666 * additional states has the instance. They may have more additional states,
667 * but the extra ones will be ignored.
668 * </p>
669 * <p>
670 * The instance and all the sample instances <em>must</em> be based on similar
671 * trajectory data, i.e. they must either all be based on orbits or all be based
672 * on absolute position-velocity-acceleration. Any inconsistency will trigger
673 * an {@link OrekitIllegalStateException}.
674 * </p>
675 * <p>
676 * As this implementation of interpolation is polynomial, it should be used only
677 * with small samples (about 10-20 points) in order to avoid <a
678 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
679 * and numerical problems (including NaN appearing).
680 * </p>
681 * @exception OrekitIllegalStateException if some instances are not based on
682 * similar trajectory data
683 */
684 public SpacecraftState interpolate(final AbsoluteDate date,
685 final Stream<SpacecraftState> sample) {
686
687 // prepare interpolators
688 final List<Orbit> orbits;
689 final List<AbsolutePVCoordinates> absPvas;
690 if (isOrbitDefined()) {
691 orbits = new ArrayList<Orbit>();
692 absPvas = null;
693 } else {
694 orbits = null;
695 absPvas = new ArrayList<AbsolutePVCoordinates>();
696 }
697 final List<Attitude> attitudes = new ArrayList<>();
698 final HermiteInterpolator massInterpolator = new HermiteInterpolator();
699 final List<DoubleArrayDictionary.Entry> addionalEntries = additional.getData();
700 final Map<String, HermiteInterpolator> additionalInterpolators =
701 new HashMap<String, HermiteInterpolator>(addionalEntries.size());
702 for (final DoubleArrayDictionary.Entry entry : addionalEntries) {
703 additionalInterpolators.put(entry.getKey(), new HermiteInterpolator());
704 }
705 final List<DoubleArrayDictionary.Entry> additionalDotEntries = additionalDot.getData();
706 final Map<String, HermiteInterpolator> additionalDotInterpolators =
707 new HashMap<String, HermiteInterpolator>(additionalDotEntries.size());
708 for (final DoubleArrayDictionary.Entry entry : additionalDotEntries) {
709 additionalDotInterpolators.put(entry.getKey(), new HermiteInterpolator());
710 }
711
712 // extract sample data
713 sample.forEach(state -> {
714 final double deltaT = state.getDate().durationFrom(date);
715 if (isOrbitDefined()) {
716 orbits.add(state.getOrbit());
717 } else {
718 absPvas.add(state.getAbsPVA());
719 }
720 attitudes.add(state.getAttitude());
721 massInterpolator.addSamplePoint(deltaT,
722 new double[] {
723 state.getMass()
724 });
725 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
726 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
727 }
728 for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
729 entry.getValue().addSamplePoint(deltaT, state.getAdditionalStateDerivative(entry.getKey()));
730 }
731
732 });
733
734 // perform interpolations
735 final Orbit interpolatedOrbit;
736 final AbsolutePVCoordinates interpolatedAbsPva;
737 if (isOrbitDefined()) {
738 interpolatedOrbit = orbit.interpolate(date, orbits);
739 interpolatedAbsPva = null;
740 } else {
741 interpolatedOrbit = null;
742 interpolatedAbsPva = absPva.interpolate(date, absPvas);
743 }
744 final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
745 final double interpolatedMass = massInterpolator.value(0)[0];
746 final DoubleArrayDictionary interpolatedAdditional;
747 if (additionalInterpolators.isEmpty()) {
748 interpolatedAdditional = null;
749 } else {
750 interpolatedAdditional = new DoubleArrayDictionary(additionalInterpolators.size());
751 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
752 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
753 }
754 }
755 final DoubleArrayDictionary interpolatedAdditionalDot;
756 if (additionalDotInterpolators.isEmpty()) {
757 interpolatedAdditionalDot = null;
758 } else {
759 interpolatedAdditionalDot = new DoubleArrayDictionary(additionalDotInterpolators.size());
760 for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
761 interpolatedAdditionalDot.put(entry.getKey(), entry.getValue().value(0));
762 }
763 }
764
765 // create the complete interpolated state
766 if (isOrbitDefined()) {
767 return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass,
768 interpolatedAdditional, interpolatedAdditionalDot);
769 } else {
770 return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass,
771 interpolatedAdditional, interpolatedAdditionalDot);
772 }
773
774 }
775
776 /** Get the absolute position-velocity-acceleration.
777 * <p>
778 * A state contains either an {@link AbsolutePVCoordinates absolute
779 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
780 * one is present can be checked using {@link #isOrbitDefined()}.
781 * </p>
782 * @return absolute position-velocity-acceleration
783 * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
784 * which mean the state rather contains an {@link Orbit}
785 * @see #isOrbitDefined()
786 * @see #getOrbit()
787 */
788 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
789 if (absPva == null) {
790 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
791 }
792 return absPva;
793 }
794
795 /** Get the current orbit.
796 * <p>
797 * A state contains either an {@link AbsolutePVCoordinates absolute
798 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
799 * one is present can be checked using {@link #isOrbitDefined()}.
800 * </p>
801 * @return the orbit
802 * @exception OrekitIllegalStateException if orbit is null,
803 * which means the state rather contains an {@link AbsolutePVCoordinates absolute
804 * position-velocity-acceleration}
805 * @see #isOrbitDefined()
806 * @see #getAbsPVA()
807 */
808 public Orbit getOrbit() throws OrekitIllegalStateException {
809 if (orbit == null) {
810 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
811 }
812 return orbit;
813 }
814
815 /** Get the date.
816 * @return date
817 */
818 public AbsoluteDate getDate() {
819 return (absPva == null) ? orbit.getDate() : absPva.getDate();
820 }
821
822 /** Get the defining frame.
823 * @return the frame in which state is defined
824 */
825 public Frame getFrame() {
826 return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
827 }
828
829 /** Check if an additional state is available.
830 * @param name name of the additional state
831 * @return true if the additional state is available
832 * @see #addAdditionalState(String, double[])
833 * @see #getAdditionalState(String)
834 * @see #getAdditionalStates()
835 */
836 public boolean hasAdditionalState(final String name) {
837 return additional.getEntry(name) != null;
838 }
839
840 /** Check if an additional state derivative is available.
841 * @param name name of the additional state derivative
842 * @return true if the additional state derivative is available
843 * @see #addAdditionalStateDerivative(String, double[])
844 * @see #getAdditionalStateDerivative(String)
845 * @see #getAdditionalStatesDerivatives()
846 * @since 11.1
847 */
848 public boolean hasAdditionalStateDerivative(final String name) {
849 return additionalDot.getEntry(name) != null;
850 }
851
852 /** Check if two instances have the same set of additional states available.
853 * <p>
854 * Only the names and dimensions of the additional states are compared,
855 * not their values.
856 * </p>
857 * @param state state to compare to instance
858 * @exception MathIllegalStateException if an additional state does not have
859 * the same dimension in both states
860 */
861 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
862 throws MathIllegalStateException {
863
864 // check instance additional states is a subset of the other one
865 for (final DoubleArrayDictionary.Entry entry : additional.getData()) {
866 final double[] other = state.additional.get(entry.getKey());
867 if (other == null) {
868 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
869 entry.getKey());
870 }
871 if (other.length != entry.getValue().length) {
872 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
873 other.length, entry.getValue().length);
874 }
875 }
876
877 // check instance additional states derivatives is a subset of the other one
878 for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
879 final double[] other = state.additionalDot.get(entry.getKey());
880 if (other == null) {
881 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
882 entry.getKey());
883 }
884 if (other.length != entry.getValue().length) {
885 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
886 other.length, entry.getValue().length);
887 }
888 }
889
890 if (state.additional.size() > additional.size()) {
891 // the other state has more additional states
892 for (final DoubleArrayDictionary.Entry entry : state.additional.getData()) {
893 if (additional.getEntry(entry.getKey()) == null) {
894 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
895 entry.getKey());
896 }
897 }
898 }
899
900 if (state.additionalDot.size() > additionalDot.size()) {
901 // the other state has more additional states
902 for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
903 if (additionalDot.getEntry(entry.getKey()) == null) {
904 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
905 entry.getKey());
906 }
907 }
908 }
909
910 }
911
912 /** Get an additional state.
913 * @param name name of the additional state
914 * @return value of the additional state
915 * @see #addAdditionalState(String, double[])
916 * @see #hasAdditionalState(String)
917 * @see #getAdditionalStates()
918 */
919 public double[] getAdditionalState(final String name) {
920 final DoubleArrayDictionary.Entry entry = additional.getEntry(name);
921 if (entry == null) {
922 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
923 }
924 return entry.getValue();
925 }
926
927 /** Get an additional state derivative.
928 * @param name name of the additional state derivative
929 * @return value of the additional state derivative
930 * @see #addAdditionalStateDerivative(String, double[])
931 * @see #hasAdditionalStateDerivative(String)
932 * @see #getAdditionalStatesDerivatives()
933 * @since 11.1
934 */
935 public double[] getAdditionalStateDerivative(final String name) {
936 final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
937 if (entry == null) {
938 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
939 }
940 return entry.getValue();
941 }
942
943 /** Get an unmodifiable map of additional states.
944 * @return unmodifiable map of additional states
945 * @see #addAdditionalState(String, double[])
946 * @see #hasAdditionalState(String)
947 * @see #getAdditionalState(String)
948 * @deprecated as of 11.1, replaced by {@link #getAdditionalStatesValues()}
949 */
950 @Deprecated
951 public Map<String, double[]> getAdditionalStates() {
952 return getAdditionalStatesValues().toMap();
953 }
954
955 /** Get an unmodifiable map of additional states.
956 * @return unmodifiable map of additional states
957 * @see #addAdditionalState(String, double[])
958 * @see #hasAdditionalState(String)
959 * @see #getAdditionalState(String)
960 * @since 11.1
961 */
962 public DoubleArrayDictionary getAdditionalStatesValues() {
963 return additional.unmodifiableView();
964 }
965
966 /** Get an unmodifiable map of additional states derivatives.
967 * @return unmodifiable map of additional states derivatives
968 * @see #addAdditionalStateDerivative(String, double[])
969 * @see #hasAdditionalStateDerivative(String)
970 * @see #getAdditionalStateDerivative(String)
971 * @since 11.1
972 */
973 public DoubleArrayDictionary getAdditionalStatesDerivatives() {
974 return additionalDot.unmodifiableView();
975 }
976
977 /** Compute the transform from state defining frame to spacecraft frame.
978 * <p>The spacecraft frame origin is at the point defined by the orbit
979 * (or absolute position-velocity-acceleration), and its orientation is
980 * defined by the attitude.</p>
981 * @return transform from specified frame to current spacecraft frame
982 */
983 public Transform toTransform() {
984 final TimeStampedPVCoordinates pv = getPVCoordinates();
985 return new Transform(pv.getDate(),
986 new Transform(pv.getDate(), pv.negate()),
987 new Transform(pv.getDate(), attitude.getOrientation()));
988 }
989
990 /** Get the central attraction coefficient.
991 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
992 * state is contains an absolute position-velocity-acceleration rather than an orbit
993 */
994 public double getMu() {
995 return (absPva == null) ? orbit.getMu() : Double.NaN;
996 }
997
998 /** Get the Keplerian period.
999 * <p>The Keplerian period is computed directly from semi major axis
1000 * and central acceleration constant.</p>
1001 * @return keplerian period in seconds, or {code Double.NaN} if the
1002 * state is contains an absolute position-velocity-acceleration rather
1003 * than an orbit
1004 */
1005 public double getKeplerianPeriod() {
1006 return (absPva == null) ? orbit.getKeplerianPeriod() : Double.NaN;
1007 }
1008
1009 /** Get the Keplerian mean motion.
1010 * <p>The Keplerian mean motion is computed directly from semi major axis
1011 * and central acceleration constant.</p>
1012 * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
1013 * state is contains an absolute position-velocity-acceleration rather
1014 * than an orbit
1015 */
1016 public double getKeplerianMeanMotion() {
1017 return (absPva == null) ? orbit.getKeplerianMeanMotion() : Double.NaN;
1018 }
1019
1020 /** Get the semi-major axis.
1021 * @return semi-major axis (m), or {code Double.NaN} if the
1022 * state is contains an absolute position-velocity-acceleration rather
1023 * than an orbit
1024 */
1025 public double getA() {
1026 return (absPva == null) ? orbit.getA() : Double.NaN;
1027 }
1028
1029 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
1030 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
1031 * state is contains an absolute position-velocity-acceleration rather
1032 * than an orbit
1033 * @see #getE()
1034 */
1035 public double getEquinoctialEx() {
1036 return (absPva == null) ? orbit.getEquinoctialEx() : Double.NaN;
1037 }
1038
1039 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
1040 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
1041 * state is contains an absolute position-velocity-acceleration rather
1042 * than an orbit
1043 * @see #getE()
1044 */
1045 public double getEquinoctialEy() {
1046 return (absPva == null) ? orbit.getEquinoctialEy() : Double.NaN;
1047 }
1048
1049 /** Get the first component of the inclination vector (as per equinoctial parameters).
1050 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
1051 * state is contains an absolute position-velocity-acceleration rather
1052 * than an orbit
1053 * @see #getI()
1054 */
1055 public double getHx() {
1056 return (absPva == null) ? orbit.getHx() : Double.NaN;
1057 }
1058
1059 /** Get the second component of the inclination vector (as per equinoctial parameters).
1060 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
1061 * state is contains an absolute position-velocity-acceleration rather
1062 * than an orbit
1063 * @see #getI()
1064 */
1065 public double getHy() {
1066 return (absPva == null) ? orbit.getHy() : Double.NaN;
1067 }
1068
1069 /** Get the true latitude argument (as per equinoctial parameters).
1070 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
1071 * state is contains an absolute position-velocity-acceleration rather
1072 * than an orbit
1073 * @see #getLE()
1074 * @see #getLM()
1075 */
1076 public double getLv() {
1077 return (absPva == null) ? orbit.getLv() : Double.NaN;
1078 }
1079
1080 /** Get the eccentric latitude argument (as per equinoctial parameters).
1081 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
1082 * state is contains an absolute position-velocity-acceleration rather
1083 * than an orbit
1084 * @see #getLv()
1085 * @see #getLM()
1086 */
1087 public double getLE() {
1088 return (absPva == null) ? orbit.getLE() : Double.NaN;
1089 }
1090
1091 /** Get the mean longitude argument (as per equinoctial parameters).
1092 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
1093 * state is contains an absolute position-velocity-acceleration rather
1094 * than an orbit
1095 * @see #getLv()
1096 * @see #getLE()
1097 */
1098 public double getLM() {
1099 return (absPva == null) ? orbit.getLM() : Double.NaN;
1100 }
1101
1102 // Additional orbital elements
1103
1104 /** Get the eccentricity.
1105 * @return eccentricity, or {code Double.NaN} if the
1106 * state is contains an absolute position-velocity-acceleration rather
1107 * than an orbit
1108 * @see #getEquinoctialEx()
1109 * @see #getEquinoctialEy()
1110 */
1111 public double getE() {
1112 return (absPva == null) ? orbit.getE() : Double.NaN;
1113 }
1114
1115 /** Get the inclination.
1116 * @return inclination (rad)
1117 * @see #getHx()
1118 * @see #getHy()
1119 */
1120 public double getI() {
1121 return (absPva == null) ? orbit.getI() : Double.NaN;
1122 }
1123
1124 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
1125 * <p>
1126 * Compute the position and velocity of the satellite. This method caches its
1127 * results, and recompute them only when the method is called with a new value
1128 * for mu. The result is provided as a reference to the internally cached
1129 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
1130 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
1131 * </p>
1132 * @return pvCoordinates in orbit definition frame
1133 */
1134 public TimeStampedPVCoordinates getPVCoordinates() {
1135 return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
1136 }
1137
1138 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
1139 * <p>
1140 * Compute the position and velocity of the satellite. This method caches its
1141 * results, and recompute them only when the method is called with a new value
1142 * for mu. The result is provided as a reference to the internally cached
1143 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
1144 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
1145 * </p>
1146 * @param outputFrame frame in which coordinates should be defined
1147 * @return pvCoordinates in orbit definition frame
1148 */
1149 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
1150 return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
1151 }
1152
1153 /** Get the attitude.
1154 * @return the attitude.
1155 */
1156 public Attitude getAttitude() {
1157 return attitude;
1158 }
1159
1160 /** Gets the current mass.
1161 * @return the mass (kg)
1162 */
1163 public double getMass() {
1164 return mass;
1165 }
1166
1167 /** Replace the instance with a data transfer object for serialization.
1168 * @return data transfer object that will be serialized
1169 */
1170 private Object writeReplace() {
1171 return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
1172 }
1173
1174 /** Internal class used only for serialization. */
1175 private static class DTOO implements Serializable {
1176
1177 /** Serializable UID. */
1178 private static final long serialVersionUID = 20211121L;
1179
1180 /** Orbit. */
1181 private final Orbit orbit;
1182
1183 /** Attitude and mass double values. */
1184 private double[] d;
1185
1186 /** Additional states. */
1187 private final DoubleArrayDictionary additional;
1188
1189 /** Additional states derivatives. */
1190 private final DoubleArrayDictionary additionalDot;
1191
1192 /** Simple constructor.
1193 * @param state instance to serialize
1194 */
1195 private DTOO(final SpacecraftState state) {
1196
1197 this.orbit = state.orbit;
1198 this.additional = state.additional.getData().isEmpty() ? null : state.additional;
1199 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;
1200
1201 final Rotation rotation = state.attitude.getRotation();
1202 final Vector3D spin = state.attitude.getSpin();
1203 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
1204 this.d = new double[] {
1205 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
1206 spin.getX(), spin.getY(), spin.getZ(),
1207 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
1208 state.mass
1209 };
1210
1211 }
1212
1213 /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
1214 * @return replacement {@link SpacecraftState}
1215 */
1216 private Object readResolve() {
1217 return new SpacecraftState(orbit,
1218 new Attitude(orbit.getFrame(),
1219 new TimeStampedAngularCoordinates(orbit.getDate(),
1220 new Rotation(d[0], d[1], d[2], d[3], false),
1221 new Vector3D(d[4], d[5], d[6]),
1222 new Vector3D(d[7], d[8], d[9]))),
1223 d[10], additional, additionalDot);
1224 }
1225
1226 }
1227
1228 /** Internal class used only for serialization. */
1229 private static class DTOA implements Serializable {
1230
1231 /** Serializable UID. */
1232 private static final long serialVersionUID = 20211121L;
1233
1234 /** Absolute position-velocity-acceleration. */
1235 private final AbsolutePVCoordinates absPva;
1236
1237 /** Attitude and mass double values. */
1238 private double[] d;
1239
1240 /** Additional states. */
1241 private final DoubleArrayDictionary additional;
1242
1243 /** Additional states derivatives. */
1244 private final DoubleArrayDictionary additionalDot;
1245
1246 /** Simple constructor.
1247 * @param state instance to serialize
1248 */
1249 private DTOA(final SpacecraftState state) {
1250
1251 this.absPva = state.absPva;
1252 this.additional = state.additional.getData().isEmpty() ? null : state.additional;
1253 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;
1254
1255 final Rotation rotation = state.attitude.getRotation();
1256 final Vector3D spin = state.attitude.getSpin();
1257 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
1258 this.d = new double[] {
1259 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
1260 spin.getX(), spin.getY(), spin.getZ(),
1261 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
1262 state.mass
1263 };
1264
1265 }
1266
1267 /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
1268 * @return replacement {@link SpacecraftState}
1269 */
1270 private Object readResolve() {
1271 return new SpacecraftState(absPva,
1272 new Attitude(absPva.getFrame(),
1273 new TimeStampedAngularCoordinates(absPva.getDate(),
1274 new Rotation(d[0], d[1], d[2], d[3], false),
1275 new Vector3D(d[4], d[5], d[6]),
1276 new Vector3D(d[7], d[8], d[9]))),
1277 d[10], additional, additionalDot);
1278 }
1279 }
1280
1281 @Override
1282 public String toString() {
1283 return "SpacecraftState{" +
1284 "orbit=" + orbit +
1285 ", attitude=" + attitude +
1286 ", mass=" + mass +
1287 ", additional=" + additional +
1288 ", additionalDot=" + additionalDot +
1289 '}';
1290 }
1291 }