SpacecraftState.java

  1. /* Copyright 2002-2024 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 java.io.Serializable;

  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalStateException;
  21. import org.hipparchus.geometry.euclidean.threed.Rotation;
  22. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.attitudes.Attitude;
  25. import org.orekit.attitudes.AttitudeProvider;
  26. import org.orekit.attitudes.FrameAlignedProvider;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitIllegalArgumentException;
  29. import org.orekit.errors.OrekitIllegalStateException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.frames.Frame;
  32. import org.orekit.frames.StaticTransform;
  33. import org.orekit.frames.Transform;
  34. import org.orekit.orbits.Orbit;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.TimeShiftable;
  37. import org.orekit.time.TimeStamped;
  38. import org.orekit.utils.AbsolutePVCoordinates;
  39. import org.orekit.utils.DoubleArrayDictionary;
  40. import org.orekit.utils.TimeStampedAngularCoordinates;
  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.  * states, which are simply named double arrays which can hold any user-defined
  50.  * data.
  51.  * </p>
  52.  * <p>
  53.  * The state can be slightly shifted to close dates. This actual shift varies
  54.  * between {@link Orbit} and {@link AbsolutePVCoordinates}.
  55.  * For attitude it is a linear extrapolation taking the spin rate into account
  56.  * and no mass change. It is <em>not</em> intended as a replacement for proper
  57.  * orbit and attitude propagation but should be sufficient for either small
  58.  * time shifts or coarse accuracy.
  59.  * </p>
  60.  * <p>
  61.  * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
  62.  * </p>
  63.  * @see org.orekit.propagation.numerical.NumericalPropagator
  64.  * @author Fabien Maussion
  65.  * @author V&eacute;ronique Pommier-Maurussane
  66.  * @author Luc Maisonobe
  67.  */
  68. public class SpacecraftState
  69.     implements TimeStamped, TimeShiftable<SpacecraftState>, Serializable {

  70.     /** Default mass. */
  71.     public static final double DEFAULT_MASS = 1000.0;

  72.     /** Serializable UID. */
  73.     private static final long serialVersionUID = 20211119L;

  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.     /** Orbital state. */
  80.     private final Orbit orbit;

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

  83.     /** Attitude. */
  84.     private final Attitude attitude;

  85.     /** Current mass (kg). */
  86.     private final double mass;

  87.     /** Additional states. */
  88.     private final DoubleArrayDictionary additional;

  89.     /** Additional states derivatives.
  90.      * @since 11.1
  91.      */
  92.     private final DoubleArrayDictionary additionalDot;

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

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

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

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

  134.     /** Build a spacecraft state from orbit and additional states.
  135.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  136.      * @param orbit the orbit
  137.      * @param additional additional states
  138.      * @since 11.1
  139.      */
  140.     public SpacecraftState(final Orbit orbit, final DoubleArrayDictionary additional) {
  141.         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
  142.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  143.              DEFAULT_MASS, additional);
  144.     }

  145.     /** Build a spacecraft state from orbit, attitude and additional states.
  146.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  147.      * @param orbit the orbit
  148.      * @param attitude attitude
  149.      * @param additional additional states
  150.      * @exception IllegalArgumentException if orbit and attitude dates
  151.      * or frames are not equal
  152.      * @since 11.1
  153.      */
  154.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final DoubleArrayDictionary additional)
  155.         throws IllegalArgumentException {
  156.         this(orbit, attitude, DEFAULT_MASS, additional);
  157.     }

  158.     /** Create a new instance from orbit, mass and additional states.
  159.      * <p>Attitude law is set to an unspecified default attitude.</p>
  160.      * @param orbit the orbit
  161.      * @param mass the mass (kg)
  162.      * @param additional additional states
  163.      * @since 11.1
  164.      */
  165.     public SpacecraftState(final Orbit orbit, final double mass, final DoubleArrayDictionary additional) {
  166.         this(orbit, getDefaultAttitudeProvider(orbit.getFrame())
  167.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  168.              mass, additional);
  169.     }

  170.     /** Build a spacecraft state from orbit, attitude, mass and additional states.
  171.      * @param orbit the orbit
  172.      * @param attitude attitude
  173.      * @param mass the mass (kg)
  174.      * @param additional additional states (may be null if no additional states are available)
  175.      * @exception IllegalArgumentException if orbit and attitude dates
  176.      * or frames are not equal
  177.      * @since 11.1
  178.      */
  179.     public SpacecraftState(final Orbit orbit, final Attitude attitude,
  180.                            final double mass, final DoubleArrayDictionary additional)
  181.         throws IllegalArgumentException {
  182.         this(orbit, attitude, mass, additional, null);
  183.     }

  184.     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
  185.      * @param orbit the orbit
  186.      * @param attitude attitude
  187.      * @param mass the mass (kg)
  188.      * @param additional additional states (may be null if no additional states are available)
  189.      * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
  190.      * @exception IllegalArgumentException if orbit and attitude dates
  191.      * or frames are not equal
  192.      * @since 11.1
  193.      */
  194.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
  195.                            final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
  196.         throws IllegalArgumentException {
  197.         checkConsistency(orbit, attitude);
  198.         this.orbit      = orbit;
  199.         this.absPva     = null;
  200.         this.attitude   = attitude;
  201.         this.mass       = mass;
  202.         if (additional == null) {
  203.             this.additional = new DoubleArrayDictionary();
  204.         } else {
  205.             this.additional = additional;
  206.         }
  207.         if (additionalDot == null) {
  208.             this.additionalDot = new DoubleArrayDictionary();
  209.         } else {
  210.             this.additionalDot = new DoubleArrayDictionary(additionalDot);
  211.         }
  212.     }

  213.     /** Build a spacecraft state from position-velocity-acceleration only.
  214.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  215.      * @param absPva position-velocity-acceleration
  216.      */
  217.     public SpacecraftState(final AbsolutePVCoordinates absPva) {
  218.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  219.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  220.              DEFAULT_MASS, (DoubleArrayDictionary) null);
  221.     }

  222.     /** Build a spacecraft state from position-velocity-acceleration and attitude.
  223.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  224.      * @param absPva position-velocity-acceleration
  225.      * @param attitude attitude
  226.      * @exception IllegalArgumentException if orbit and attitude dates
  227.      * or frames are not equal
  228.      */
  229.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
  230.         throws IllegalArgumentException {
  231.         this(absPva, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null);
  232.     }

  233.     /** Create a new instance from position-velocity-acceleration and mass.
  234.      * <p>Attitude law is set to an unspecified default attitude.</p>
  235.      * @param absPva position-velocity-acceleration
  236.      * @param mass the mass (kg)
  237.      */
  238.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
  239.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  240.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  241.              mass, (DoubleArrayDictionary) null);
  242.     }

  243.     /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
  244.      * @param absPva position-velocity-acceleration
  245.      * @param attitude attitude
  246.      * @param mass the mass (kg)
  247.      * @exception IllegalArgumentException if orbit and attitude dates
  248.      * or frames are not equal
  249.      */
  250.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
  251.         throws IllegalArgumentException {
  252.         this(absPva, attitude, mass, (DoubleArrayDictionary) null);
  253.     }

  254.     /** Build a spacecraft state from position-velocity-acceleration and additional states.
  255.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  256.      * @param absPva position-velocity-acceleration
  257.      * @param additional additional states
  258.      * @since 11.1
  259.      */
  260.     public SpacecraftState(final AbsolutePVCoordinates absPva, final DoubleArrayDictionary additional) {
  261.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  262.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  263.              DEFAULT_MASS, additional);
  264.     }

  265.     /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states.
  266.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  267.      * @param absPva position-velocity-acceleration
  268.      * @param attitude attitude
  269.      * @param additional additional states
  270.      * @exception IllegalArgumentException if orbit and attitude dates
  271.      * or frames are not equal
  272.      * @since 11.1
  273.      */
  274.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final DoubleArrayDictionary additional)
  275.         throws IllegalArgumentException {
  276.         this(absPva, attitude, DEFAULT_MASS, additional);
  277.     }

  278.     /** Create a new instance from position-velocity-acceleration, mass and additional states.
  279.      * <p>Attitude law is set to an unspecified default attitude.</p>
  280.      * @param absPva position-velocity-acceleration
  281.      * @param mass the mass (kg)
  282.      * @param additional additional states
  283.      * @since 11.1
  284.      */
  285.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final DoubleArrayDictionary additional) {
  286.         this(absPva, getDefaultAttitudeProvider(absPva.getFrame())
  287.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  288.              mass, additional);
  289.     }

  290.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states.
  291.      * @param absPva position-velocity-acceleration
  292.      * @param attitude attitude
  293.      * @param mass the mass (kg)
  294.      * @param additional additional states (may be null if no additional states are available)
  295.      * @exception IllegalArgumentException if orbit and attitude dates
  296.      * or frames are not equal
  297.      * @since 11.1
  298.      */
  299.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
  300.                            final double mass, final DoubleArrayDictionary additional)
  301.         throws IllegalArgumentException {
  302.         this(absPva, attitude, mass, additional, null);
  303.     }

  304.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
  305.      * @param absPva position-velocity-acceleration
  306.      * @param attitude attitude
  307.      * @param mass the mass (kg)
  308.      * @param additional additional states (may be null if no additional states are available)
  309.      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
  310.      * @exception IllegalArgumentException if orbit and attitude dates
  311.      * or frames are not equal
  312.      * @since 11.1
  313.      */
  314.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
  315.                            final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
  316.         throws IllegalArgumentException {
  317.         checkConsistency(absPva, attitude);
  318.         this.orbit      = null;
  319.         this.absPva     = absPva;
  320.         this.attitude   = attitude;
  321.         this.mass       = mass;
  322.         if (additional == null) {
  323.             this.additional = new DoubleArrayDictionary();
  324.         } else {
  325.             this.additional = new DoubleArrayDictionary(additional);
  326.         }
  327.         if (additionalDot == null) {
  328.             this.additionalDot = new DoubleArrayDictionary();
  329.         } else {
  330.             this.additionalDot = new DoubleArrayDictionary(additionalDot);
  331.         }
  332.     }

  333.     /** Add an additional state.
  334.      * <p>
  335.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  336.      * so this method does <em>not</em> change the instance, but rather
  337.      * creates a new instance, which has the same orbit, attitude, mass
  338.      * and additional states as the original instance, except it also
  339.      * has the specified state. If the original instance already had an
  340.      * additional state with the same name, it will be overridden. If it
  341.      * did not have any additional state with that name, the new instance
  342.      * will have one more additional state than the original instance.
  343.      * </p>
  344.      * @param name name of the additional state (names containing "orekit"
  345.      * with any case are reserved for the library internal use)
  346.      * @param value value of the additional state
  347.      * @return a new instance, with the additional state added
  348.      * @see #hasAdditionalState(String)
  349.      * @see #getAdditionalState(String)
  350.      * @see #getAdditionalStatesValues()
  351.      */
  352.     public SpacecraftState addAdditionalState(final String name, final double... value) {
  353.         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additional);
  354.         newDict.put(name, value.clone());
  355.         if (isOrbitDefined()) {
  356.             return new SpacecraftState(orbit, attitude, mass, newDict, additionalDot);
  357.         } else {
  358.             return new SpacecraftState(absPva, attitude, mass, newDict, additionalDot);
  359.         }
  360.     }

  361.     /** Add an additional state derivative.
  362.      * <p>
  363.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  364.      * so this method does <em>not</em> change the instance, but rather
  365.      * creates a new instance, which has the same components as the original
  366.      * instance, except it also has the specified state derivative. If the
  367.      * original instance already had an additional state derivative with the
  368.      * same name, it will be overridden. If it did not have any additional
  369.      * state derivative with that name, the new instance will have one more
  370.      * additional state derivative than the original instance.
  371.      * </p>
  372.      * @param name name of the additional state derivative (names containing "orekit"
  373.      * with any case are reserved for the library internal use)
  374.      * @param value value of the additional state derivative
  375.      * @return a new instance, with the additional state added
  376.      * @see #hasAdditionalStateDerivative(String)
  377.      * @see #getAdditionalStateDerivative(String)
  378.      * @see #getAdditionalStatesDerivatives()
  379.      * @since 11.1
  380.      */
  381.     public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
  382.         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
  383.         newDict.put(name, value.clone());
  384.         if (isOrbitDefined()) {
  385.             return new SpacecraftState(orbit, attitude, mass, additional, newDict);
  386.         } else {
  387.             return new SpacecraftState(absPva, attitude, mass, additional, newDict);
  388.         }
  389.     }

  390.     /** Check orbit and attitude dates are equal.
  391.      * @param orbit the orbit
  392.      * @param attitude attitude
  393.      * @exception IllegalArgumentException if orbit and attitude dates
  394.      * are not equal
  395.      */
  396.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  397.         throws IllegalArgumentException {
  398.         if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
  399.             DATE_INCONSISTENCY_THRESHOLD) {
  400.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  401.                                                      orbit.getDate(), attitude.getDate());
  402.         }
  403.         if (orbit.getFrame() != attitude.getReferenceFrame()) {
  404.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  405.                                                      orbit.getFrame().getName(),
  406.                                                      attitude.getReferenceFrame().getName());
  407.         }
  408.     }

  409.     /** Defines provider for default Attitude when not passed to constructor.
  410.      * Currently chosen arbitrarily as aligned with input frame.
  411.      * It is also used in {@link FieldSpacecraftState}.
  412.      * @param frame reference frame
  413.      * @return default attitude provider
  414.      * @since 12.0
  415.      */
  416.     static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) {
  417.         return new FrameAlignedProvider(frame);
  418.     }

  419.     /** Check if the state contains an orbit part.
  420.      * <p>
  421.      * A state contains either an {@link AbsolutePVCoordinates absolute
  422.      * position-velocity-acceleration} or an {@link Orbit orbit}.
  423.      * </p>
  424.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  425.      * will not throw an exception), or false if the state contains an
  426.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  427.      * will not throw an exception)
  428.      */
  429.     public boolean isOrbitDefined() {
  430.         return orbit != null;
  431.     }

  432.     /** Check AbsolutePVCoordinates and attitude dates are equal.
  433.      * @param absPva position-velocity-acceleration
  434.      * @param attitude attitude
  435.      * @exception IllegalArgumentException if orbit and attitude dates
  436.      * are not equal
  437.      */
  438.     private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
  439.         throws IllegalArgumentException {
  440.         if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) >
  441.             DATE_INCONSISTENCY_THRESHOLD) {
  442.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  443.                                                      absPva.getDate(), attitude.getDate());
  444.         }
  445.         if (absPva.getFrame() != attitude.getReferenceFrame()) {
  446.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  447.                                                      absPva.getFrame().getName(),
  448.                                                      attitude.getReferenceFrame().getName());
  449.         }
  450.     }

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

  494.     /** Shift additional states.
  495.      * @param dt time shift in seconds
  496.      * @return shifted additional states
  497.      * @since 11.1.1
  498.      */
  499.     private DoubleArrayDictionary shiftAdditional(final double dt) {

  500.         // fast handling when there are no derivatives at all
  501.         if (additionalDot.size() == 0) {
  502.             return additional;
  503.         }

  504.         // there are derivatives, we need to take them into account in the additional state
  505.         final DoubleArrayDictionary shifted = new DoubleArrayDictionary(additional);
  506.         for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
  507.             final DoubleArrayDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
  508.             if (entry != null) {
  509.                 entry.scaledIncrement(dt, dotEntry);
  510.             }
  511.         }

  512.         return shifted;

  513.     }

  514.     /** Get the absolute position-velocity-acceleration.
  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 absolute position-velocity-acceleration
  521.      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
  522.      * which mean the state rather contains an {@link Orbit}
  523.      * @see #isOrbitDefined()
  524.      * @see #getOrbit()
  525.      */
  526.     public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
  527.         if (isOrbitDefined()) {
  528.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
  529.         }
  530.         return absPva;
  531.     }

  532.     /** Get the current orbit.
  533.      * <p>
  534.      * A state contains either an {@link AbsolutePVCoordinates absolute
  535.      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
  536.      * one is present can be checked using {@link #isOrbitDefined()}.
  537.      * </p>
  538.      * @return the orbit
  539.      * @exception OrekitIllegalStateException if orbit is null,
  540.      * which means the state rather contains an {@link AbsolutePVCoordinates absolute
  541.      * position-velocity-acceleration}
  542.      * @see #isOrbitDefined()
  543.      * @see #getAbsPVA()
  544.      */
  545.     public Orbit getOrbit() throws OrekitIllegalStateException {
  546.         if (orbit == null) {
  547.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
  548.         }
  549.         return orbit;
  550.     }

  551.     /** {@inheritDoc} */
  552.     @Override
  553.     public AbsoluteDate getDate() {
  554.         return (absPva == null) ? orbit.getDate() : absPva.getDate();
  555.     }

  556.     /** Get the defining frame.
  557.      * @return the frame in which state is defined
  558.      */
  559.     public Frame getFrame() {
  560.         return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
  561.     }

  562.     /** Check if an additional state is available.
  563.      * @param name name of the additional state
  564.      * @return true if the additional state is available
  565.      * @see #addAdditionalState(String, double[])
  566.      * @see #getAdditionalState(String)
  567.      * @see #getAdditionalStatesValues()
  568.      */
  569.     public boolean hasAdditionalState(final String name) {
  570.         return additional.getEntry(name) != null;
  571.     }

  572.     /** Check if an additional state derivative is available.
  573.      * @param name name of the additional state derivative
  574.      * @return true if the additional state derivative is available
  575.      * @see #addAdditionalStateDerivative(String, double[])
  576.      * @see #getAdditionalStateDerivative(String)
  577.      * @see #getAdditionalStatesDerivatives()
  578.      * @since 11.1
  579.      */
  580.     public boolean hasAdditionalStateDerivative(final String name) {
  581.         return additionalDot.getEntry(name) != null;
  582.     }

  583.     /** Check if two instances have the same set of additional states available.
  584.      * <p>
  585.      * Only the names and dimensions of the additional states are compared,
  586.      * not their values.
  587.      * </p>
  588.      * @param state state to compare to instance
  589.      * @exception MathIllegalStateException if an additional state does not have
  590.      * the same dimension in both states
  591.      */
  592.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  593.         throws MathIllegalStateException {

  594.         // check instance additional states is a subset of the other one
  595.         for (final DoubleArrayDictionary.Entry entry : additional.getData()) {
  596.             final double[] other = state.additional.get(entry.getKey());
  597.             if (other == null) {
  598.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  599.                                           entry.getKey());
  600.             }
  601.             if (other.length != entry.getValue().length) {
  602.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  603.                                                     other.length, entry.getValue().length);
  604.             }
  605.         }

  606.         // check instance additional states derivatives is a subset of the other one
  607.         for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
  608.             final double[] other = state.additionalDot.get(entry.getKey());
  609.             if (other == null) {
  610.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  611.                                           entry.getKey());
  612.             }
  613.             if (other.length != entry.getValue().length) {
  614.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  615.                                                     other.length, entry.getValue().length);
  616.             }
  617.         }

  618.         if (state.additional.size() > additional.size()) {
  619.             // the other state has more additional states
  620.             for (final DoubleArrayDictionary.Entry entry : state.additional.getData()) {
  621.                 if (additional.getEntry(entry.getKey()) == null) {
  622.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  623.                                               entry.getKey());
  624.                 }
  625.             }
  626.         }

  627.         if (state.additionalDot.size() > additionalDot.size()) {
  628.             // the other state has more additional states
  629.             for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
  630.                 if (additionalDot.getEntry(entry.getKey()) == null) {
  631.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  632.                                               entry.getKey());
  633.                 }
  634.             }
  635.         }

  636.     }

  637.     /** Get an additional state.
  638.      * @param name name of the additional state
  639.      * @return value of the additional state
  640.      * @see #addAdditionalState(String, double[])
  641.      * @see #hasAdditionalState(String)
  642.      * @see #getAdditionalStatesValues()
  643.      */
  644.     public double[] getAdditionalState(final String name) {
  645.         final DoubleArrayDictionary.Entry entry = additional.getEntry(name);
  646.         if (entry == null) {
  647.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  648.         }
  649.         return entry.getValue();
  650.     }

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

  666.     /** Get an unmodifiable map of additional states.
  667.      * @return unmodifiable map of additional states
  668.      * @see #addAdditionalState(String, double[])
  669.      * @see #hasAdditionalState(String)
  670.      * @see #getAdditionalState(String)
  671.      * @since 11.1
  672.      */
  673.     public DoubleArrayDictionary getAdditionalStatesValues() {
  674.         return additional.unmodifiableView();
  675.     }

  676.     /** Get an unmodifiable map of additional states derivatives.
  677.      * @return unmodifiable map of additional states derivatives
  678.      * @see #addAdditionalStateDerivative(String, double[])
  679.      * @see #hasAdditionalStateDerivative(String)
  680.      * @see #getAdditionalStateDerivative(String)
  681.      * @since 11.1
  682.      */
  683.     public DoubleArrayDictionary getAdditionalStatesDerivatives() {
  684.         return additionalDot.unmodifiableView();
  685.     }

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

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

  704.     /** Get the central attraction coefficient.
  705.      * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
  706.      * state contains an absolute position-velocity-acceleration rather than an orbit
  707.      */
  708.     public double getMu() {
  709.         return isOrbitDefined() ? orbit.getMu() : Double.NaN;
  710.     }

  711.     /** Get the Keplerian period.
  712.      * <p>The Keplerian period is computed directly from semi major axis
  713.      * and central acceleration constant.</p>
  714.      * @return Keplerian period in seconds, or {code Double.NaN} if the
  715.      * state contains an absolute position-velocity-acceleration rather
  716.      * than an orbit
  717.      */
  718.     public double getKeplerianPeriod() {
  719.         return isOrbitDefined() ? orbit.getKeplerianPeriod() : Double.NaN;
  720.     }

  721.     /** Get the Keplerian mean motion.
  722.      * <p>The Keplerian mean motion is computed directly from semi major axis
  723.      * and central acceleration constant.</p>
  724.      * @return Keplerian mean motion in radians per second, or {code Double.NaN} if the
  725.      * state contains an absolute position-velocity-acceleration rather
  726.      * than an orbit
  727.      */
  728.     public double getKeplerianMeanMotion() {
  729.         return isOrbitDefined() ? orbit.getKeplerianMeanMotion() : Double.NaN;
  730.     }

  731.     /** Get the semi-major axis.
  732.      * @return semi-major axis (m), or {code Double.NaN} if the
  733.      * state contains an absolute position-velocity-acceleration rather
  734.      * than an orbit
  735.      */
  736.     public double getA() {
  737.         return isOrbitDefined() ? orbit.getA() : Double.NaN;
  738.     }

  739.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  740.      * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
  741.      * state contains an absolute position-velocity-acceleration rather
  742.      * than an orbit
  743.      * @see #getE()
  744.      */
  745.     public double getEquinoctialEx() {
  746.         return isOrbitDefined() ? orbit.getEquinoctialEx() : Double.NaN;
  747.     }

  748.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  749.      * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
  750.      * state contains an absolute position-velocity-acceleration rather
  751.      * than an orbit
  752.      * @see #getE()
  753.      */
  754.     public double getEquinoctialEy() {
  755.         return isOrbitDefined() ? orbit.getEquinoctialEy() : Double.NaN;
  756.     }

  757.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  758.      * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
  759.      * state contains an absolute position-velocity-acceleration rather
  760.      * than an orbit
  761.      * @see #getI()
  762.      */
  763.     public double getHx() {
  764.         return isOrbitDefined() ? orbit.getHx() : Double.NaN;
  765.     }

  766.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  767.      * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
  768.      * state contains an absolute position-velocity-acceleration rather
  769.      * than an orbit
  770.      * @see #getI()
  771.      */
  772.     public double getHy() {
  773.         return isOrbitDefined() ? orbit.getHy() : Double.NaN;
  774.     }

  775.     /** Get the true latitude argument (as per equinoctial parameters).
  776.      * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
  777.      * state contains an absolute position-velocity-acceleration rather
  778.      * than an orbit
  779.      * @see #getLE()
  780.      * @see #getLM()
  781.      */
  782.     public double getLv() {
  783.         return isOrbitDefined() ? orbit.getLv() : Double.NaN;
  784.     }

  785.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  786.      * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
  787.      * state contains an absolute position-velocity-acceleration rather
  788.      * than an orbit
  789.      * @see #getLv()
  790.      * @see #getLM()
  791.      */
  792.     public double getLE() {
  793.         return isOrbitDefined() ? orbit.getLE() : Double.NaN;
  794.     }

  795.     /** Get the mean longitude argument (as per equinoctial parameters).
  796.      * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
  797.      * state contains an absolute position-velocity-acceleration rather
  798.      * than an orbit
  799.      * @see #getLv()
  800.      * @see #getLE()
  801.      */
  802.     public double getLM() {
  803.         return isOrbitDefined() ? orbit.getLM() : Double.NaN;
  804.     }

  805.     // Additional orbital elements

  806.     /** Get the eccentricity.
  807.      * @return eccentricity, or {code Double.NaN} if the
  808.      * state contains an absolute position-velocity-acceleration rather
  809.      * than an orbit
  810.      * @see #getEquinoctialEx()
  811.      * @see #getEquinoctialEy()
  812.      */
  813.     public double getE() {
  814.         return isOrbitDefined() ? orbit.getE() : Double.NaN;
  815.     }

  816.     /** Get the inclination.
  817.      * @return inclination (rad)
  818.      * @see #getHx()
  819.      * @see #getHy()
  820.      */
  821.     public double getI() {
  822.         return isOrbitDefined() ? orbit.getI() : Double.NaN;
  823.     }

  824.     /** Get the position in orbit definition frame.
  825.      * @return position in orbit definition frame
  826.      * @since 12.0
  827.      * @see #getPVCoordinates()
  828.      */
  829.     public Vector3D getPosition() {
  830.         return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
  831.     }

  832.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  833.      * <p>
  834.      * Compute the position and velocity of the satellite. This method caches its
  835.      * results, and recompute them only when the method is called with a new value
  836.      * for mu. The result is provided as a reference to the internally cached
  837.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  838.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  839.      * </p>
  840.      * @return pvCoordinates in orbit definition frame
  841.      */
  842.     public TimeStampedPVCoordinates getPVCoordinates() {
  843.         return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  844.     }

  845.     /** Get the position in given output frame.
  846.      * @param outputFrame frame in which position should be defined
  847.      * @return position in given output frame
  848.      * @since 12.0
  849.      * @see #getPVCoordinates(Frame)
  850.      */
  851.     public Vector3D getPosition(final Frame outputFrame) {
  852.         return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
  853.     }

  854.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  855.      * <p>
  856.      * Compute the position and velocity of the satellite. This method caches its
  857.      * results, and recompute them only when the method is called with a new value
  858.      * for mu. The result is provided as a reference to the internally cached
  859.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  860.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  861.      * </p>
  862.      * @param outputFrame frame in which coordinates should be defined
  863.      * @return pvCoordinates in orbit definition frame
  864.      */
  865.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  866.         return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  867.     }

  868.     /** Get the attitude.
  869.      * @return the attitude.
  870.      */
  871.     public Attitude getAttitude() {
  872.         return attitude;
  873.     }

  874.     /** Gets the current mass.
  875.      * @return the mass (kg)
  876.      */
  877.     public double getMass() {
  878.         return mass;
  879.     }

  880.     /** Replace the instance with a data transfer object for serialization.
  881.      * @return data transfer object that will be serialized
  882.      */
  883.     private Object writeReplace() {
  884.         return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
  885.     }

  886.     /** Internal class used only for serialization. */
  887.     private static class DTOO implements Serializable {

  888.         /** Serializable UID. */
  889.         private static final long serialVersionUID = 20211121L;

  890.         /** Orbit. */
  891.         private final Orbit orbit;

  892.         /** Attitude and mass double values. */
  893.         private double[] d;

  894.         /** Additional states. */
  895.         private final DoubleArrayDictionary additional;

  896.         /** Additional states derivatives. */
  897.         private final DoubleArrayDictionary additionalDot;

  898.         /** Simple constructor.
  899.          * @param state instance to serialize
  900.          */
  901.         private DTOO(final SpacecraftState state) {

  902.             this.orbit         = state.orbit;
  903.             this.additional    = state.additional.getData().isEmpty()    ? null : state.additional;
  904.             this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;

  905.             final Rotation rotation             = state.attitude.getRotation();
  906.             final Vector3D spin                 = state.attitude.getSpin();
  907.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  908.             this.d = new double[] {
  909.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  910.                 spin.getX(), spin.getY(), spin.getZ(),
  911.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  912.                 state.mass
  913.             };

  914.         }

  915.         /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
  916.          * @return replacement {@link SpacecraftState}
  917.          */
  918.         private Object readResolve() {
  919.             return new SpacecraftState(orbit,
  920.                                        new Attitude(orbit.getFrame(),
  921.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  922.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  923.                                                                                       new Vector3D(d[4], d[5], d[6]),
  924.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  925.                                        d[10], additional, additionalDot);
  926.         }

  927.     }

  928.     /** Internal class used only for serialization. */
  929.     private static class DTOA implements Serializable {

  930.         /** Serializable UID. */
  931.         private static final long serialVersionUID = 20211121L;

  932.         /** Absolute position-velocity-acceleration. */
  933.         private final AbsolutePVCoordinates absPva;

  934.         /** Attitude and mass double values. */
  935.         private double[] d;

  936.         /** Additional states. */
  937.         private final DoubleArrayDictionary additional;

  938.         /** Additional states derivatives. */
  939.         private final DoubleArrayDictionary additionalDot;

  940.         /** Simple constructor.
  941.          * @param state instance to serialize
  942.          */
  943.         private DTOA(final SpacecraftState state) {

  944.             this.absPva        = state.absPva;
  945.             this.additional    = state.additional.getData().isEmpty()    ? null : state.additional;
  946.             this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;

  947.             final Rotation rotation             = state.attitude.getRotation();
  948.             final Vector3D spin                 = state.attitude.getSpin();
  949.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  950.             this.d = new double[] {
  951.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  952.                 spin.getX(), spin.getY(), spin.getZ(),
  953.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  954.                 state.mass
  955.             };

  956.         }

  957.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  958.          * @return replacement {@link SpacecraftState}
  959.          */
  960.         private Object readResolve() {
  961.             return new SpacecraftState(absPva,
  962.                                        new Attitude(absPva.getFrame(),
  963.                                                     new TimeStampedAngularCoordinates(absPva.getDate(),
  964.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  965.                                                                                       new Vector3D(d[4], d[5], d[6]),
  966.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  967.                                        d[10], additional, additionalDot);
  968.         }
  969.     }

  970.     @Override
  971.     public String toString() {
  972.         return "SpacecraftState{" +
  973.                 "orbit=" + orbit +
  974.                 ", attitude=" + attitude +
  975.                 ", mass=" + mass +
  976.                 ", additional=" + additional +
  977.                 ", additionalDot=" + additionalDot +
  978.                 '}';
  979.     }
  980. }