SpacecraftState.java

  1. /* Copyright 2002-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.propagation;

  18. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.stream.Stream;

  24. import org.hipparchus.analysis.interpolation.HermiteInterpolator;
  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.exception.MathIllegalStateException;
  27. import org.hipparchus.geometry.euclidean.threed.Rotation;
  28. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  29. import org.hipparchus.util.FastMath;
  30. import org.orekit.attitudes.Attitude;
  31. import org.orekit.attitudes.LofOffset;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitIllegalArgumentException;
  34. import org.orekit.errors.OrekitIllegalStateException;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.frames.Frame;
  37. import org.orekit.frames.LOFType;
  38. import org.orekit.frames.Transform;
  39. import org.orekit.orbits.Orbit;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.time.TimeInterpolable;
  42. import org.orekit.time.TimeShiftable;
  43. import org.orekit.time.TimeStamped;
  44. import org.orekit.utils.AbsolutePVCoordinates;
  45. import org.orekit.utils.DoubleArrayDictionary;
  46. import org.orekit.utils.TimeStampedAngularCoordinates;
  47. import org.orekit.utils.TimeStampedPVCoordinates;

  48. /** This class is the representation of a complete state holding orbit, attitude
  49.  * and mass information at a given date.
  50.  *
  51.  * <p>It contains an {@link Orbit orbital state} at a current
  52.  * {@link AbsoluteDate} both handled by an {@link Orbit}, plus the current
  53.  * mass and attitude. Orbit and state are guaranteed to be consistent in terms
  54.  * of date and reference frame. The spacecraft state may also contain additional
  55.  * states, which are simply named double arrays which can hold any user-defined
  56.  * data.
  57.  * </p>
  58.  * <p>
  59.  * The state can be slightly shifted to close dates. This shift is based on
  60.  * a simple Keplerian model for orbit, a linear extrapolation for attitude
  61.  * taking the spin rate into account and no mass change. It is <em>not</em>
  62.  * intended as a replacement for proper orbit and attitude propagation but
  63.  * should be sufficient for either small time shifts or coarse accuracy.
  64.  * </p>
  65.  * <p>
  66.  * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
  67.  * </p>
  68.  * @see org.orekit.propagation.numerical.NumericalPropagator
  69.  * @author Fabien Maussion
  70.  * @author V&eacute;ronique Pommier-Maurussane
  71.  * @author Luc Maisonobe
  72.  */
  73. public class SpacecraftState
  74.     implements TimeStamped, TimeShiftable<SpacecraftState>, TimeInterpolable<SpacecraftState>, Serializable {

  75.     /** Serializable UID. */
  76.     private static final long serialVersionUID = 20211119L;

  77.     /** Default mass. */
  78.     private static final double DEFAULT_MASS = 1000.0;

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

  84.     /** Orbital state. */
  85.     private final Orbit orbit;

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

  88.     /** Attitude. */
  89.     private final Attitude attitude;

  90.     /** Current mass (kg). */
  91.     private final double mass;

  92.     /** Additional states. */
  93.     private final DoubleArrayDictionary additional;

  94.     /** Additional states derivatives.
  95.      * @since 11.1
  96.      */
  97.     private final DoubleArrayDictionary additionalDot;

  98.     /** Build a spacecraft state from orbit only.
  99.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  100.      * @param orbit the orbit
  101.      */
  102.     public SpacecraftState(final Orbit orbit) {
  103.         this(orbit,
  104.              new LofOffset(orbit.getFrame(),
  105.                            LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  106.              DEFAULT_MASS, (DoubleArrayDictionary) null);
  107.     }

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

  119.     /** Create a new instance from orbit and mass.
  120.      * <p>Attitude law is set to an unspecified default attitude.</p>
  121.      * @param orbit the orbit
  122.      * @param mass the mass (kg)
  123.      */
  124.     public SpacecraftState(final Orbit orbit, final double mass) {
  125.         this(orbit,
  126.              new LofOffset(orbit.getFrame(),
  127.                            LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  128.              mass, (DoubleArrayDictionary) null);
  129.     }

  130.     /** Build a spacecraft state from orbit, attitude and mass.
  131.      * @param orbit the orbit
  132.      * @param attitude attitude
  133.      * @param mass the mass (kg)
  134.      * @exception IllegalArgumentException if orbit and attitude dates
  135.      * or frames are not equal
  136.      */
  137.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
  138.         throws IllegalArgumentException {
  139.         this(orbit, attitude, mass, (DoubleArrayDictionary) null);
  140.     }

  141.     /** Build a spacecraft state from orbit and additional states.
  142.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  143.      * @param orbit the orbit
  144.      * @param additional additional states
  145.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, DoubleArrayDictionary)}
  146.      */
  147.     @Deprecated
  148.     public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional) {
  149.         this(orbit, new DoubleArrayDictionary(additional));
  150.     }

  151.     /** Build a spacecraft state from orbit and additional states.
  152.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  153.      * @param orbit the orbit
  154.      * @param additional additional states
  155.      * @since 11.1
  156.      */
  157.     public SpacecraftState(final Orbit orbit, final DoubleArrayDictionary additional) {
  158.         this(orbit,
  159.              new LofOffset(orbit.getFrame(),
  160.                            LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  161.              DEFAULT_MASS, additional);
  162.     }

  163.     /** Build a spacecraft state from orbit, attitude and additional states.
  164.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  165.      * @param orbit the orbit
  166.      * @param attitude attitude
  167.      * @param additional additional states
  168.      * @exception IllegalArgumentException if orbit and attitude dates
  169.      * or frames are not equal
  170.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, Attitude, DoubleArrayDictionary)}
  171.      */
  172.     @Deprecated
  173.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
  174.         throws IllegalArgumentException {
  175.         this(orbit, attitude, new DoubleArrayDictionary(additional));
  176.     }

  177.     /** Build a spacecraft state from orbit, attitude and additional states.
  178.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  179.      * @param orbit the orbit
  180.      * @param attitude attitude
  181.      * @param additional additional states
  182.      * @exception IllegalArgumentException if orbit and attitude dates
  183.      * or frames are not equal
  184.      * @since 11.1
  185.      */
  186.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final DoubleArrayDictionary additional)
  187.         throws IllegalArgumentException {
  188.         this(orbit, attitude, DEFAULT_MASS, additional);
  189.     }

  190.     /** Create a new instance from orbit, mass and additional states.
  191.      * <p>Attitude law is set to an unspecified default attitude.</p>
  192.      * @param orbit the orbit
  193.      * @param mass the mass (kg)
  194.      * @param additional additional states
  195.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, double, DoubleArrayDictionary)}
  196.      */
  197.     @Deprecated
  198.     public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional) {
  199.         this(orbit, mass, new DoubleArrayDictionary(additional));
  200.     }

  201.     /** Create a new instance from orbit, mass and additional states.
  202.      * <p>Attitude law is set to an unspecified default attitude.</p>
  203.      * @param orbit the orbit
  204.      * @param mass the mass (kg)
  205.      * @param additional additional states
  206.      * @since 11.1
  207.      */
  208.     public SpacecraftState(final Orbit orbit, final double mass, final DoubleArrayDictionary additional) {
  209.         this(orbit,
  210.              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  211.              mass, additional);
  212.     }

  213.     /** Build a spacecraft state from orbit, attitude, mass and additional states.
  214.      * @param orbit the orbit
  215.      * @param attitude attitude
  216.      * @param mass the mass (kg)
  217.      * @param additional additional states (may be null if no additional states are available)
  218.      * @exception IllegalArgumentException if orbit and attitude dates
  219.      * or frames are not equal
  220.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(Orbit, Attitude, double, DoubleArrayDictionary)}
  221.      */
  222.     @Deprecated
  223.     public SpacecraftState(final Orbit orbit, final Attitude attitude,
  224.                            final double mass, final Map<String, double[]> additional)
  225.         throws IllegalArgumentException {
  226.         this(orbit, attitude, mass, new DoubleArrayDictionary(additional));
  227.     }

  228.     /** Build a spacecraft state from orbit, attitude, mass and additional states.
  229.      * @param orbit the orbit
  230.      * @param attitude attitude
  231.      * @param mass the mass (kg)
  232.      * @param additional additional states (may be null if no additional states are available)
  233.      * @exception IllegalArgumentException if orbit and attitude dates
  234.      * or frames are not equal
  235.      * @since 11.1
  236.      */
  237.     public SpacecraftState(final Orbit orbit, final Attitude attitude,
  238.                            final double mass, final DoubleArrayDictionary additional)
  239.         throws IllegalArgumentException {
  240.         this(orbit, attitude, mass, additional, null);
  241.     }

  242.     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
  243.      * @param orbit the orbit
  244.      * @param attitude attitude
  245.      * @param mass the mass (kg)
  246.      * @param additional additional states (may be null if no additional states are available)
  247.      * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
  248.      * @exception IllegalArgumentException if orbit and attitude dates
  249.      * or frames are not equal
  250.      * @since 11.1
  251.      */
  252.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass,
  253.                            final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
  254.         throws IllegalArgumentException {
  255.         checkConsistency(orbit, attitude);
  256.         this.orbit      = orbit;
  257.         this.absPva     = null;
  258.         this.attitude   = attitude;
  259.         this.mass       = mass;
  260.         if (additional == null) {
  261.             this.additional = new DoubleArrayDictionary();
  262.         } else {
  263.             this.additional = additional;
  264.         }
  265.         if (additionalDot == null) {
  266.             this.additionalDot = new DoubleArrayDictionary();
  267.         } else {
  268.             this.additionalDot = new DoubleArrayDictionary(additionalDot);
  269.         }
  270.     }



  271.     /** Build a spacecraft state from position-velocity-acceleration only.
  272.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  273.      * @param absPva position-velocity-acceleration
  274.      */
  275.     public SpacecraftState(final AbsolutePVCoordinates absPva) {
  276.         this(absPva,
  277.              new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  278.              DEFAULT_MASS, (DoubleArrayDictionary) null);
  279.     }

  280.     /** Build a spacecraft state from position-velocity-acceleration and attitude.
  281.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  282.      * @param absPva position-velocity-acceleration
  283.      * @param attitude attitude
  284.      * @exception IllegalArgumentException if orbit and attitude dates
  285.      * or frames are not equal
  286.      */
  287.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
  288.         throws IllegalArgumentException {
  289.         this(absPva, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null);
  290.     }

  291.     /** Create a new instance from position-velocity-acceleration and mass.
  292.      * <p>Attitude law is set to an unspecified default attitude.</p>
  293.      * @param absPva position-velocity-acceleration
  294.      * @param mass the mass (kg)
  295.      */
  296.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
  297.         this(absPva,
  298.              new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  299.              mass, (DoubleArrayDictionary) null);
  300.     }

  301.     /** Build a spacecraft state from position-velocity-acceleration, attitude and mass.
  302.      * @param absPva position-velocity-acceleration
  303.      * @param attitude attitude
  304.      * @param mass the mass (kg)
  305.      * @exception IllegalArgumentException if orbit and attitude dates
  306.      * or frames are not equal
  307.      */
  308.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
  309.         throws IllegalArgumentException {
  310.         this(absPva, attitude, mass, (DoubleArrayDictionary) null);
  311.     }

  312.     /** Build a spacecraft state from position-velocity-acceleration and additional states.
  313.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  314.      * @param absPva position-velocity-acceleration
  315.      * @param additional additional states
  316.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, DoubleArrayDictionary)}
  317.      */
  318.     @Deprecated
  319.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Map<String, double[]> additional) {
  320.         this(absPva, new DoubleArrayDictionary(additional));
  321.     }

  322.     /** Build a spacecraft state from position-velocity-acceleration and additional states.
  323.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  324.      * @param absPva position-velocity-acceleration
  325.      * @param additional additional states
  326.      * @since 11.1
  327.      */
  328.     public SpacecraftState(final AbsolutePVCoordinates absPva, final DoubleArrayDictionary additional) {
  329.         this(absPva,
  330.              new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  331.              DEFAULT_MASS, additional);
  332.     }

  333.     /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states.
  334.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  335.      * @param absPva position-velocity-acceleration
  336.      * @param attitude attitude
  337.      * @param additional additional states
  338.      * @exception IllegalArgumentException if orbit and attitude dates
  339.      * or frames are not equal
  340.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, Attitude, DoubleArrayDictionary)}
  341.      */
  342.     @Deprecated
  343.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final Map<String, double[]> additional)
  344.         throws IllegalArgumentException {
  345.         this(absPva, attitude, new DoubleArrayDictionary(additional));
  346.     }

  347.     /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states.
  348.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  349.      * @param absPva position-velocity-acceleration
  350.      * @param attitude attitude
  351.      * @param additional additional states
  352.      * @exception IllegalArgumentException if orbit and attitude dates
  353.      * or frames are not equal
  354.      * @since 11.1
  355.      */
  356.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final DoubleArrayDictionary additional)
  357.         throws IllegalArgumentException {
  358.         this(absPva, attitude, DEFAULT_MASS, additional);
  359.     }

  360.     /** Create a new instance from position-velocity-acceleration, mass and additional states.
  361.      * <p>Attitude law is set to an unspecified default attitude.</p>
  362.      * @param absPva position-velocity-acceleration
  363.      * @param mass the mass (kg)
  364.      * @param additional additional states
  365.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, double, DoubleArrayDictionary)}
  366.      */
  367.     @Deprecated
  368.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final Map<String, double[]> additional) {
  369.         this(absPva, mass, new DoubleArrayDictionary(additional));
  370.     }

  371.     /** Create a new instance from position-velocity-acceleration, mass and additional states.
  372.      * <p>Attitude law is set to an unspecified default attitude.</p>
  373.      * @param absPva position-velocity-acceleration
  374.      * @param mass the mass (kg)
  375.      * @param additional additional states
  376.      * @since 11.1
  377.      */
  378.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final DoubleArrayDictionary additional) {
  379.         this(absPva,
  380.              new LofOffset(absPva.getFrame(), LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  381.              mass, additional);
  382.     }

  383.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states.
  384.      * @param absPva position-velocity-acceleration
  385.      * @param attitude attitude
  386.      * @param mass the mass (kg)
  387.      * @param additional additional states (may be null if no additional states are available)
  388.      * @exception IllegalArgumentException if orbit and attitude dates
  389.      * or frames are not equal
  390.      * @deprecated as of 11.1, replaced by {@link #SpacecraftState(AbsolutePVCoordinates, Attitude, double, DoubleArrayDictionary)}
  391.      */
  392.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
  393.                            final double mass, final Map<String, double[]> additional)
  394.         throws IllegalArgumentException {
  395.         this(absPva, attitude, mass, new DoubleArrayDictionary(additional));
  396.     }

  397.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states.
  398.      * @param absPva position-velocity-acceleration
  399.      * @param attitude attitude
  400.      * @param mass the mass (kg)
  401.      * @param additional additional states (may be null if no additional states are available)
  402.      * @exception IllegalArgumentException if orbit and attitude dates
  403.      * or frames are not equal
  404.      * @since 11.1
  405.      */
  406.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
  407.                            final double mass, final DoubleArrayDictionary additional)
  408.         throws IllegalArgumentException {
  409.         this(absPva, attitude, mass, additional, null);
  410.     }

  411.     /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives.
  412.      * @param absPva position-velocity-acceleration
  413.      * @param attitude attitude
  414.      * @param mass the mass (kg)
  415.      * @param additional additional states (may be null if no additional states are available)
  416.      * @param additionalDot additional states derivatives(may be null if no additional states derivativesare available)
  417.      * @exception IllegalArgumentException if orbit and attitude dates
  418.      * or frames are not equal
  419.      * @since 11.1
  420.      */
  421.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass,
  422.                            final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot)
  423.         throws IllegalArgumentException {
  424.         checkConsistency(absPva, attitude);
  425.         this.orbit      = null;
  426.         this.absPva     = absPva;
  427.         this.attitude   = attitude;
  428.         this.mass       = mass;
  429.         if (additional == null) {
  430.             this.additional = new DoubleArrayDictionary();
  431.         } else {
  432.             this.additional = new DoubleArrayDictionary(additional);
  433.         }
  434.         if (additionalDot == null) {
  435.             this.additionalDot = new DoubleArrayDictionary();
  436.         } else {
  437.             this.additionalDot = new DoubleArrayDictionary(additionalDot);
  438.         }
  439.     }

  440.     /** Add an additional state.
  441.      * <p>
  442.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  443.      * so this method does <em>not</em> change the instance, but rather
  444.      * creates a new instance, which has the same orbit, attitude, mass
  445.      * and additional states as the original instance, except it also
  446.      * has the specified state. If the original instance already had an
  447.      * additional state with the same name, it will be overridden. If it
  448.      * did not have any additional state with that name, the new instance
  449.      * will have one more additional state than the original instance.
  450.      * </p>
  451.      * @param name name of the additional state (names containing "orekit"
  452.      * with any case are reserved for the library internal use)
  453.      * @param value value of the additional state
  454.      * @return a new instance, with the additional state added
  455.      * @see #hasAdditionalState(String)
  456.      * @see #getAdditionalState(String)
  457.      * @see #getAdditionalStates()
  458.      */
  459.     public SpacecraftState addAdditionalState(final String name, final double... value) {
  460.         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additional);
  461.         newDict.put(name, value.clone());
  462.         if (absPva == null) {
  463.             return new SpacecraftState(orbit, attitude, mass, newDict, additionalDot);
  464.         } else {
  465.             return new SpacecraftState(absPva, attitude, mass, newDict, additionalDot);
  466.         }
  467.     }

  468.     /** Add an additional state derivative.
  469.      * <p>
  470.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  471.      * so this method does <em>not</em> change the instance, but rather
  472.      * creates a new instance, which has the same components as the original
  473.      * instance, except it also has the specified state derivative. If the
  474.      * original instance already had an additional state derivative with the
  475.      * same name, it will be overridden. If it did not have any additional
  476.      * state derivative with that name, the new instance will have one more
  477.      * additional state derivative than the original instance.
  478.      * </p>
  479.      * @param name name of the additional state derivative (names containing "orekit"
  480.      * with any case are reserved for the library internal use)
  481.      * @param value value of the additional state derivative
  482.      * @return a new instance, with the additional state added
  483.      * @see #hasAdditionalStateDerivative(String)
  484.      * @see #getAdditionalStateDerivative(String)
  485.      * @see #getAdditionalStatesDerivatives()
  486.      * @since 11.1
  487.      */
  488.     public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) {
  489.         final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot);
  490.         newDict.put(name, value.clone());
  491.         if (absPva == null) {
  492.             return new SpacecraftState(orbit, attitude, mass, additional, newDict);
  493.         } else {
  494.             return new SpacecraftState(absPva, attitude, mass, additional, newDict);
  495.         }
  496.     }

  497.     /** Check orbit and attitude dates are equal.
  498.      * @param orbit the orbit
  499.      * @param attitude attitude
  500.      * @exception IllegalArgumentException if orbit and attitude dates
  501.      * are not equal
  502.      */
  503.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  504.         throws IllegalArgumentException {
  505.         if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
  506.             DATE_INCONSISTENCY_THRESHOLD) {
  507.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  508.                                                      orbit.getDate(), attitude.getDate());
  509.         }
  510.         if (orbit.getFrame() != attitude.getReferenceFrame()) {
  511.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  512.                                                      orbit.getFrame().getName(),
  513.                                                      attitude.getReferenceFrame().getName());
  514.         }
  515.     }

  516.     /** Check if the state contains an orbit part.
  517.      * <p>
  518.      * A state contains either an {@link AbsolutePVCoordinates absolute
  519.      * position-velocity-acceleration} or an {@link Orbit orbit}.
  520.      * </p>
  521.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  522.      * will not throw an exception), or false if the state contains an
  523.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  524.      * will not throw an exception)
  525.      */
  526.     public boolean isOrbitDefined() {
  527.         return orbit != null;
  528.     }

  529.     /** Check AbsolutePVCoordinates and attitude dates are equal.
  530.      * @param absPva position-velocity-acceleration
  531.      * @param attitude attitude
  532.      * @exception IllegalArgumentException if orbit and attitude dates
  533.      * are not equal
  534.      */
  535.     private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
  536.         throws IllegalArgumentException {
  537.         if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) >
  538.             DATE_INCONSISTENCY_THRESHOLD) {
  539.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  540.                                                      absPva.getDate(), attitude.getDate());
  541.         }
  542.         if (absPva.getFrame() != attitude.getReferenceFrame()) {
  543.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  544.                                                      absPva.getFrame().getName(),
  545.                                                      attitude.getReferenceFrame().getName());
  546.         }
  547.     }

  548.     /** Get a time-shifted state.
  549.      * <p>
  550.      * The state can be slightly shifted to close dates. This shift is based on
  551.      * simple models. For orbits, the model is a Keplerian one if no derivatives
  552.      * are available in the orbit, or Keplerian plus quadratic effect of the
  553.      * non-Keplerian acceleration if derivatives are available. For attitude,
  554.      * a polynomial model is used. Neither mass nor additional states change.
  555.      * Shifting is <em>not</em> intended as a replacement for proper orbit
  556.      * and attitude propagation but should be sufficient for small time shifts
  557.      * or coarse accuracy.
  558.      * </p>
  559.      * <p>
  560.      * As a rough order of magnitude, the following table shows the extrapolation
  561.      * errors obtained between this simple shift method and an {@link
  562.      * org.orekit.propagation.numerical.NumericalPropagator numerical
  563.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  564.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  565.      * Beware that these results will be different for other orbits.
  566.      * </p>
  567.      * <table border="1">
  568.      * <caption>Extrapolation Error</caption>
  569.      * <tr style="background-color: #ccccff"><th>interpolation time (s)</th>
  570.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  571.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  572.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  573.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  574.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  575.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  576.      * </table>
  577.      * @param dt time shift in seconds
  578.      * @return a new state, shifted with respect to the instance (which is immutable)
  579.      * except for the mass and additional states which stay unchanged
  580.      */
  581.     public SpacecraftState shiftedBy(final double dt) {
  582.         if (absPva == null) {
  583.             return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  584.                                        mass, shiftAdditional(dt), additionalDot);
  585.         } else {
  586.             return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  587.                                        mass, shiftAdditional(dt), additionalDot);
  588.         }
  589.     }

  590.     /** Shift additional states.
  591.      * @param dt time shift in seconds
  592.      * @return shifted additional states
  593.      * @since 11.1.1
  594.      */
  595.     private DoubleArrayDictionary shiftAdditional(final double dt) {

  596.         // fast handling when there are no derivatives at all
  597.         if (additionalDot.size() == 0) {
  598.             return additional;
  599.         }

  600.         // there are derivatives, we need to take them into account in the additional state
  601.         final DoubleArrayDictionary shifted = new DoubleArrayDictionary(additional);
  602.         for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
  603.             final DoubleArrayDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
  604.             if (entry != null) {
  605.                 entry.scaledIncrement(dt, dotEntry);
  606.             }
  607.         }

  608.         return shifted;

  609.     }

  610.     /** {@inheritDoc}
  611.      * <p>
  612.      * The additional states that are interpolated are the ones already present
  613.      * in the instance. The sample instances must therefore have at least the same
  614.      * additional states has the instance. They may have more additional states,
  615.      * but the extra ones will be ignored.
  616.      * </p>
  617.      * <p>
  618.      * The instance and all the sample instances <em>must</em> be based on similar
  619.      * trajectory data, i.e. they must either all be based on orbits or all be based
  620.      * on absolute position-velocity-acceleration. Any inconsistency will trigger
  621.      * an {@link OrekitIllegalStateException}.
  622.      * </p>
  623.      * <p>
  624.      * As this implementation of interpolation is polynomial, it should be used only
  625.      * with small samples (about 10-20 points) in order to avoid <a
  626.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  627.      * and numerical problems (including NaN appearing).
  628.      * </p>
  629.      * @exception OrekitIllegalStateException if some instances are not based on
  630.      * similar trajectory data
  631.      */
  632.     public SpacecraftState interpolate(final AbsoluteDate date,
  633.                                        final Stream<SpacecraftState> sample) {

  634.         // prepare interpolators
  635.         final List<Orbit> orbits;
  636.         final List<AbsolutePVCoordinates> absPvas;
  637.         if (isOrbitDefined()) {
  638.             orbits  = new ArrayList<Orbit>();
  639.             absPvas = null;
  640.         } else {
  641.             orbits  = null;
  642.             absPvas = new ArrayList<AbsolutePVCoordinates>();
  643.         }
  644.         final List<Attitude> attitudes = new ArrayList<>();
  645.         final HermiteInterpolator massInterpolator = new HermiteInterpolator();
  646.         final List<DoubleArrayDictionary.Entry> addionalEntries = additional.getData();
  647.         final Map<String, HermiteInterpolator> additionalInterpolators =
  648.                 new HashMap<String, HermiteInterpolator>(addionalEntries.size());
  649.         for (final DoubleArrayDictionary.Entry entry : addionalEntries) {
  650.             additionalInterpolators.put(entry.getKey(), new HermiteInterpolator());
  651.         }
  652.         final List<DoubleArrayDictionary.Entry> additionalDotEntries = additionalDot.getData();
  653.         final Map<String, HermiteInterpolator> additionalDotInterpolators =
  654.                 new HashMap<String, HermiteInterpolator>(additionalDotEntries.size());
  655.         for (final DoubleArrayDictionary.Entry entry : additionalDotEntries) {
  656.             additionalDotInterpolators.put(entry.getKey(), new HermiteInterpolator());
  657.         }

  658.         // extract sample data
  659.         sample.forEach(state -> {
  660.             final double deltaT = state.getDate().durationFrom(date);
  661.             if (isOrbitDefined()) {
  662.                 orbits.add(state.getOrbit());
  663.             } else {
  664.                 absPvas.add(state.getAbsPVA());
  665.             }
  666.             attitudes.add(state.getAttitude());
  667.             massInterpolator.addSamplePoint(deltaT,
  668.                                             new double[] {
  669.                                                 state.getMass()
  670.                                                 });
  671.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  672.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  673.             }
  674.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
  675.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalStateDerivative(entry.getKey()));
  676.             }

  677.         });

  678.         // perform interpolations
  679.         final Orbit interpolatedOrbit;
  680.         final AbsolutePVCoordinates interpolatedAbsPva;
  681.         if (isOrbitDefined()) {
  682.             interpolatedOrbit  = orbit.interpolate(date, orbits);
  683.             interpolatedAbsPva = null;
  684.         } else {
  685.             interpolatedOrbit  = null;
  686.             interpolatedAbsPva = absPva.interpolate(date, absPvas);
  687.         }
  688.         final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
  689.         final double interpolatedMass       = massInterpolator.value(0)[0];
  690.         final DoubleArrayDictionary interpolatedAdditional;
  691.         if (additionalInterpolators.isEmpty()) {
  692.             interpolatedAdditional = null;
  693.         } else {
  694.             interpolatedAdditional = new DoubleArrayDictionary(additionalInterpolators.size());
  695.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  696.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
  697.             }
  698.         }
  699.         final DoubleArrayDictionary interpolatedAdditionalDot;
  700.         if (additionalDotInterpolators.isEmpty()) {
  701.             interpolatedAdditionalDot = null;
  702.         } else {
  703.             interpolatedAdditionalDot = new DoubleArrayDictionary(additionalDotInterpolators.size());
  704.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalDotInterpolators.entrySet()) {
  705.                 interpolatedAdditionalDot.put(entry.getKey(), entry.getValue().value(0));
  706.             }
  707.         }

  708.         // create the complete interpolated state
  709.         if (isOrbitDefined()) {
  710.             return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass,
  711.                                        interpolatedAdditional, interpolatedAdditionalDot);
  712.         } else {
  713.             return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass,
  714.                                        interpolatedAdditional, interpolatedAdditionalDot);
  715.         }

  716.     }

  717.     /** Get the absolute position-velocity-acceleration.
  718.      * <p>
  719.      * A state contains either an {@link AbsolutePVCoordinates absolute
  720.      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
  721.      * one is present can be checked using {@link #isOrbitDefined()}.
  722.      * </p>
  723.      * @return absolute position-velocity-acceleration
  724.      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
  725.      * which mean the state rather contains an {@link Orbit}
  726.      * @see #isOrbitDefined()
  727.      * @see #getOrbit()
  728.      */
  729.     public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException {
  730.         if (absPva == null) {
  731.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
  732.         }
  733.         return absPva;
  734.     }

  735.     /** Get the current orbit.
  736.      * <p>
  737.      * A state contains either an {@link AbsolutePVCoordinates absolute
  738.      * position-velocity-acceleration} or an {@link Orbit orbit}. Which
  739.      * one is present can be checked using {@link #isOrbitDefined()}.
  740.      * </p>
  741.      * @return the orbit
  742.      * @exception OrekitIllegalStateException if orbit is null,
  743.      * which means the state rather contains an {@link AbsolutePVCoordinates absolute
  744.      * position-velocity-acceleration}
  745.      * @see #isOrbitDefined()
  746.      * @see #getAbsPVA()
  747.      */
  748.     public Orbit getOrbit() throws OrekitIllegalStateException {
  749.         if (orbit == null) {
  750.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
  751.         }
  752.         return orbit;
  753.     }

  754.     /** Get the date.
  755.      * @return date
  756.      */
  757.     public AbsoluteDate getDate() {
  758.         return (absPva == null) ? orbit.getDate() : absPva.getDate();
  759.     }

  760.     /** Get the defining frame.
  761.      * @return the frame in which state is defined
  762.      */
  763.     public Frame getFrame() {
  764.         return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
  765.     }

  766.     /** Check if an additional state is available.
  767.      * @param name name of the additional state
  768.      * @return true if the additional state is available
  769.      * @see #addAdditionalState(String, double[])
  770.      * @see #getAdditionalState(String)
  771.      * @see #getAdditionalStates()
  772.      */
  773.     public boolean hasAdditionalState(final String name) {
  774.         return additional.getEntry(name) != null;
  775.     }

  776.     /** Check if an additional state derivative is available.
  777.      * @param name name of the additional state derivative
  778.      * @return true if the additional state derivative is available
  779.      * @see #addAdditionalStateDerivative(String, double[])
  780.      * @see #getAdditionalStateDerivative(String)
  781.      * @see #getAdditionalStatesDerivatives()
  782.      * @since 11.1
  783.      */
  784.     public boolean hasAdditionalStateDerivative(final String name) {
  785.         return additionalDot.getEntry(name) != null;
  786.     }

  787.     /** Check if two instances have the same set of additional states available.
  788.      * <p>
  789.      * Only the names and dimensions of the additional states are compared,
  790.      * not their values.
  791.      * </p>
  792.      * @param state state to compare to instance
  793.      * @exception MathIllegalStateException if an additional state does not have
  794.      * the same dimension in both states
  795.      */
  796.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  797.         throws MathIllegalStateException {

  798.         // check instance additional states is a subset of the other one
  799.         for (final DoubleArrayDictionary.Entry entry : additional.getData()) {
  800.             final double[] other = state.additional.get(entry.getKey());
  801.             if (other == null) {
  802.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  803.                                           entry.getKey());
  804.             }
  805.             if (other.length != entry.getValue().length) {
  806.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  807.                                                     other.length, entry.getValue().length);
  808.             }
  809.         }

  810.         // check instance additional states derivatives is a subset of the other one
  811.         for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) {
  812.             final double[] other = state.additionalDot.get(entry.getKey());
  813.             if (other == null) {
  814.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  815.                                           entry.getKey());
  816.             }
  817.             if (other.length != entry.getValue().length) {
  818.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  819.                                                     other.length, entry.getValue().length);
  820.             }
  821.         }

  822.         if (state.additional.size() > additional.size()) {
  823.             // the other state has more additional states
  824.             for (final DoubleArrayDictionary.Entry entry : state.additional.getData()) {
  825.                 if (additional.getEntry(entry.getKey()) == null) {
  826.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  827.                                               entry.getKey());
  828.                 }
  829.             }
  830.         }

  831.         if (state.additionalDot.size() > additionalDot.size()) {
  832.             // the other state has more additional states
  833.             for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) {
  834.                 if (additionalDot.getEntry(entry.getKey()) == null) {
  835.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  836.                                               entry.getKey());
  837.                 }
  838.             }
  839.         }

  840.     }

  841.     /** Get an additional state.
  842.      * @param name name of the additional state
  843.      * @return value of the additional state
  844.      * @see #addAdditionalState(String, double[])
  845.      * @see #hasAdditionalState(String)
  846.      * @see #getAdditionalStates()
  847.      */
  848.     public double[] getAdditionalState(final String name) {
  849.         final DoubleArrayDictionary.Entry entry = additional.getEntry(name);
  850.         if (entry == null) {
  851.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  852.         }
  853.         return entry.getValue();
  854.     }

  855.     /** Get an additional state derivative.
  856.      * @param name name of the additional state derivative
  857.      * @return value of the additional state derivative
  858.      * @see #addAdditionalStateDerivative(String, double[])
  859.      * @see #hasAdditionalStateDerivative(String)
  860.      * @see #getAdditionalStatesDerivatives()
  861.      * @since 11.1
  862.      */
  863.     public double[] getAdditionalStateDerivative(final String name) {
  864.         final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name);
  865.         if (entry == null) {
  866.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  867.         }
  868.         return entry.getValue();
  869.     }

  870.     /** Get an unmodifiable map of additional states.
  871.      * @return unmodifiable map of additional states
  872.      * @see #addAdditionalState(String, double[])
  873.      * @see #hasAdditionalState(String)
  874.      * @see #getAdditionalState(String)
  875.      * @deprecated as of 11.1, replaced by {@link #getAdditionalStatesValues()}
  876.      */
  877.     @Deprecated
  878.     public Map<String, double[]> getAdditionalStates() {
  879.         return getAdditionalStatesValues().toMap();
  880.     }

  881.     /** Get an unmodifiable map of additional states.
  882.      * @return unmodifiable map of additional states
  883.      * @see #addAdditionalState(String, double[])
  884.      * @see #hasAdditionalState(String)
  885.      * @see #getAdditionalState(String)
  886.      * @since 11.1
  887.      */
  888.     public DoubleArrayDictionary getAdditionalStatesValues() {
  889.         return additional.unmodifiableView();
  890.     }

  891.     /** Get an unmodifiable map of additional states derivatives.
  892.      * @return unmodifiable map of additional states derivatives
  893.      * @see #addAdditionalStateDerivative(String, double[])
  894.      * @see #hasAdditionalStateDerivative(String)
  895.      * @see #getAdditionalStateDerivative(String)
  896.      * @since 11.1
  897.      */
  898.     public DoubleArrayDictionary getAdditionalStatesDerivatives() {
  899.         return additionalDot.unmodifiableView();
  900.     }

  901.     /** Compute the transform from state defining frame to spacecraft frame.
  902.      * <p>The spacecraft frame origin is at the point defined by the orbit
  903.      * (or absolute position-velocity-acceleration), and its orientation is
  904.      * defined by the attitude.</p>
  905.      * @return transform from specified frame to current spacecraft frame
  906.      */
  907.     public Transform toTransform() {
  908.         final TimeStampedPVCoordinates pv = getPVCoordinates();
  909.         return new Transform(pv.getDate(),
  910.                              new Transform(pv.getDate(), pv.negate()),
  911.                              new Transform(pv.getDate(), attitude.getOrientation()));
  912.     }

  913.     /** Get the central attraction coefficient.
  914.      * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
  915.      * state is contains an absolute position-velocity-acceleration rather than an orbit
  916.      */
  917.     public double getMu() {
  918.         return (absPva == null) ? orbit.getMu() : Double.NaN;
  919.     }

  920.     /** Get the Keplerian period.
  921.      * <p>The Keplerian period is computed directly from semi major axis
  922.      * and central acceleration constant.</p>
  923.      * @return keplerian period in seconds, or {code Double.NaN} if the
  924.      * state is contains an absolute position-velocity-acceleration rather
  925.      * than an orbit
  926.      */
  927.     public double getKeplerianPeriod() {
  928.         return (absPva == null) ? orbit.getKeplerianPeriod() : Double.NaN;
  929.     }

  930.     /** Get the Keplerian mean motion.
  931.      * <p>The Keplerian mean motion is computed directly from semi major axis
  932.      * and central acceleration constant.</p>
  933.      * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
  934.      * state is contains an absolute position-velocity-acceleration rather
  935.      * than an orbit
  936.      */
  937.     public double getKeplerianMeanMotion() {
  938.         return (absPva == null) ? orbit.getKeplerianMeanMotion() : Double.NaN;
  939.     }

  940.     /** Get the semi-major axis.
  941.      * @return semi-major axis (m), or {code Double.NaN} if the
  942.      * state is contains an absolute position-velocity-acceleration rather
  943.      * than an orbit
  944.      */
  945.     public double getA() {
  946.         return (absPva == null) ? orbit.getA() : Double.NaN;
  947.     }

  948.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  949.      * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
  950.      * state is contains an absolute position-velocity-acceleration rather
  951.      * than an orbit
  952.      * @see #getE()
  953.      */
  954.     public double getEquinoctialEx() {
  955.         return (absPva == null) ? orbit.getEquinoctialEx() : Double.NaN;
  956.     }

  957.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  958.      * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
  959.      * state is contains an absolute position-velocity-acceleration rather
  960.      * than an orbit
  961.      * @see #getE()
  962.      */
  963.     public double getEquinoctialEy() {
  964.         return (absPva == null) ? orbit.getEquinoctialEy() : Double.NaN;
  965.     }

  966.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  967.      * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
  968.      * state is contains an absolute position-velocity-acceleration rather
  969.      * than an orbit
  970.      * @see #getI()
  971.      */
  972.     public double getHx() {
  973.         return (absPva == null) ? orbit.getHx() : Double.NaN;
  974.     }

  975.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  976.      * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
  977.      * state is contains an absolute position-velocity-acceleration rather
  978.      * than an orbit
  979.      * @see #getI()
  980.      */
  981.     public double getHy() {
  982.         return (absPva == null) ? orbit.getHy() : Double.NaN;
  983.     }

  984.     /** Get the true latitude argument (as per equinoctial parameters).
  985.      * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
  986.      * state is contains an absolute position-velocity-acceleration rather
  987.      * than an orbit
  988.      * @see #getLE()
  989.      * @see #getLM()
  990.      */
  991.     public double getLv() {
  992.         return (absPva == null) ? orbit.getLv() : Double.NaN;
  993.     }

  994.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  995.      * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
  996.      * state is contains an absolute position-velocity-acceleration rather
  997.      * than an orbit
  998.      * @see #getLv()
  999.      * @see #getLM()
  1000.      */
  1001.     public double getLE() {
  1002.         return (absPva == null) ? orbit.getLE() : Double.NaN;
  1003.     }

  1004.     /** Get the mean longitude argument (as per equinoctial parameters).
  1005.      * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
  1006.      * state is contains an absolute position-velocity-acceleration rather
  1007.      * than an orbit
  1008.      * @see #getLv()
  1009.      * @see #getLE()
  1010.      */
  1011.     public double getLM() {
  1012.         return (absPva == null) ? orbit.getLM() : Double.NaN;
  1013.     }

  1014.     // Additional orbital elements

  1015.     /** Get the eccentricity.
  1016.      * @return eccentricity, or {code Double.NaN} if the
  1017.      * state is contains an absolute position-velocity-acceleration rather
  1018.      * than an orbit
  1019.      * @see #getEquinoctialEx()
  1020.      * @see #getEquinoctialEy()
  1021.      */
  1022.     public double getE() {
  1023.         return (absPva == null) ? orbit.getE() : Double.NaN;
  1024.     }

  1025.     /** Get the inclination.
  1026.      * @return inclination (rad)
  1027.      * @see #getHx()
  1028.      * @see #getHy()
  1029.      */
  1030.     public double getI() {
  1031.         return (absPva == null) ? orbit.getI() : Double.NaN;
  1032.     }

  1033.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  1034.      * <p>
  1035.      * Compute the position and velocity of the satellite. This method caches its
  1036.      * results, and recompute them only when the method is called with a new value
  1037.      * for mu. The result is provided as a reference to the internally cached
  1038.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  1039.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  1040.      * </p>
  1041.      * @return pvCoordinates in orbit definition frame
  1042.      */
  1043.     public TimeStampedPVCoordinates getPVCoordinates() {
  1044.         return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  1045.     }

  1046.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  1047.      * <p>
  1048.      * Compute the position and velocity of the satellite. This method caches its
  1049.      * results, and recompute them only when the method is called with a new value
  1050.      * for mu. The result is provided as a reference to the internally cached
  1051.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  1052.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  1053.      * </p>
  1054.      * @param outputFrame frame in which coordinates should be defined
  1055.      * @return pvCoordinates in orbit definition frame
  1056.      */
  1057.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  1058.         return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  1059.     }

  1060.     /** Get the attitude.
  1061.      * @return the attitude.
  1062.      */
  1063.     public Attitude getAttitude() {
  1064.         return attitude;
  1065.     }

  1066.     /** Gets the current mass.
  1067.      * @return the mass (kg)
  1068.      */
  1069.     public double getMass() {
  1070.         return mass;
  1071.     }

  1072.     /** Replace the instance with a data transfer object for serialization.
  1073.      * @return data transfer object that will be serialized
  1074.      */
  1075.     private Object writeReplace() {
  1076.         return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
  1077.     }

  1078.     /** Internal class used only for serialization. */
  1079.     private static class DTOO implements Serializable {

  1080.         /** Serializable UID. */
  1081.         private static final long serialVersionUID = 20211121L;

  1082.         /** Orbit. */
  1083.         private final Orbit orbit;

  1084.         /** Attitude and mass double values. */
  1085.         private double[] d;

  1086.         /** Additional states. */
  1087.         private final DoubleArrayDictionary additional;

  1088.         /** Additional states derivatives. */
  1089.         private final DoubleArrayDictionary additionalDot;

  1090.         /** Simple constructor.
  1091.          * @param state instance to serialize
  1092.          */
  1093.         private DTOO(final SpacecraftState state) {

  1094.             this.orbit         = state.orbit;
  1095.             this.additional    = state.additional.getData().isEmpty()    ? null : state.additional;
  1096.             this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;

  1097.             final Rotation rotation             = state.attitude.getRotation();
  1098.             final Vector3D spin                 = state.attitude.getSpin();
  1099.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  1100.             this.d = new double[] {
  1101.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  1102.                 spin.getX(), spin.getY(), spin.getZ(),
  1103.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  1104.                 state.mass
  1105.             };

  1106.         }

  1107.         /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
  1108.          * @return replacement {@link SpacecraftState}
  1109.          */
  1110.         private Object readResolve() {
  1111.             return new SpacecraftState(orbit,
  1112.                                        new Attitude(orbit.getFrame(),
  1113.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  1114.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  1115.                                                                                       new Vector3D(d[4], d[5], d[6]),
  1116.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  1117.                                        d[10], additional, additionalDot);
  1118.         }

  1119.     }

  1120.     /** Internal class used only for serialization. */
  1121.     private static class DTOA implements Serializable {

  1122.         /** Serializable UID. */
  1123.         private static final long serialVersionUID = 20211121L;

  1124.         /** Absolute position-velocity-acceleration. */
  1125.         private final AbsolutePVCoordinates absPva;

  1126.         /** Attitude and mass double values. */
  1127.         private double[] d;

  1128.         /** Additional states. */
  1129.         private final DoubleArrayDictionary additional;

  1130.         /** Additional states derivatives. */
  1131.         private final DoubleArrayDictionary additionalDot;

  1132.         /** Simple constructor.
  1133.          * @param state instance to serialize
  1134.          */
  1135.         private DTOA(final SpacecraftState state) {

  1136.             this.absPva        = state.absPva;
  1137.             this.additional    = state.additional.getData().isEmpty()    ? null : state.additional;
  1138.             this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot;

  1139.             final Rotation rotation             = state.attitude.getRotation();
  1140.             final Vector3D spin                 = state.attitude.getSpin();
  1141.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  1142.             this.d = new double[] {
  1143.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  1144.                 spin.getX(), spin.getY(), spin.getZ(),
  1145.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  1146.                 state.mass
  1147.             };

  1148.         }

  1149.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  1150.          * @return replacement {@link SpacecraftState}
  1151.          */
  1152.         private Object readResolve() {
  1153.             return new SpacecraftState(absPva,
  1154.                                        new Attitude(absPva.getFrame(),
  1155.                                                     new TimeStampedAngularCoordinates(absPva.getDate(),
  1156.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  1157.                                                                                       new Vector3D(d[4], d[5], d[6]),
  1158.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  1159.                                        d[10], additional, additionalDot);
  1160.         }
  1161.     }

  1162.     @Override
  1163.     public String toString() {
  1164.         return "SpacecraftState{" +
  1165.                 "orbit=" + orbit +
  1166.                 ", attitude=" + attitude +
  1167.                 ", mass=" + mass +
  1168.                 ", additional=" + additional +
  1169.                 ", additionalDot=" + additionalDot +
  1170.                 '}';
  1171.     }
  1172. }