SpacecraftState.java

  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. import org.hipparchus.exception.LocalizedCoreFormats;
  19. import org.hipparchus.exception.MathIllegalStateException;
  20. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  21. import org.hipparchus.util.FastMath;
  22. import org.orekit.attitudes.Attitude;
  23. import org.orekit.attitudes.AttitudeProvider;
  24. import org.orekit.attitudes.FrameAlignedProvider;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitIllegalArgumentException;
  27. import org.orekit.errors.OrekitIllegalStateException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.frames.StaticTransform;
  31. import org.orekit.frames.Transform;
  32. import org.orekit.orbits.Orbit;
  33. import org.orekit.propagation.numerical.NumericalPropagator;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.time.TimeOffset;
  36. import org.orekit.time.TimeShiftable;
  37. import org.orekit.time.TimeStamped;
  38. import org.orekit.utils.AbsolutePVCoordinates;
  39. import org.orekit.utils.DataDictionary;
  40. import org.orekit.utils.DoubleArrayDictionary;
  41. import org.orekit.utils.TimeStampedPVCoordinates;

  42. /** This class is the representation of a complete state holding orbit, attitude
  43.  * and mass information at a given date, meant primarily for propagation.
  44.  *
  45.  * <p>It contains an {@link Orbit}, or an {@link AbsolutePVCoordinates} if there
  46.  * is no definite central body, plus the current mass and attitude at the intrinsic
  47.  * {@link AbsoluteDate}. Quantities are guaranteed to be consistent in terms
  48.  * of date and reference frame. The spacecraft state may also contain additional
  49.  * data, which are simply named.
  50.  * </p>
  51.  * <p>
  52.  * The state can be slightly shifted to close dates. This actual shift varies
  53.  * between {@link Orbit} and {@link AbsolutePVCoordinates}.
  54.  * For attitude it is a linear extrapolation taking the spin rate into account
  55.  * and no mass change. It is <em>not</em> intended as a replacement for proper
  56.  * orbit and attitude propagation but should be sufficient for either small
  57.  * time shifts or coarse accuracy.
  58.  * </p>
  59.  * <p>
  60.  * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
  61.  * </p>
  62.  * @see NumericalPropagator
  63.  * @author Fabien Maussion
  64.  * @author V&eacute;ronique Pommier-Maurussane
  65.  * @author Luc Maisonobe
  66.  */
  67. public class SpacecraftState implements TimeStamped, TimeShiftable<SpacecraftState> {

  68.     /** Default mass. */
  69.     public static final double DEFAULT_MASS = 1000.0;

  70.     /**
  71.      * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
  72.      * corresponds to sub-mm accuracy at LEO orbital velocities.
  73.      */
  74.     private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;

  75.     /** Orbital state. */
  76.     private final Orbit orbit;

  77.     /** Trajectory state, when it is not an orbit. */
  78.     private final AbsolutePVCoordinates absPva;

  79.     /** Attitude. */
  80.     private final Attitude attitude;

  81.     /** Current mass (kg). */
  82.     private final double mass;

  83.     /** Additional data, can be any object (String, double[], etc.). */
  84.     private final DataDictionary additional;

  85.     /** Additional states derivatives.
  86.      * @since 11.1
  87.      */
  88.     private final DoubleArrayDictionary additionalDot;

  89.     /** Build a spacecraft state from orbit only.
  90.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  91.      * @param orbit the orbit
  92.      */
  93.     public SpacecraftState(final Orbit orbit) {
  94.         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
  95.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  96.              DEFAULT_MASS, null, null);
  97.     }

  98.     /** Build a spacecraft state from orbit and attitude. Kept for performance.
  99.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  100.      * @param orbit the orbit
  101.      * @param attitude attitude
  102.      * @exception IllegalArgumentException if orbit and attitude dates
  103.      * or frames are not equal
  104.      */
  105.     public SpacecraftState(final Orbit orbit, final Attitude attitude)
  106.         throws IllegalArgumentException {
  107.         this(orbit, attitude, DEFAULT_MASS, null, null);
  108.     }

  109.     /** Create a new instance from orbit and mass.
  110.      * <p>Attitude law is set to an unspecified default attitude.</p>
  111.      * @param orbit the orbit
  112.      * @param mass the mass (kg)
  113.      * @deprecated since 13.0, use withXXX
  114.      */
  115.     @Deprecated
  116.     public SpacecraftState(final Orbit orbit, final double mass) {
  117.         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
  118.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  119.              mass, null, null);
  120.     }

  121.     /** Build a spacecraft state from orbit, attitude and mass.
  122.      * @param orbit the orbit
  123.      * @param attitude attitude
  124.      * @param mass the mass (kg)
  125.      * @exception IllegalArgumentException if orbit and attitude dates
  126.      * or frames are not equal
  127.      * @deprecated since 13.0, use withXXX
  128.      */
  129.     @Deprecated
  130.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
  131.         throws IllegalArgumentException {
  132.         this(orbit, attitude, mass, null, null);
  133.     }

  134.     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
  135.      * @param orbit the orbit
  136.      * @param attitude attitude
  137.      * @param mass the mass (kg)
  138.      * @param additional additional data (may be null if no additional states are available)
  139.      * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
  140.      * @exception IllegalArgumentException if orbit and attitude dates
  141.      * or frames are not equal
  142.      * @since 13.0
  143.      */
  144.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
  145.                            final DataDictionary additional, final DoubleArrayDictionary additionalDot)
  146.         throws IllegalArgumentException {
  147.         checkConsistency(orbit, attitude);
  148.         this.orbit      = orbit;
  149.         this.absPva     = null;
  150.         this.attitude   = attitude;
  151.         this.mass       = mass;
  152.         this.additional = additional == null ? new DataDictionary() : additional;
  153.         this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot);
  154.     }

  155.     /** Build a spacecraft state from position-velocity-acceleration only.
  156.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  157.      * @param absPva position-velocity-acceleration
  158.      */
  159.     public SpacecraftState(final AbsolutePVCoordinates absPva) {
  160.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  161.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  162.              DEFAULT_MASS, null, null);
  163.     }

  164.     /** Build a spacecraft state from position-velocity-acceleration and attitude. Kept for performance.
  165.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  166.      * @param absPva position-velocity-acceleration
  167.      * @param attitude attitude
  168.      * @exception IllegalArgumentException if orbit and attitude dates
  169.      * or frames are not equal
  170.      */
  171.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
  172.         throws IllegalArgumentException {
  173.         this(absPva, attitude, DEFAULT_MASS, null, null);
  174.     }

  175.     /** Create a new instance from position-velocity-acceleration and mass.
  176.      * <p>Attitude law is set to an unspecified default attitude.</p>
  177.      * @param absPva position-velocity-acceleration
  178.      * @param mass the mass (kg)
  179.      * @deprecated since 13.0, use withXXX
  180.      */
  181.     @Deprecated
  182.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
  183.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  184.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  185.              mass,  null, null);
  186.     }

  187.     /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
  188.      * @param absPva position-velocity-acceleration
  189.      * @param attitude attitude
  190.      * @param mass the mass (kg)
  191.      * @exception IllegalArgumentException if orbit and attitude dates
  192.      * or frames are not equal
  193.      * @deprecated since 13.0, use withXXX
  194.      */
  195.     @Deprecated
  196.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
  197.         throws IllegalArgumentException {
  198.         this(absPva, attitude, mass, null, null);
  199.     }

  200.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
  201.      * @param absPva position-velocity-acceleration
  202.      * @param attitude attitude
  203.      * @param mass the mass (kg)
  204.      * @param additional additional data (may be null if no additional data are available)
  205.      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
  206.      * @exception IllegalArgumentException if orbit and attitude dates
  207.      * or frames are not equal
  208.      * @since 13.0
  209.      */
  210.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
  211.                            final DataDictionary additional, final DoubleArrayDictionary additionalDot)
  212.         throws IllegalArgumentException {
  213.         checkConsistency(absPva, attitude);
  214.         this.orbit      = null;
  215.         this.absPva     = absPva;
  216.         this.attitude   = attitude;
  217.         this.mass       = mass;
  218.         this.additional = additional == null ? new DataDictionary() : new DataDictionary(additional);
  219.         this.additionalDot = additionalDot == null ? new DoubleArrayDictionary() : new DoubleArrayDictionary(additionalDot);
  220.     }

  221.     /**
  222.      * Create a new instance with input mass.
  223.      * @param newMass mass
  224.      * @return new state
  225.      * @since 13.0
  226.      */
  227.     public SpacecraftState withMass(final double newMass) {
  228.         if (isOrbitDefined()) {
  229.             return new SpacecraftState(orbit, attitude, newMass, additional, additionalDot);
  230.         } else {
  231.             return new SpacecraftState(absPva, attitude, newMass, additional, additionalDot);
  232.         }
  233.     }

  234.     /**
  235.      * Create a new instance with input attitude.
  236.      * @param newAttitude attitude
  237.      * @return new state
  238.      * @since 13.0
  239.      */
  240.     public SpacecraftState withAttitude(final Attitude newAttitude) {
  241.         if (isOrbitDefined()) {
  242.             return new SpacecraftState(orbit, newAttitude, mass, additional, additionalDot);
  243.         } else {
  244.             return new SpacecraftState(absPva, newAttitude, mass, additional, additionalDot);
  245.         }
  246.     }

  247.     /**
  248.      * Create a new instance with input additional data.
  249.      * @param dataDictionary additional data
  250.      * @return new state
  251.      * @since 13.0
  252.      */
  253.     public SpacecraftState withAdditionalData(final DataDictionary dataDictionary) {
  254.         if (isOrbitDefined()) {
  255.             return new SpacecraftState(orbit, attitude, mass, dataDictionary, additionalDot);
  256.         } else {
  257.             return new SpacecraftState(absPva, attitude, mass, dataDictionary, additionalDot);
  258.         }
  259.     }

  260.     /**
  261.      * Create a new instance with input additional data.
  262.      * @param additionalStateDerivatives additional state derivatives
  263.      * @return new state
  264.      * @since 13.0
  265.      */
  266.     public SpacecraftState withAdditionalStatesDerivatives(final DoubleArrayDictionary additionalStateDerivatives) {
  267.         if (isOrbitDefined()) {
  268.             return new SpacecraftState(orbit, attitude, mass, additional, additionalStateDerivatives);
  269.         } else {
  270.             return new SpacecraftState(absPva, attitude, mass, additional, additionalStateDerivatives);
  271.         }
  272.     }

  273.     /** Add an additional data.
  274.      * <p>
  275.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  276.      * so this method does <em>not</em> change the instance, but rather
  277.      * creates a new instance, which has the same orbit, attitude, mass
  278.      * and additional states as the original instance, except it also
  279.      * has the specified state. If the original instance already had an
  280.      * additional data with the same name, it will be overridden. If it
  281.      * did not have any additional state with that name, the new instance
  282.      * will have one more additional state than the original instance.
  283.      * </p>
  284.      * @param name name of the additional data (names containing "orekit"
  285.      * with any case are reserved for the library internal use)
  286.      * @param value value of the additional data
  287.      * @return a new instance, with the additional data added
  288.      * @see #hasAdditionalData(String)
  289.      * @see #getAdditionalData(String)
  290.      * @see #getAdditionalDataValues()
  291.      * @since 13.0
  292.      */
  293.     public SpacecraftState addAdditionalData(final String name, final Object value) {
  294.         final DataDictionary newDict = new DataDictionary(additional);
  295.         if (value instanceof double[]) {
  296.             newDict.put(name, ((double[]) value).clone());
  297.         } else if (value instanceof Double) {
  298.             newDict.put(name, new double[] {(double) value});
  299.         } else {
  300.             newDict.put(name, value);
  301.         }
  302.         return withAdditionalData(newDict);
  303.     }

  304.     /** Add an additional state derivative.
  305.      * <p>
  306.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  307.      * so this method does <em>not</em> change the instance, but rather
  308.      * creates a new instance, which has the same components as the original
  309.      * instance, except it also has the specified state derivative. If the
  310.      * original instance already had an additional state derivative with the
  311.      * same name, it will be overridden. If it did not have any additional
  312.      * state derivative with that name, the new instance will have one more
  313.      * additional state derivative than the original instance.
  314.      * </p>
  315.      * @param name name of the additional state derivative (names containing "orekit"
  316.      * with any case are reserved for the library internal use)
  317.      * @param value value of the additional state derivative
  318.      * @return a new instance, with the additional state added
  319.      * @see #hasAdditionalStateDerivative(String)
  320.      * @see #getAdditionalStateDerivative(String)
  321.      * @see #getAdditionalStatesDerivatives()
  322.      * @since 11.1
  323.      */
  324.     public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
  325.         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
  326.         newDict.put(name, value.clone());
  327.         return withAdditionalStatesDerivatives(newDict);
  328.     }

  329.     /** Check orbit and attitude dates are equal.
  330.      * @param orbit the orbit
  331.      * @param attitude attitude
  332.      * @exception IllegalArgumentException if orbit and attitude dates
  333.      * are not equal
  334.      */
  335.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  336.         throws IllegalArgumentException {
  337.         checkDateAndFrameConsistency(attitude, orbit.getDate(), orbit.getFrame());
  338.     }

  339.     /** Defines provider for default Attitude when not passed to constructor.
  340.      * Currently chosen arbitrarily as aligned with input frame.
  341.      * It is also used in {@link FieldSpacecraftState}.
  342.      * @param frame reference frame
  343.      * @return default attitude provider
  344.      * @since 12.0
  345.      */
  346.     static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) {
  347.         return new FrameAlignedProvider(frame);
  348.     }

  349.     /** Check if the state contains an orbit part.
  350.      * <p>
  351.      * A state contains either an {@link AbsolutePVCoordinates absolute
  352.      * position-velocity-acceleration} or an {@link Orbit orbit}.
  353.      * </p>
  354.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  355.      * will not throw an exception), or false if the state contains an
  356.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  357.      * will not throw an exception)
  358.      */
  359.     public boolean isOrbitDefined() {
  360.         return orbit != null;
  361.     }

  362.     /** Check AbsolutePVCoordinates and attitude dates are equal.
  363.      * @param absPva position-velocity-acceleration
  364.      * @param attitude attitude
  365.      * @exception IllegalArgumentException if orbit and attitude dates
  366.      * are not equal
  367.      */
  368.     private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
  369.         throws IllegalArgumentException {
  370.         checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame());
  371.     }

  372.     /** Check attitude frame and epoch.
  373.      * @param attitude attitude
  374.      * @param date epoch to verify
  375.      * @param frame frame to verify
  376.      */
  377.     private static void checkDateAndFrameConsistency(final Attitude attitude, final AbsoluteDate date, final Frame frame) {
  378.         if (FastMath.abs(date.durationFrom(attitude.getDate())) >
  379.                 DATE_INCONSISTENCY_THRESHOLD) {
  380.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  381.                     date, attitude.getDate());
  382.         }
  383.         if (frame != attitude.getReferenceFrame()) {
  384.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  385.                     frame.getName(),
  386.                     attitude.getReferenceFrame().getName());
  387.         }
  388.     }

  389.     /** Get a time-shifted state.
  390.      * <p>
  391.      * The state can be slightly shifted to close dates. This shift is based on
  392.      * simple models. For orbits, the model is a Keplerian one if no derivatives
  393.      * are available in the orbit, or Keplerian plus quadratic effect of the
  394.      * non-Keplerian acceleration if derivatives are available. For attitude,
  395.      * a polynomial model is used. Neither mass nor additional states change.
  396.      * Shifting is <em>not</em> intended as a replacement for proper orbit
  397.      * and attitude propagation but should be sufficient for small time shifts
  398.      * or coarse accuracy.
  399.      * </p>
  400.      * <p>
  401.      * As a rough order of magnitude, the following table shows the extrapolation
  402.      * errors obtained between this simple shift method and an {@link
  403.      * NumericalPropagator numerical
  404.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  405.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  406.      * Beware that these results will be different for other orbits.
  407.      * </p>
  408.      * <table border="1">
  409.      * <caption>Extrapolation Error</caption>
  410.      * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
  411.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  412.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  413.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  414.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  415.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  416.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  417.      * </table>
  418.      * @param dt time shift in seconds
  419.      * @return a new state, shifted with respect to the instance (which is immutable)
  420.      * except for the mass and additional states which stay unchanged
  421.      */
  422.     @Override
  423.     public SpacecraftState shiftedBy(final double dt) {
  424.         if (isOrbitDefined()) {
  425.             return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  426.                                        mass, shiftAdditional(dt), additionalDot);
  427.         } else {
  428.             return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  429.                                        mass, shiftAdditional(dt), additionalDot);
  430.         }
  431.     }

  432.     /** Get a time-shifted state.
  433.      * <p>
  434.      * The state can be slightly shifted to close dates. This shift is based on
  435.      * simple models. For orbits, the model is a Keplerian one if no derivatives
  436.      * are available in the orbit, or Keplerian plus quadratic effect of the
  437.      * non-Keplerian acceleration if derivatives are available. For attitude,
  438.      * a polynomial model is used. Neither mass nor additional states change.
  439.      * Shifting is <em>not</em> intended as a replacement for proper orbit
  440.      * and attitude propagation but should be sufficient for small time shifts
  441.      * or coarse accuracy.
  442.      * </p>
  443.      * <p>
  444.      * As a rough order of magnitude, the following table shows the extrapolation
  445.      * errors obtained between this simple shift method and an {@link
  446.      * NumericalPropagator numerical
  447.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  448.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  449.      * Beware that these results will be different for other orbits.
  450.      * </p>
  451.      * <table border="1">
  452.      * <caption>Extrapolation Error</caption>
  453.      * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
  454.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  455.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  456.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  457.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  458.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  459.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  460.      * </table>
  461.      * @param dt time shift in seconds
  462.      * @return a new state, shifted with respect to the instance (which is immutable)
  463.      * except for the mass and additional states which stay unchanged
  464.      * @since 13.0
  465.      */
  466.     @Override
  467.     public SpacecraftState shiftedBy(final TimeOffset dt) {
  468.         if (isOrbitDefined()) {
  469.             return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  470.                                        mass, shiftAdditional(dt.toDouble()), additionalDot);
  471.         } else {
  472.             return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  473.                                        mass, shiftAdditional(dt.toDouble()), additionalDot);
  474.         }
  475.     }

  476.     /** Shift additional states.
  477.      * @param dt time shift in seconds
  478.      * @return shifted additional states
  479.      * @since 11.1.1
  480.      */
  481.     private DataDictionary shiftAdditional(final double dt) {

  482.         // fast handling when there are no derivatives at all
  483.         if (additionalDot.size() == 0) {
  484.             return additional;
  485.         }

  486.         // there are derivatives, we need to take them into account in the additional state
  487.         final DataDictionary shifted = new DataDictionary(additional);
  488.         for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
  489.             final DataDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
  490.             if (entry != null) {
  491.                 entry.scaledIncrement(dt, dotEntry);
  492.             }
  493.         }

  494.         return shifted;

  495.     }

  496.     /** Get the absolute position-velocity-acceleration.
  497.      * <p>
  498.      * A state contains either an {@link AbsolutePVCoordinates absolute
  499.      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
  500.      * one is present can be checked using {@link #isOrbitDefined()}.
  501.      * </p>
  502.      * @return absolute position-velocity-acceleration
  503.      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
  504.      * which mean the state rather contains an {@link Orbit}
  505.      * @see #isOrbitDefined()
  506.      * @see #getOrbit()
  507.      */
  508.     public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
  509.         if (isOrbitDefined()) {
  510.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
  511.         }
  512.         return absPva;
  513.     }

  514.     /** Get the current orbit.
  515.      * <p>
  516.      * A state contains either an {@link AbsolutePVCoordinates absolute
  517.      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
  518.      * one is present can be checked using {@link #isOrbitDefined()}.
  519.      * </p>
  520.      * @return the orbit
  521.      * @exception OrekitIllegalStateException if orbit is null,
  522.      * which means the state rather contains an {@link AbsolutePVCoordinates absolute
  523.      * position-velocity-acceleration}
  524.      * @see #isOrbitDefined()
  525.      * @see #getAbsPVA()
  526.      */
  527.     public Orbit getOrbit() throws OrekitIllegalStateException {
  528.         if (orbit == null) {
  529.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
  530.         }
  531.         return orbit;
  532.     }

  533.     /** {@inheritDoc} */
  534.     @Override
  535.     public AbsoluteDate getDate() {
  536.         return (absPva == null) ? orbit.getDate() : absPva.getDate();
  537.     }

  538.     /** Get the defining frame.
  539.      * @return the frame in which state is defined
  540.      */
  541.     public Frame getFrame() {
  542.         return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
  543.     }

  544.     /** Check if an additional data is available.
  545.      * @param name name of the additional data
  546.      * @return true if the additional data is available
  547.      * @see #addAdditionalData(String, Object)
  548.      * @see #getAdditionalState(String)
  549.      * @see #getAdditionalData(String)
  550.      * @see #getAdditionalDataValues()
  551.      */
  552.     public boolean hasAdditionalData(final String name) {
  553.         return additional.getEntry(name) != null;
  554.     }

  555.     /** Check if an additional state derivative is available.
  556.      * @param name name of the additional state derivative
  557.      * @return true if the additional state derivative is available
  558.      * @see #addAdditionalStateDerivative(String, double[])
  559.      * @see #getAdditionalStateDerivative(String)
  560.      * @see #getAdditionalStatesDerivatives()
  561.      * @since 11.1
  562.      */
  563.     public boolean hasAdditionalStateDerivative(final String name) {
  564.         return additionalDot.getEntry(name) != null;
  565.     }

  566.     /** Check if two instances have the same set of additional states available.
  567.      * <p>
  568.      * Only the names and dimensions of the additional states are compared,
  569.      * not their values.
  570.      * </p>
  571.      * @param state state to compare to instance
  572.      * @exception MathIllegalStateException if an additional state does not have
  573.      * the same dimension in both states
  574.      */
  575.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  576.         throws MathIllegalStateException {

  577.         // check instance additional states is a subset of the other one
  578.         for (final DataDictionary.Entry entry : additional.getData()) {
  579.             final Object other = state.additional.get(entry.getKey());
  580.             if (other == null || !entry.getValue().getClass().equals(other.getClass())) {
  581.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  582.                                           entry.getKey());
  583.             }
  584.             if (other instanceof double[] && ((double[]) other).length != ((double[]) entry.getValue()).length) {
  585.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  586.                                                     ((double[]) other).length, ((double[]) entry.getValue()).length);
  587.             }
  588.         }

  589.         // check instance additional states derivatives is a subset of the other one
  590.         for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
  591.             final double[] other = state.additionalDot.get(entry.getKey());
  592.             if (other == null) {
  593.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  594.                                           entry.getKey());
  595.             }
  596.             if (other.length != entry.getValue().length) {
  597.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  598.                                                     other.length, entry.getValue().length);
  599.             }
  600.         }

  601.         if (state.additional.size() > additional.size()) {
  602.             // the other state has more additional states
  603.             for (final DataDictionary.Entry entry : state.additional.getData()) {
  604.                 if (additional.getEntry(entry.getKey()) == null) {
  605.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  606.                                               entry.getKey());
  607.                 }
  608.             }
  609.         }

  610.         if (state.additionalDot.size() > additionalDot.size()) {
  611.             // the other state has more additional states
  612.             for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
  613.                 if (additionalDot.getEntry(entry.getKey()) == null) {
  614.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  615.                                               entry.getKey());
  616.                 }
  617.             }
  618.         }

  619.     }

  620.     /**
  621.      * Get an additional state.
  622.      *
  623.      * @param name name of the additional state
  624.      * @return value of the additional state
  625.      * @see #hasAdditionalData(String)
  626.      * @see #getAdditionalDataValues()
  627.      */
  628.     public double[] getAdditionalState(final String name) {
  629.         final Object data = getAdditionalData(name);
  630.         if (!(data instanceof double[])) {
  631.             if (data instanceof Double) {
  632.                 return new double[] {(double) data};
  633.             } else {
  634.                 throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name);
  635.             }
  636.         }
  637.         return (double[]) data;
  638.     }

  639.     /**
  640.      * Get an additional data.
  641.      *
  642.      * @param name name of the additional state
  643.      * @return value of the additional state
  644.      * @see #addAdditionalData(String, Object)
  645.      * @see #hasAdditionalData(String)
  646.      * @see #getAdditionalDataValues()
  647.      * @since 13.0
  648.      */
  649.     public Object getAdditionalData(final String name) {
  650.         final DataDictionary.Entry entry = additional.getEntry(name);
  651.         if (entry == null) {
  652.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
  653.         }
  654.         return entry.getValue();
  655.     }

  656.     /** Get an additional state derivative.
  657.      * @param name name of the additional state derivative
  658.      * @return value of the additional state derivative
  659.      * @see #addAdditionalStateDerivative(String, double[])
  660.      * @see #hasAdditionalStateDerivative(String)
  661.      * @see #getAdditionalStatesDerivatives()
  662.      * @since 11.1
  663.      */
  664.     public double[] getAdditionalStateDerivative(final String name) {
  665.         final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
  666.         if (entry == null) {
  667.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
  668.         }
  669.         return entry.getValue();
  670.     }

  671.     /** Get an unmodifiable map of additional data.
  672.      * @return unmodifiable map of additional data
  673.      * @see #addAdditionalData(String, Object)
  674.      * @see #hasAdditionalData(String)
  675.      * @see #getAdditionalState(String)
  676.      * @since 11.1
  677.      */
  678.     public DataDictionary getAdditionalDataValues() {
  679.         return additional;
  680.     }

  681.     /** Get an unmodifiable map of additional states derivatives.
  682.      * @return unmodifiable map of additional states derivatives
  683.      * @see #addAdditionalStateDerivative(String, double[])
  684.      * @see #hasAdditionalStateDerivative(String)
  685.      * @see #getAdditionalStateDerivative(String)
  686.      * @since 11.1
  687.      */
  688.     public DoubleArrayDictionary getAdditionalStatesDerivatives() {
  689.         return additionalDot;
  690.     }

  691.     /** Compute the transform from state defining frame to spacecraft frame.
  692.      * <p>The spacecraft frame origin is at the point defined by the orbit
  693.      * (or absolute position-velocity-acceleration), and its orientation is
  694.      * defined by the attitude.</p>
  695.      * @return transform from specified frame to current spacecraft frame
  696.      */
  697.     public Transform toTransform() {
  698.         final TimeStampedPVCoordinates pv = getPVCoordinates();
  699.         return new Transform(pv.getDate(), pv.negate(), attitude.getOrientation());
  700.     }

  701.     /** Compute the static transform from state defining frame to spacecraft frame.
  702.      * @return static transform from specified frame to current spacecraft frame
  703.      * @see #toTransform()
  704.      * @since 12.0
  705.      */
  706.     public StaticTransform toStaticTransform() {
  707.         return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation());
  708.     }

  709.     /** Get the position in orbit definition frame.
  710.      * @return position in orbit definition frame
  711.      * @since 12.0
  712.      * @see #getPVCoordinates()
  713.      */
  714.     public Vector3D getPosition() {
  715.         return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
  716.     }

  717.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  718.      * <p>
  719.      * Compute the position and velocity of the satellite. This method caches its
  720.      * results, and recompute them only when the method is called with a new value
  721.      * for mu. The result is provided as a reference to the internally cached
  722.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  723.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  724.      * </p>
  725.      * @return pvCoordinates in orbit definition frame
  726.      */
  727.     public TimeStampedPVCoordinates getPVCoordinates() {
  728.         return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  729.     }

  730.     /** Get the position in given output frame.
  731.      * @param outputFrame frame in which position should be defined
  732.      * @return position in given output frame
  733.      * @since 12.0
  734.      * @see #getPVCoordinates(Frame)
  735.      */
  736.     public Vector3D getPosition(final Frame outputFrame) {
  737.         return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
  738.     }

  739.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  740.      * <p>
  741.      * Compute the position and velocity of the satellite. This method caches its
  742.      * results, and recompute them only when the method is called with a new value
  743.      * for mu. The result is provided as a reference to the internally cached
  744.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  745.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  746.      * </p>
  747.      * @param outputFrame frame in which coordinates should be defined
  748.      * @return pvCoordinates in given output frame
  749.      */
  750.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  751.         return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  752.     }

  753.     /** Get the attitude.
  754.      * @return the attitude.
  755.      */
  756.     public Attitude getAttitude() {
  757.         return attitude;
  758.     }

  759.     /** Gets the current mass.
  760.      * @return the mass (kg)
  761.      */
  762.     public double getMass() {
  763.         return mass;
  764.     }

  765.     @Override
  766.     public String toString() {
  767.         return "SpacecraftState{" +
  768.                 "orbit=" + orbit +
  769.                 ", attitude=" + attitude +
  770.                 ", mass=" + mass +
  771.                 ", additional=" + additional +
  772.                 ", additionalDot=" + additionalDot +
  773.                 '}';
  774.     }
  775. }