1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.propagation;
18  
19  import org.hipparchus.exception.LocalizedCoreFormats;
20  import org.hipparchus.exception.MathIllegalStateException;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.orekit.attitudes.Attitude;
24  import org.orekit.attitudes.AttitudeProvider;
25  import org.orekit.attitudes.FrameAlignedProvider;
26  import org.orekit.errors.OrekitException;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitIllegalStateException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.frames.Frame;
31  import org.orekit.frames.StaticTransform;
32  import org.orekit.frames.Transform;
33  import org.orekit.orbits.Orbit;
34  import org.orekit.propagation.numerical.NumericalPropagator;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.time.TimeOffset;
37  import org.orekit.time.TimeShiftable;
38  import org.orekit.time.TimeStamped;
39  import org.orekit.utils.AbsolutePVCoordinates;
40  import org.orekit.utils.DataDictionary;
41  import org.orekit.utils.DoubleArrayDictionary;
42  import org.orekit.utils.TimeStampedPVCoordinates;
43  
44  /** This class is the representation of a complete state holding orbit, attitude
45   * and mass information at a given date, meant primarily for propagation.
46   *
47   * <p>It contains an {@link Orbit}, or an {@link AbsolutePVCoordinates} if there
48   * is no definite central body, plus the current mass and attitude at the intrinsic
49   * {@link AbsoluteDate}. Quantities are guaranteed to be consistent in terms
50   * of date and reference frame. The spacecraft state may also contain additional
51   * data, which are simply named.
52   * </p>
53   * <p>
54   * The state can be slightly shifted to close dates. This actual shift varies
55   * between {@link Orbit} and {@link AbsolutePVCoordinates}.
56   * For attitude it is a linear extrapolation taking the spin rate into account
57   * and no mass change. It is <em>not</em> intended as a replacement for proper
58   * orbit and attitude propagation but should be sufficient for either small
59   * time shifts or coarse accuracy.
60   * </p>
61   * <p>
62   * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
63   * </p>
64   * @see NumericalPropagator
65   * @author Fabien Maussion
66   * @author V&eacute;ronique Pommier-Maurussane
67   * @author Luc Maisonobe
68   */
69  public class SpacecraftState implements TimeStamped, TimeShiftable<SpacecraftState> {
70  
71      /** Default mass. */
72      public static final double DEFAULT_MASS = 1000.0;
73  
74      /**
75       * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
76       * corresponds to sub-mm accuracy at LEO orbital velocities.
77       */
78      private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
79  
80      /** Orbital state. */
81      private final Orbit orbit;
82  
83      /** Trajectory state, when it is not an orbit. */
84      private final AbsolutePVCoordinates absPva;
85  
86      /** Attitude. */
87      private final Attitude attitude;
88  
89      /** Current mass (kg). */
90      private final double mass;
91  
92      /** Additional data, can be any object (String, double[], etc.). */
93      private final DataDictionary additional;
94  
95      /** Additional states derivatives.
96       * @since 11.1
97       */
98      private final DoubleArrayDictionary additionalDot;
99  
100     /** Build a spacecraft state from orbit only.
101      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
102      * @param orbit the orbit
103      */
104     public SpacecraftState(final Orbit orbit) {
105         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
106                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
107              DEFAULT_MASS, null, null);
108     }
109 
110     /** Build a spacecraft state from orbit and attitude. Kept for performance.
111      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
112      * @param orbit the orbit
113      * @param attitude attitude
114      * @exception IllegalArgumentException if orbit and attitude dates
115      * or frames are not equal
116      */
117     public SpacecraftState(final Orbit orbit, final Attitude attitude)
118         throws IllegalArgumentException {
119         this(orbit, attitude, DEFAULT_MASS, null, null);
120     }
121 
122     /** Create a new instance from orbit and mass.
123      * <p>Attitude law is set to an unspecified default attitude.</p>
124      * @param orbit the orbit
125      * @param mass the mass (kg)
126      * @deprecated since 13.0, use withXXX
127      */
128     @Deprecated
129     public SpacecraftState(final Orbit orbit, final double mass) {
130         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
131                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
132              mass, null, null);
133     }
134 
135     /** Build a spacecraft state from orbit, attitude and mass.
136      * @param orbit the orbit
137      * @param attitude attitude
138      * @param mass the mass (kg)
139      * @exception IllegalArgumentException if orbit and attitude dates
140      * or frames are not equal
141      * @deprecated since 13.0, use withXXX
142      */
143     @Deprecated
144     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
145         throws IllegalArgumentException {
146         this(orbit, attitude, mass, null, null);
147     }
148 
149     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
150      * @param orbit the orbit
151      * @param attitude attitude
152      * @param mass the mass (kg)
153      * @param additional additional data (may be null if no additional states are available)
154      * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
155      * @exception IllegalArgumentException if orbit and attitude dates
156      * or frames are not equal
157      * @since 13.0
158      */
159     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
160                            final DataDictionary additional, final DoubleArrayDictionary additionalDot)
161         throws IllegalArgumentException {
162         checkConsistency(orbit, attitude);
163         this.orbit      = orbit;
164         this.absPva     = null;
165         this.attitude   = attitude;
166         this.mass       = mass;
167         this.additional = additional == null ? new DataDictionary() : additional;
168         this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot);
169     }
170 
171     /** Build a spacecraft state from position-velocity-acceleration only.
172      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
173      * @param absPva position-velocity-acceleration
174      */
175     public SpacecraftState(final AbsolutePVCoordinates absPva) {
176         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
177                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
178              DEFAULT_MASS, null, null);
179     }
180 
181     /** Build a spacecraft state from position-velocity-acceleration and attitude. Kept for performance.
182      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
183      * @param absPva position-velocity-acceleration
184      * @param attitude attitude
185      * @exception IllegalArgumentException if orbit and attitude dates
186      * or frames are not equal
187      */
188     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
189         throws IllegalArgumentException {
190         this(absPva, attitude, DEFAULT_MASS, null, null);
191     }
192 
193     /** Create a new instance from position-velocity-acceleration and mass.
194      * <p>Attitude law is set to an unspecified default attitude.</p>
195      * @param absPva position-velocity-acceleration
196      * @param mass the mass (kg)
197      * @deprecated since 13.0, use withXXX
198      */
199     @Deprecated
200     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
201         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
202                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
203              mass,  null, null);
204     }
205 
206     /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
207      * @param absPva position-velocity-acceleration
208      * @param attitude attitude
209      * @param mass the mass (kg)
210      * @exception IllegalArgumentException if orbit and attitude dates
211      * or frames are not equal
212      * @deprecated since 13.0, use withXXX
213      */
214     @Deprecated
215     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
216         throws IllegalArgumentException {
217         this(absPva, attitude, mass, null, null);
218     }
219 
220     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
221      * @param absPva position-velocity-acceleration
222      * @param attitude attitude
223      * @param mass the mass (kg)
224      * @param additional additional data (may be null if no additional data are available)
225      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
226      * @exception IllegalArgumentException if orbit and attitude dates
227      * or frames are not equal
228      * @since 13.0
229      */
230     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
231                            final DataDictionary additional, final DoubleArrayDictionary additionalDot)
232         throws IllegalArgumentException {
233         checkConsistency(absPva, attitude);
234         this.orbit      = null;
235         this.absPva     = absPva;
236         this.attitude   = attitude;
237         this.mass       = mass;
238         this.additional = additional == null ? new DataDictionary() : new DataDictionary(additional);
239         this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot);
240     }
241 
242     /**
243      * Create a new instance with input mass.
244      * @param newMass mass
245      * @return new state
246      * @since 13.0
247      */
248     public SpacecraftState withMass(final double newMass) {
249         if (isOrbitDefined()) {
250             return new SpacecraftState(orbit, attitude, newMass, additional, additionalDot);
251         } else {
252             return new SpacecraftState(absPva, attitude, newMass, additional, additionalDot);
253         }
254     }
255 
256     /**
257      * Create a new instance with input attitude.
258      * @param newAttitude attitude
259      * @return new state
260      * @since 13.0
261      */
262     public SpacecraftState withAttitude(final Attitude newAttitude) {
263         if (isOrbitDefined()) {
264             return new SpacecraftState(orbit, newAttitude, mass, additional, additionalDot);
265         } else {
266             return new SpacecraftState(absPva, newAttitude, mass, additional, additionalDot);
267         }
268     }
269 
270     /**
271      * Create a new instance with input additional data.
272      * @param dataDictionary additional data
273      * @return new state
274      * @since 13.0
275      */
276     public SpacecraftState withAdditionalData(final DataDictionary dataDictionary) {
277         if (isOrbitDefined()) {
278             return new SpacecraftState(orbit, attitude, mass, dataDictionary, additionalDot);
279         } else {
280             return new SpacecraftState(absPva, attitude, mass, dataDictionary, additionalDot);
281         }
282     }
283 
284     /**
285      * Create a new instance with input additional data.
286      * @param additionalStateDerivatives additional state derivatives
287      * @return new state
288      * @since 13.0
289      */
290     public SpacecraftState withAdditionalStatesDerivatives(final DoubleArrayDictionary additionalStateDerivatives) {
291         if (isOrbitDefined()) {
292             return new SpacecraftState(orbit, attitude, mass, additional, additionalStateDerivatives);
293         } else {
294             return new SpacecraftState(absPva, attitude, mass, additional, additionalStateDerivatives);
295         }
296     }
297 
298     /** Add an additional data.
299      * <p>
300      * {@link SpacecraftState SpacecraftState} instances are immutable,
301      * so this method does <em>not</em> change the instance, but rather
302      * creates a new instance, which has the same orbit, attitude, mass
303      * and additional states as the original instance, except it also
304      * has the specified state. If the original instance already had an
305      * additional data with the same name, it will be overridden. If it
306      * did not have any additional state with that name, the new instance
307      * will have one more additional state than the original instance.
308      * </p>
309      * @param name name of the additional data (names containing "orekit"
310      * with any case are reserved for the library internal use)
311      * @param value value of the additional data
312      * @return a new instance, with the additional data added
313      * @see #hasAdditionalData(String)
314      * @see #getAdditionalData(String)
315      * @see #getAdditionalDataValues()
316      * @since 13.0
317      */
318     public SpacecraftState addAdditionalData(final String name, final Object value) {
319         final DataDictionary newDict = new DataDictionary(additional);
320         if (value instanceof double[]) {
321             newDict.put(name, ((double[]) value).clone());
322         } else if (value instanceof Double) {
323             newDict.put(name, new double[] {(double) value});
324         } else {
325             newDict.put(name, value);
326         }
327         return withAdditionalData(newDict);
328     }
329 
330     /** Add an additional state derivative.
331      * <p>
332      * {@link SpacecraftState SpacecraftState} instances are immutable,
333      * so this method does <em>not</em> change the instance, but rather
334      * creates a new instance, which has the same components as the original
335      * instance, except it also has the specified state derivative. If the
336      * original instance already had an additional state derivative with the
337      * same name, it will be overridden. If it did not have any additional
338      * state derivative with that name, the new instance will have one more
339      * additional state derivative than the original instance.
340      * </p>
341      * @param name name of the additional state derivative (names containing "orekit"
342      * with any case are reserved for the library internal use)
343      * @param value value of the additional state derivative
344      * @return a new instance, with the additional state added
345      * @see #hasAdditionalStateDerivative(String)
346      * @see #getAdditionalStateDerivative(String)
347      * @see #getAdditionalStatesDerivatives()
348      * @since 11.1
349      */
350     public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
351         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
352         newDict.put(name, value.clone());
353         return withAdditionalStatesDerivatives(newDict);
354     }
355 
356     /** Check orbit and attitude dates are equal.
357      * @param orbit the orbit
358      * @param attitude attitude
359      * @exception IllegalArgumentException if orbit and attitude dates
360      * are not equal
361      */
362     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
363         throws IllegalArgumentException {
364         checkDateAndFrameConsistency(attitude, orbit.getDate(), orbit.getFrame());
365     }
366 
367     /** Defines provider for default Attitude when not passed to constructor.
368      * Currently chosen arbitrarily as aligned with input frame.
369      * It is also used in {@link FieldSpacecraftState}.
370      * @param frame reference frame
371      * @return default attitude provider
372      * @since 12.0
373      */
374     static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) {
375         return new FrameAlignedProvider(frame);
376     }
377 
378     /** Check if the state contains an orbit part.
379      * <p>
380      * A state contains either an {@link AbsolutePVCoordinates absolute
381      * position-velocity-acceleration} or an {@link Orbit orbit}.
382      * </p>
383      * @return true if state contains an orbit (in which case {@link #getOrbit()}
384      * will not throw an exception), or false if the state contains an
385      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
386      * will not throw an exception)
387      */
388     public boolean isOrbitDefined() {
389         return orbit != null;
390     }
391 
392     /** Check AbsolutePVCoordinates and attitude dates are equal.
393      * @param absPva position-velocity-acceleration
394      * @param attitude attitude
395      * @exception IllegalArgumentException if orbit and attitude dates
396      * are not equal
397      */
398     private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
399         throws IllegalArgumentException {
400         checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame());
401     }
402 
403     /** Check attitude frame and epoch.
404      * @param attitude attitude
405      * @param date epoch to verify
406      * @param frame frame to verify
407      */
408     private static void checkDateAndFrameConsistency(final Attitude attitude, final AbsoluteDate date, final Frame frame) {
409         if (FastMath.abs(date.durationFrom(attitude.getDate())) >
410                 DATE_INCONSISTENCY_THRESHOLD) {
411             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
412                     date, attitude.getDate());
413         }
414         if (frame != attitude.getReferenceFrame()) {
415             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
416                     frame.getName(),
417                     attitude.getReferenceFrame().getName());
418         }
419     }
420 
421     /** Get a time-shifted state.
422      * <p>
423      * The state can be slightly shifted to close dates. This shift is based on
424      * simple models. For orbits, the model is a Keplerian one if no derivatives
425      * are available in the orbit, or Keplerian plus quadratic effect of the
426      * non-Keplerian acceleration if derivatives are available. For attitude,
427      * a polynomial model is used. Neither mass nor additional states change.
428      * Shifting is <em>not</em> intended as a replacement for proper orbit
429      * and attitude propagation but should be sufficient for small time shifts
430      * or coarse accuracy.
431      * </p>
432      * <p>
433      * As a rough order of magnitude, the following table shows the extrapolation
434      * errors obtained between this simple shift method and an {@link
435      * NumericalPropagator numerical
436      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
437      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
438      * Beware that these results will be different for other orbits.
439      * </p>
440      * <table border="1">
441      * <caption>Extrapolation Error</caption>
442      * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
443      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
444      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
445      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
446      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
447      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
448      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
449      * </table>
450      * @param dt time shift in seconds
451      * @return a new state, shifted with respect to the instance (which is immutable)
452      * except for the mass and additional states which stay unchanged
453      */
454     @Override
455     public SpacecraftState shiftedBy(final double dt) {
456         if (isOrbitDefined()) {
457             return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
458                                        mass, shiftAdditional(dt), additionalDot);
459         } else {
460             return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
461                                        mass, shiftAdditional(dt), additionalDot);
462         }
463     }
464 
465     /** Get a time-shifted state.
466      * <p>
467      * The state can be slightly shifted to close dates. This shift is based on
468      * simple models. For orbits, the model is a Keplerian one if no derivatives
469      * are available in the orbit, or Keplerian plus quadratic effect of the
470      * non-Keplerian acceleration if derivatives are available. For attitude,
471      * a polynomial model is used. Neither mass nor additional states change.
472      * Shifting is <em>not</em> intended as a replacement for proper orbit
473      * and attitude propagation but should be sufficient for small time shifts
474      * or coarse accuracy.
475      * </p>
476      * <p>
477      * As a rough order of magnitude, the following table shows the extrapolation
478      * errors obtained between this simple shift method and an {@link
479      * NumericalPropagator numerical
480      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
481      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
482      * Beware that these results will be different for other orbits.
483      * </p>
484      * <table border="1">
485      * <caption>Extrapolation Error</caption>
486      * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
487      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
488      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
489      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
490      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
491      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
492      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
493      * </table>
494      * @param dt time shift in seconds
495      * @return a new state, shifted with respect to the instance (which is immutable)
496      * except for the mass and additional states which stay unchanged
497      * @since 13.0
498      */
499     @Override
500     public SpacecraftState shiftedBy(final TimeOffset dt) {
501         if (isOrbitDefined()) {
502             return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
503                                        mass, shiftAdditional(dt.toDouble()), additionalDot);
504         } else {
505             return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
506                                        mass, shiftAdditional(dt.toDouble()), additionalDot);
507         }
508     }
509 
510     /** Shift additional states.
511      * @param dt time shift in seconds
512      * @return shifted additional states
513      * @since 11.1.1
514      */
515     private DataDictionary shiftAdditional(final double dt) {
516 
517         // fast handling when there are no derivatives at all
518         if (additionalDot.size() == 0) {
519             return additional;
520         }
521 
522         // there are derivatives, we need to take them into account in the additional state
523         final DataDictionary shifted = new DataDictionary(additional);
524         for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
525             final DataDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
526             if (entry != null) {
527                 entry.scaledIncrement(dt, dotEntry);
528             }
529         }
530 
531         return shifted;
532 
533     }
534 
535     /** Get the absolute position-velocity-acceleration.
536      * <p>
537      * A state contains either an {@link AbsolutePVCoordinates absolute
538      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
539      * one is present can be checked using {@link #isOrbitDefined()}.
540      * </p>
541      * @return absolute position-velocity-acceleration
542      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
543      * which mean the state rather contains an {@link Orbit}
544      * @see #isOrbitDefined()
545      * @see #getOrbit()
546      */
547     public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
548         if (isOrbitDefined()) {
549             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
550         }
551         return absPva;
552     }
553 
554     /** Get the current orbit.
555      * <p>
556      * A state contains either an {@link AbsolutePVCoordinates absolute
557      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
558      * one is present can be checked using {@link #isOrbitDefined()}.
559      * </p>
560      * @return the orbit
561      * @exception OrekitIllegalStateException if orbit is null,
562      * which means the state rather contains an {@link AbsolutePVCoordinates absolute
563      * position-velocity-acceleration}
564      * @see #isOrbitDefined()
565      * @see #getAbsPVA()
566      */
567     public Orbit getOrbit() throws OrekitIllegalStateException {
568         if (orbit == null) {
569             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
570         }
571         return orbit;
572     }
573 
574     /** {@inheritDoc} */
575     @Override
576     public AbsoluteDate getDate() {
577         return (absPva == null) ? orbit.getDate() : absPva.getDate();
578     }
579 
580     /** Get the defining frame.
581      * @return the frame in which state is defined
582      */
583     public Frame getFrame() {
584         return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
585     }
586 
587     /** Check if an additional data is available.
588      * @param name name of the additional data
589      * @return true if the additional data is available
590      * @see #addAdditionalData(String, Object)
591      * @see #getAdditionalState(String)
592      * @see #getAdditionalData(String)
593      * @see #getAdditionalDataValues()
594      */
595     public boolean hasAdditionalData(final String name) {
596         return additional.getEntry(name) != null;
597     }
598 
599     /** Check if an additional state derivative is available.
600      * @param name name of the additional state derivative
601      * @return true if the additional state derivative is available
602      * @see #addAdditionalStateDerivative(String, double[])
603      * @see #getAdditionalStateDerivative(String)
604      * @see #getAdditionalStatesDerivatives()
605      * @since 11.1
606      */
607     public boolean hasAdditionalStateDerivative(final String name) {
608         return additionalDot.getEntry(name) != null;
609     }
610 
611     /** Check if two instances have the same set of additional states available.
612      * <p>
613      * Only the names and dimensions of the additional states are compared,
614      * not their values.
615      * </p>
616      * @param state state to compare to instance
617      * @exception MathIllegalStateException if an additional state does not have
618      * the same dimension in both states
619      */
620     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
621         throws MathIllegalStateException {
622 
623         // check instance additional states is a subset of the other one
624         for (final DataDictionary.Entry entry : additional.getData()) {
625             final Object other = state.additional.get(entry.getKey());
626             if (other == null || !entry.getValue().getClass().equals(other.getClass())) {
627                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
628                                           entry.getKey());
629             }
630             if (other instanceof double[] && ((double[]) other).length != ((double[]) entry.getValue()).length) {
631                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
632                                                     ((double[]) other).length, ((double[]) entry.getValue()).length);
633             }
634         }
635 
636         // check instance additional states derivatives is a subset of the other one
637         for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
638             final double[] other = state.additionalDot.get(entry.getKey());
639             if (other == null) {
640                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
641                                           entry.getKey());
642             }
643             if (other.length != entry.getValue().length) {
644                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
645                                                     other.length, entry.getValue().length);
646             }
647         }
648 
649         if (state.additional.size() > additional.size()) {
650             // the other state has more additional states
651             for (final DataDictionary.Entry entry : state.additional.getData()) {
652                 if (additional.getEntry(entry.getKey()) == null) {
653                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
654                                               entry.getKey());
655                 }
656             }
657         }
658 
659         if (state.additionalDot.size() > additionalDot.size()) {
660             // the other state has more additional states
661             for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
662                 if (additionalDot.getEntry(entry.getKey()) == null) {
663                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
664                                               entry.getKey());
665                 }
666             }
667         }
668 
669     }
670 
671     /**
672      * Get an additional state.
673      *
674      * @param name name of the additional state
675      * @return value of the additional state
676      * @see #hasAdditionalData(String)
677      * @see #getAdditionalDataValues()
678      */
679     public double[] getAdditionalState(final String name) {
680         final Object data = getAdditionalData(name);
681         if (!(data instanceof double[])) {
682             if (data instanceof Double) {
683                 return new double[] {(double) data};
684             } else {
685                 throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name);
686             }
687         }
688         return (double[]) data;
689     }
690 
691     /**
692      * Get an additional data.
693      *
694      * @param name name of the additional state
695      * @return value of the additional state
696      * @see #addAdditionalData(String, Object)
697      * @see #hasAdditionalData(String)
698      * @see #getAdditionalDataValues()
699      * @since 13.0
700      */
701     public Object getAdditionalData(final String name) {
702         final DataDictionary.Entry entry = additional.getEntry(name);
703         if (entry == null) {
704             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
705         }
706         return entry.getValue();
707     }
708 
709     /** Get an additional state derivative.
710      * @param name name of the additional state derivative
711      * @return value of the additional state derivative
712      * @see #addAdditionalStateDerivative(String, double[])
713      * @see #hasAdditionalStateDerivative(String)
714      * @see #getAdditionalStatesDerivatives()
715      * @since 11.1
716      */
717     public double[] getAdditionalStateDerivative(final String name) {
718         final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
719         if (entry == null) {
720             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
721         }
722         return entry.getValue();
723     }
724 
725     /** Get an unmodifiable map of additional data.
726      * @return unmodifiable map of additional data
727      * @see #addAdditionalData(String, Object)
728      * @see #hasAdditionalData(String)
729      * @see #getAdditionalState(String)
730      * @since 11.1
731      */
732     public DataDictionary getAdditionalDataValues() {
733         return additional;
734     }
735 
736     /** Get an unmodifiable map of additional states derivatives.
737      * @return unmodifiable map of additional states derivatives
738      * @see #addAdditionalStateDerivative(String, double[])
739      * @see #hasAdditionalStateDerivative(String)
740      * @see #getAdditionalStateDerivative(String)
741      * @since 11.1
742      */
743     public DoubleArrayDictionary getAdditionalStatesDerivatives() {
744         return additionalDot;
745     }
746 
747     /** Compute the transform from state defining frame to spacecraft frame.
748      * <p>The spacecraft frame origin is at the point defined by the orbit
749      * (or absolute position-velocity-acceleration), and its orientation is
750      * defined by the attitude.</p>
751      * @return transform from specified frame to current spacecraft frame
752      */
753     public Transform toTransform() {
754         final TimeStampedPVCoordinates pv = getPVCoordinates();
755         return new Transform(pv.getDate(), pv.negate(), attitude.getOrientation());
756     }
757 
758     /** Compute the static transform from state defining frame to spacecraft frame.
759      * @return static transform from specified frame to current spacecraft frame
760      * @see #toTransform()
761      * @since 12.0
762      */
763     public StaticTransform toStaticTransform() {
764         return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation());
765     }
766 
767     /** Get the position in orbit definition frame.
768      * @return position in orbit definition frame
769      * @since 12.0
770      * @see #getPVCoordinates()
771      */
772     public Vector3D getPosition() {
773         return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
774     }
775 
776     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
777      * <p>
778      * Compute the position and velocity of the satellite. This method caches its
779      * results, and recompute them only when the method is called with a new value
780      * for mu. The result is provided as a reference to the internally cached
781      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
782      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
783      * </p>
784      * @return pvCoordinates in orbit definition frame
785      */
786     public TimeStampedPVCoordinates getPVCoordinates() {
787         return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
788     }
789 
790     /** Get the position in given output frame.
791      * @param outputFrame frame in which position should be defined
792      * @return position in given output frame
793      * @since 12.0
794      * @see #getPVCoordinates(Frame)
795      */
796     public Vector3D getPosition(final Frame outputFrame) {
797         return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
798     }
799 
800     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
801      * <p>
802      * Compute the position and velocity of the satellite. This method caches its
803      * results, and recompute them only when the method is called with a new value
804      * for mu. The result is provided as a reference to the internally cached
805      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
806      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
807      * </p>
808      * @param outputFrame frame in which coordinates should be defined
809      * @return pvCoordinates in given output frame
810      */
811     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
812         return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
813     }
814 
815     /** Get the attitude.
816      * @return the attitude.
817      */
818     public Attitude getAttitude() {
819         return attitude;
820     }
821 
822     /** Gets the current mass.
823      * @return the mass (kg)
824      */
825     public double getMass() {
826         return mass;
827     }
828 
829     @Override
830     public String toString() {
831         return "SpacecraftState{" +
832                 "orbit=" + orbit +
833                 ", attitude=" + attitude +
834                 ", mass=" + mass +
835                 ", additional=" + additional +
836                 ", additionalDot=" + additionalDot +
837                 '}';
838     }
839 }