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&eacute;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 }