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, additional, additionalDot);
631 } else {
632 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
633 mass, additional, additionalDot);
634 }
635 }
636
637 /** {@inheritDoc}
638 * <p>
639 * The additional states that are interpolated are the ones already present
640 * in the instance. The sample instances must therefore have at least the same
641 * additional states has the instance. They may have more additional states,
642 * but the extra ones will be ignored.
643 * </p>
644 * <p>
645 * The instance and all the sample instances <em>must</em> be based on similar
646 * trajectory data, i.e. they must either all be based on orbits or all be based
647 * on absolute position-velocity-acceleration. Any inconsistency will trigger
648 * an {@link OrekitIllegalStateException}.
649 * </p>
650 * <p>
651 * As this implementation of interpolation is polynomial, it should be used only
652 * with small samples (about 10-20 points) in order to avoid <a
653 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
654 * and numerical problems (including NaN appearing).
655 * </p>
656 * @exception OrekitIllegalStateException if some instances are not based on
657 * similar trajectory data
658 */
659 public SpacecraftState interpolate(final AbsoluteDate date,
660 final Stream<SpacecraftState> sample) {
661
662 // prepare interpolators
663 final List<Orbit> orbits;
664 final List<AbsolutePVCoordinates> absPvas;
665 if (isOrbitDefined()) {
666 orbits = new ArrayList<Orbit>();
667 absPvas = null;
668 } else {
669 orbits = null;
670 absPvas = new ArrayList<AbsolutePVCoordinates>();
671 }
672 final List<Attitude> attitudes = new ArrayList<>();
673 final HermiteInterpolator massInterpolator = new HermiteInterpolator();
674 final List<DoubleArrayDictionary.Entry> addionalEntries = additional.getData();
675 final Map<String, HermiteInterpolator> additionalInterpolators =
676 new HashMap<String, HermiteInterpolator>(addionalEntries.size());
677 for (final DoubleArrayDictionary.Entry entry : addionalEntries) {
678 additionalInterpolators.put(entry.getKey(), new HermiteInterpolator());
679 }
680 final List<DoubleArrayDictionary.Entry> additionalDotEntries = additionalDot.getData();
681 final Map<String, HermiteInterpolator> additionalDotInterpolators =
682 new HashMap<String, HermiteInterpolator>(additionalDotEntries.size());
683 for (final DoubleArrayDictionary.Entry entry : additionalDotEntries) {
684 additionalDotInterpolators.put(entry.getKey(), new HermiteInterpolator());
685 }
686
687 // extract sample data
688 sample.forEach(state -> {
689 final double deltaT = state.getDate().durationFrom(date);
690 if (isOrbitDefined()) {
691 orbits.add(state.getOrbit());
692 } else {
693 absPvas.add(state.getAbsPVA());
694 }
695 attitudes.add(state.getAttitude());
696 massInterpolator.addSamplePoint(deltaT,
697 new double[] {
698 state.getMass()
699 });
700 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
701 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
702 }
703 for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
704 entry.getValue().addSamplePoint(deltaT, state.getAdditionalStateDerivative(entry.getKey()));
705 }
706
707 });
708
709 // perform interpolations
710 final Orbit interpolatedOrbit;
711 final AbsolutePVCoordinates interpolatedAbsPva;
712 if (isOrbitDefined()) {
713 interpolatedOrbit = orbit.interpolate(date, orbits);
714 interpolatedAbsPva = null;
715 } else {
716 interpolatedOrbit = null;
717 interpolatedAbsPva = absPva.interpolate(date, absPvas);
718 }
719 final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
720 final double interpolatedMass = massInterpolator.value(0)[0];
721 final DoubleArrayDictionary interpolatedAdditional;
722 if (additionalInterpolators.isEmpty()) {
723 interpolatedAdditional = null;
724 } else {
725 interpolatedAdditional = new DoubleArrayDictionary(additionalInterpolators.size());
726 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
727 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
728 }
729 }
730 final DoubleArrayDictionary interpolatedAdditionalDot;
731 if (additionalDotInterpolators.isEmpty()) {
732 interpolatedAdditionalDot = null;
733 } else {
734 interpolatedAdditionalDot = new DoubleArrayDictionary(additionalDotInterpolators.size());
735 for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
736 interpolatedAdditionalDot.put(entry.getKey(), entry.getValue().value(0));
737 }
738 }
739
740 // create the complete interpolated state
741 if (isOrbitDefined()) {
742 return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass,
743 interpolatedAdditional, interpolatedAdditionalDot);
744 } else {
745 return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass,
746 interpolatedAdditional, interpolatedAdditionalDot);
747 }
748
749 }
750
751 /** Get the absolute position-velocity-acceleration.
752 * <p>
753 * A state contains either an {@link AbsolutePVCoordinates absolute
754 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
755 * one is present can be checked using {@link #isOrbitDefined()}.
756 * </p>
757 * @return absolute position-velocity-acceleration
758 * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
759 * which mean the state rather contains an {@link Orbit}
760 * @see #isOrbitDefined()
761 * @see #getOrbit()
762 */
763 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
764 if (absPva == null) {
765 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
766 }
767 return absPva;
768 }
769
770 /** Get the current orbit.
771 * <p>
772 * A state contains either an {@link AbsolutePVCoordinates absolute
773 * position-velocity-acceleration} or an {@link Orbit orbit}. Which
774 * one is present can be checked using {@link #isOrbitDefined()}.
775 * </p>
776 * @return the orbit
777 * @exception OrekitIllegalStateException if orbit is null,
778 * which means the state rather contains an {@link AbsolutePVCoordinates absolute
779 * position-velocity-acceleration}
780 * @see #isOrbitDefined()
781 * @see #getAbsPVA()
782 */
783 public Orbit getOrbit() throws OrekitIllegalStateException {
784 if (orbit == null) {
785 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
786 }
787 return orbit;
788 }
789
790 /** Get the date.
791 * @return date
792 */
793 public AbsoluteDate getDate() {
794 return (absPva == null) ? orbit.getDate() : absPva.getDate();
795 }
796
797 /** Get the defining frame.
798 * @return the frame in which state is defined
799 */
800 public Frame getFrame() {
801 return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
802 }
803
804 /** Check if an additional state is available.
805 * @param name name of the additional state
806 * @return true if the additional state is available
807 * @see #addAdditionalState(String, double[])
808 * @see #getAdditionalState(String)
809 * @see #getAdditionalStates()
810 */
811 public boolean hasAdditionalState(final String name) {
812 return additional.getEntry(name) != null;
813 }
814
815 /** Check if an additional state derivative is available.
816 * @param name name of the additional state derivative
817 * @return true if the additional state derivative is available
818 * @see #addAdditionalStateDerivative(String, double[])
819 * @see #getAdditionalStateDerivative(String)
820 * @see #getAdditionalStatesDerivatives()
821 * @since 11.1
822 */
823 public boolean hasAdditionalStateDerivative(final String name) {
824 return additionalDot.getEntry(name) != null;
825 }
826
827 /** Check if two instances have the same set of additional states available.
828 * <p>
829 * Only the names and dimensions of the additional states are compared,
830 * not their values.
831 * </p>
832 * @param state state to compare to instance
833 * @exception MathIllegalStateException if an additional state does not have
834 * the same dimension in both states
835 */
836 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
837 throws MathIllegalStateException {
838
839 // check instance additional states is a subset of the other one
840 for (final DoubleArrayDictionary.Entry entry : additional.getData()) {
841 final double[] other = state.additional.get(entry.getKey());
842 if (other == null) {
843 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
844 entry.getKey());
845 }
846 if (other.length != entry.getValue().length) {
847 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
848 other.length, entry.getValue().length);
849 }
850 }
851
852 // check instance additional states derivatives is a subset of the other one
853 for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
854 final double[] other = state.additionalDot.get(entry.getKey());
855 if (other == null) {
856 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
857 entry.getKey());
858 }
859 if (other.length != entry.getValue().length) {
860 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
861 other.length, entry.getValue().length);
862 }
863 }
864
865 if (state.additional.size() > additional.size()) {
866 // the other state has more additional states
867 for (final DoubleArrayDictionary.Entry entry : state.additional.getData()) {
868 if (additional.getEntry(entry.getKey()) == null) {
869 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
870 entry.getKey());
871 }
872 }
873 }
874
875 if (state.additionalDot.size() > additionalDot.size()) {
876 // the other state has more additional states
877 for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
878 if (additionalDot.getEntry(entry.getKey()) == null) {
879 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
880 entry.getKey());
881 }
882 }
883 }
884
885 }
886
887 /** Get an additional state.
888 * @param name name of the additional state
889 * @return value of the additional state
890 * @see #addAdditionalState(String, double[])
891 * @see #hasAdditionalState(String)
892 * @see #getAdditionalStates()
893 */
894 public double[] getAdditionalState(final String name) {
895 final DoubleArrayDictionary.Entry entry = additional.getEntry(name);
896 if (entry == null) {
897 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
898 }
899 return entry.getValue();
900 }
901
902 /** Get an additional state derivative.
903 * @param name name of the additional state derivative
904 * @return value of the additional state derivative
905 * @see #addAdditionalStateDerivative(String, double[])
906 * @see #hasAdditionalStateDerivative(String)
907 * @see #getAdditionalStatesDerivatives()
908 * @since 11.1
909 */
910 public double[] getAdditionalStateDerivative(final String name) {
911 final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
912 if (entry == null) {
913 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
914 }
915 return entry.getValue();
916 }
917
918 /** Get an unmodifiable map of additional states.
919 * @return unmodifiable map of additional states
920 * @see #addAdditionalState(String, double[])
921 * @see #hasAdditionalState(String)
922 * @see #getAdditionalState(String)
923 * @deprecated as of 11.1, replaced by {@link #getAdditionalStatesValues()}
924 */
925 @Deprecated
926 public Map<String, double[]> getAdditionalStates() {
927 return getAdditionalStatesValues().toMap();
928 }
929
930 /** Get an unmodifiable map of additional states.
931 * @return unmodifiable map of additional states
932 * @see #addAdditionalState(String, double[])
933 * @see #hasAdditionalState(String)
934 * @see #getAdditionalState(String)
935 * @since 11.1
936 */
937 public DoubleArrayDictionary getAdditionalStatesValues() {
938 return additional.unmodifiableView();
939 }
940
941 /** Get an unmodifiable map of additional states derivatives.
942 * @return unmodifiable map of additional states derivatives
943 * @see #addAdditionalStateDerivative(String, double[])
944 * @see #hasAdditionalStateDerivative(String)
945 * @see #getAdditionalStateDerivative(String)
946 * @since 11.1
947 */
948 public DoubleArrayDictionary getAdditionalStatesDerivatives() {
949 return additionalDot.unmodifiableView();
950 }
951
952 /** Compute the transform from state defining frame to spacecraft frame.
953 * <p>The spacecraft frame origin is at the point defined by the orbit
954 * (or absolute position-velocity-acceleration), and its orientation is
955 * defined by the attitude.</p>
956 * @return transform from specified frame to current spacecraft frame
957 */
958 public Transform toTransform() {
959 final TimeStampedPVCoordinates pv = getPVCoordinates();
960 return new Transform(pv.getDate(),
961 new Transform(pv.getDate(), pv.negate()),
962 new Transform(pv.getDate(), attitude.getOrientation()));
963 }
964
965 /** Get the central attraction coefficient.
966 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
967 * state is contains an absolute position-velocity-acceleration rather than an orbit
968 */
969 public double getMu() {
970 return (absPva == null) ? orbit.getMu() : Double.NaN;
971 }
972
973 /** Get the Keplerian period.
974 * <p>The Keplerian period is computed directly from semi major axis
975 * and central acceleration constant.</p>
976 * @return keplerian period in seconds, or {code Double.NaN} if the
977 * state is contains an absolute position-velocity-acceleration rather
978 * than an orbit
979 */
980 public double getKeplerianPeriod() {
981 return (absPva == null) ? orbit.getKeplerianPeriod() : Double.NaN;
982 }
983
984 /** Get the Keplerian mean motion.
985 * <p>The Keplerian mean motion is computed directly from semi major axis
986 * and central acceleration constant.</p>
987 * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
988 * state is contains an absolute position-velocity-acceleration rather
989 * than an orbit
990 */
991 public double getKeplerianMeanMotion() {
992 return (absPva == null) ? orbit.getKeplerianMeanMotion() : Double.NaN;
993 }
994
995 /** Get the semi-major axis.
996 * @return semi-major axis (m), or {code Double.NaN} if the
997 * state is contains an absolute position-velocity-acceleration rather
998 * than an orbit
999 */
1000 public double getA() {
1001 return (absPva == null) ? orbit.getA() : Double.NaN;
1002 }
1003
1004 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
1005 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
1006 * state is contains an absolute position-velocity-acceleration rather
1007 * than an orbit
1008 * @see #getE()
1009 */
1010 public double getEquinoctialEx() {
1011 return (absPva == null) ? orbit.getEquinoctialEx() : Double.NaN;
1012 }
1013
1014 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
1015 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
1016 * state is contains an absolute position-velocity-acceleration rather
1017 * than an orbit
1018 * @see #getE()
1019 */
1020 public double getEquinoctialEy() {
1021 return (absPva == null) ? orbit.getEquinoctialEy() : Double.NaN;
1022 }
1023
1024 /** Get the first component of the inclination vector (as per equinoctial parameters).
1025 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
1026 * state is contains an absolute position-velocity-acceleration rather
1027 * than an orbit
1028 * @see #getI()
1029 */
1030 public double getHx() {
1031 return (absPva == null) ? orbit.getHx() : Double.NaN;
1032 }
1033
1034 /** Get the second component of the inclination vector (as per equinoctial parameters).
1035 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
1036 * state is contains an absolute position-velocity-acceleration rather
1037 * than an orbit
1038 * @see #getI()
1039 */
1040 public double getHy() {
1041 return (absPva == null) ? orbit.getHy() : Double.NaN;
1042 }
1043
1044 /** Get the true latitude argument (as per equinoctial parameters).
1045 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
1046 * state is contains an absolute position-velocity-acceleration rather
1047 * than an orbit
1048 * @see #getLE()
1049 * @see #getLM()
1050 */
1051 public double getLv() {
1052 return (absPva == null) ? orbit.getLv() : Double.NaN;
1053 }
1054
1055 /** Get the eccentric latitude argument (as per equinoctial parameters).
1056 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
1057 * state is contains an absolute position-velocity-acceleration rather
1058 * than an orbit
1059 * @see #getLv()
1060 * @see #getLM()
1061 */
1062 public double getLE() {
1063 return (absPva == null) ? orbit.getLE() : Double.NaN;
1064 }
1065
1066 /** Get the mean longitude argument (as per equinoctial parameters).
1067 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
1068 * state is contains an absolute position-velocity-acceleration rather
1069 * than an orbit
1070 * @see #getLv()
1071 * @see #getLE()
1072 */
1073 public double getLM() {
1074 return (absPva == null) ? orbit.getLM() : Double.NaN;
1075 }
1076
1077 // Additional orbital elements
1078
1079 /** Get the eccentricity.
1080 * @return eccentricity, or {code Double.NaN} if the
1081 * state is contains an absolute position-velocity-acceleration rather
1082 * than an orbit
1083 * @see #getEquinoctialEx()
1084 * @see #getEquinoctialEy()
1085 */
1086 public double getE() {
1087 return (absPva == null) ? orbit.getE() : Double.NaN;
1088 }
1089
1090 /** Get the inclination.
1091 * @return inclination (rad)
1092 * @see #getHx()
1093 * @see #getHy()
1094 */
1095 public double getI() {
1096 return (absPva == null) ? orbit.getI() : Double.NaN;
1097 }
1098
1099 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
1100 * <p>
1101 * Compute the position and velocity of the satellite. This method caches its
1102 * results, and recompute them only when the method is called with a new value
1103 * for mu. The result is provided as a reference to the internally cached
1104 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
1105 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
1106 * </p>
1107 * @return pvCoordinates in orbit definition frame
1108 */
1109 public TimeStampedPVCoordinates getPVCoordinates() {
1110 return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
1111 }
1112
1113 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
1114 * <p>
1115 * Compute the position and velocity of the satellite. This method caches its
1116 * results, and recompute them only when the method is called with a new value
1117 * for mu. The result is provided as a reference to the internally cached
1118 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
1119 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
1120 * </p>
1121 * @param outputFrame frame in which coordinates should be defined
1122 * @return pvCoordinates in orbit definition frame
1123 */
1124 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
1125 return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
1126 }
1127
1128 /** Get the attitude.
1129 * @return the attitude.
1130 */
1131 public Attitude getAttitude() {
1132 return attitude;
1133 }
1134
1135 /** Gets the current mass.
1136 * @return the mass (kg)
1137 */
1138 public double getMass() {
1139 return mass;
1140 }
1141
1142 /** Replace the instance with a data transfer object for serialization.
1143 * @return data transfer object that will be serialized
1144 */
1145 private Object writeReplace() {
1146 return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
1147 }
1148
1149 /** Internal class used only for serialization. */
1150 private static class DTOO implements Serializable {
1151
1152 /** Serializable UID. */
1153 private static final long serialVersionUID = 20211121L;
1154
1155 /** Orbit. */
1156 private final Orbit orbit;
1157
1158 /** Attitude and mass double values. */
1159 private double[] d;
1160
1161 /** Additional states. */
1162 private final DoubleArrayDictionary additional;
1163
1164 /** Additional states derivatives. */
1165 private final DoubleArrayDictionary additionalDot;
1166
1167 /** Simple constructor.
1168 * @param state instance to serialize
1169 */
1170 private DTOO(final SpacecraftState state) {
1171
1172 this.orbit = state.orbit;
1173 this.additional = state.additional.getData().isEmpty() ? null : state.additional;
1174 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;
1175
1176 final Rotation rotation = state.attitude.getRotation();
1177 final Vector3D spin = state.attitude.getSpin();
1178 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
1179 this.d = new double[] {
1180 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
1181 spin.getX(), spin.getY(), spin.getZ(),
1182 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
1183 state.mass
1184 };
1185
1186 }
1187
1188 /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
1189 * @return replacement {@link SpacecraftState}
1190 */
1191 private Object readResolve() {
1192 return new SpacecraftState(orbit,
1193 new Attitude(orbit.getFrame(),
1194 new TimeStampedAngularCoordinates(orbit.getDate(),
1195 new Rotation(d[0], d[1], d[2], d[3], false),
1196 new Vector3D(d[4], d[5], d[6]),
1197 new Vector3D(d[7], d[8], d[9]))),
1198 d[10], additional, additionalDot);
1199 }
1200
1201 }
1202
1203 /** Internal class used only for serialization. */
1204 private static class DTOA implements Serializable {
1205
1206 /** Serializable UID. */
1207 private static final long serialVersionUID = 20211121L;
1208
1209 /** Absolute position-velocity-acceleration. */
1210 private final AbsolutePVCoordinates absPva;
1211
1212 /** Attitude and mass double values. */
1213 private double[] d;
1214
1215 /** Additional states. */
1216 private final DoubleArrayDictionary additional;
1217
1218 /** Additional states derivatives. */
1219 private final DoubleArrayDictionary additionalDot;
1220
1221 /** Simple constructor.
1222 * @param state instance to serialize
1223 */
1224 private DTOA(final SpacecraftState state) {
1225
1226 this.absPva = state.absPva;
1227 this.additional = state.additional.getData().isEmpty() ? null : state.additional;
1228 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;
1229
1230 final Rotation rotation = state.attitude.getRotation();
1231 final Vector3D spin = state.attitude.getSpin();
1232 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
1233 this.d = new double[] {
1234 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
1235 spin.getX(), spin.getY(), spin.getZ(),
1236 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
1237 state.mass
1238 };
1239
1240 }
1241
1242 /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
1243 * @return replacement {@link SpacecraftState}
1244 */
1245 private Object readResolve() {
1246 return new SpacecraftState(absPva,
1247 new Attitude(absPva.getFrame(),
1248 new TimeStampedAngularCoordinates(absPva.getDate(),
1249 new Rotation(d[0], d[1], d[2], d[3], false),
1250 new Vector3D(d[4], d[5], d[6]),
1251 new Vector3D(d[7], d[8], d[9]))),
1252 d[10], additional, additionalDot);
1253 }
1254 }
1255
1256 @Override
1257 public String toString() {
1258 return "SpacecraftState{" +
1259 "orbit=" + orbit +
1260 ", attitude=" + attitude +
1261 ", mass=" + mass +
1262 ", additional=" + additional +
1263 ", additionalDot=" + additionalDot +
1264 '}';
1265 }
1266 }