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