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