SpacecraftState.java

  1. /* Copyright 2002-2020 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.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.stream.Stream;

  25. import org.hipparchus.analysis.interpolation.HermiteInterpolator;
  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.geometry.euclidean.threed.Rotation;
  29. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  30. import org.hipparchus.util.FastMath;
  31. import org.orekit.attitudes.Attitude;
  32. import org.orekit.attitudes.LofOffset;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitIllegalArgumentException;
  35. import org.orekit.errors.OrekitIllegalStateException;
  36. import org.orekit.errors.OrekitMessages;
  37. import org.orekit.frames.Frame;
  38. import org.orekit.frames.LOFType;
  39. import org.orekit.frames.Transform;
  40. import org.orekit.orbits.Orbit;
  41. import org.orekit.time.AbsoluteDate;
  42. import org.orekit.time.TimeInterpolable;
  43. import org.orekit.time.TimeShiftable;
  44. import org.orekit.time.TimeStamped;
  45. import org.orekit.utils.AbsolutePVCoordinates;
  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 = 20130407L;

  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 Map<String, double[]> additional;

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

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

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

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

  135.     /** Build a spacecraft state from orbit only.
  136.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  137.      * @param orbit the orbit
  138.      * @param additional additional states
  139.      */
  140.     public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional) {
  141.         this(orbit,
  142.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  143.              DEFAULT_MASS, additional);
  144.     }

  145.     /** Build a spacecraft state from orbit and attitude provider.
  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.      */
  153.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
  154.         throws IllegalArgumentException {
  155.         this(orbit, attitude, DEFAULT_MASS, additional);
  156.     }

  157.     /** Create a new instance from orbit and mass.
  158.      * <p>Attitude law is set to an unspecified default attitude.</p>
  159.      * @param orbit the orbit
  160.      * @param mass the mass (kg)
  161.      * @param additional additional states
  162.      */
  163.     public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional) {
  164.         this(orbit,
  165.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  166.              mass, additional);
  167.     }

  168.     /** Build a spacecraft state from orbit, attitude provider and mass.
  169.      * @param orbit the orbit
  170.      * @param attitude attitude
  171.      * @param mass the mass (kg)
  172.      * @param additional additional states (may be null if no additional states are available)
  173.      * @exception IllegalArgumentException if orbit and attitude dates
  174.      * or frames are not equal
  175.      */
  176.     public SpacecraftState(final Orbit orbit, final Attitude attitude,
  177.                            final double mass, final Map<String, double[]> additional)
  178.         throws IllegalArgumentException {
  179.         checkConsistency(orbit, attitude);
  180.         this.orbit      = orbit;
  181.         this.absPva     = null;
  182.         this.attitude   = attitude;
  183.         this.mass       = mass;
  184.         if (additional == null) {
  185.             this.additional = Collections.emptyMap();
  186.         } else {
  187.             this.additional = new HashMap<String, double[]>(additional.size());
  188.             for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  189.                 this.additional.put(entry.getKey(), entry.getValue().clone());
  190.             }
  191.         }
  192.     }



  193.     /** Build a spacecraft state from orbit only.
  194.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  195.      * @param absPva position-velocity-acceleration
  196.      */
  197.     public SpacecraftState(final AbsolutePVCoordinates absPva) {
  198.         this(absPva,
  199.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  200.              DEFAULT_MASS, null);
  201.     }

  202.     /** Build a spacecraft state from orbit and attitude provider.
  203.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  204.      * @param absPva position-velocity-acceleration
  205.      * @param attitude attitude
  206.      * @exception IllegalArgumentException if orbit and attitude dates
  207.      * or frames are not equal
  208.      */
  209.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude)
  210.         throws IllegalArgumentException {
  211.         this(absPva, attitude, DEFAULT_MASS, null);
  212.     }

  213.     /** Create a new instance from orbit and mass.
  214.      * <p>Attitude law is set to an unspecified default attitude.</p>
  215.      * @param absPva position-velocity-acceleration
  216.      * @param mass the mass (kg)
  217.      */
  218.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) {
  219.         this(absPva,
  220.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  221.              mass, null);
  222.     }

  223.     /** Build a spacecraft state from orbit, attitude provider and mass.
  224.      * @param absPva position-velocity-acceleration
  225.      * @param attitude attitude
  226.      * @param mass the mass (kg)
  227.      * @exception IllegalArgumentException if orbit and attitude dates
  228.      * or frames are not equal
  229.      */
  230.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass)
  231.         throws IllegalArgumentException {
  232.         this(absPva, attitude, mass, null);
  233.     }

  234.     /** Build a spacecraft state from orbit only.
  235.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  236.      * @param absPva position-velocity-acceleration
  237.      * @param additional additional states
  238.      */
  239.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Map<String, double[]> additional) {
  240.         this(absPva,
  241.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  242.              DEFAULT_MASS, additional);
  243.     }

  244.     /** Build a spacecraft state from orbit and attitude provider.
  245.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  246.      * @param absPva position-velocity-acceleration
  247.      * @param attitude attitude
  248.      * @param additional additional states
  249.      * @exception IllegalArgumentException if orbit and attitude dates
  250.      * or frames are not equal
  251.      */
  252.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final Map<String, double[]> additional)
  253.         throws IllegalArgumentException {
  254.         this(absPva, attitude, DEFAULT_MASS, additional);
  255.     }

  256.     /** Create a new instance from orbit and mass.
  257.      * <p>Attitude law is set to an unspecified default attitude.</p>
  258.      * @param absPva position-velocity-acceleration
  259.      * @param mass the mass (kg)
  260.      * @param additional additional states
  261.      */
  262.     public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final Map<String, double[]> additional) {
  263.         this(absPva,
  264.              new LofOffset(absPva.getFrame(), LOFType.VVLH).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  265.              mass, additional);
  266.     }

  267.     /** Build a spacecraft state from orbit, attitude provider and mass.
  268.      * @param absPva position-velocity-acceleration
  269.      * @param attitude attitude
  270.      * @param mass the mass (kg)
  271.      * @param additional additional states (may be null if no additional states are available)
  272.      * @exception IllegalArgumentException if orbit and attitude dates
  273.      * or frames are not equal
  274.      */
  275.     public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude,
  276.                            final double mass, final Map<String, double[]> additional)
  277.         throws IllegalArgumentException {
  278.         checkConsistency(absPva, attitude);
  279.         this.orbit      = null;
  280.         this.absPva     = absPva;
  281.         this.attitude   = attitude;
  282.         this.mass       = mass;
  283.         if (additional == null) {
  284.             this.additional = Collections.emptyMap();
  285.         } else {
  286.             this.additional = new HashMap<String, double[]>(additional.size());
  287.             for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  288.                 this.additional.put(entry.getKey(), entry.getValue().clone());
  289.             }
  290.         }
  291.     }

  292.     /** Add an additional state.
  293.      * <p>
  294.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  295.      * so this method does <em>not</em> change the instance, but rather
  296.      * creates a new instance, which has the same orbit, attitude, mass
  297.      * and additional states as the original instance, except it also
  298.      * has the specified state. If the original instance already had an
  299.      * additional state with the same name, it will be overridden. If it
  300.      * did not have any additional state with that name, the new instance
  301.      * will have one more additional state than the original instance.
  302.      * </p>
  303.      * @param name name of the additional state
  304.      * @param value value of the additional state
  305.      * @return a new instance, with the additional state added
  306.      * @see #hasAdditionalState(String)
  307.      * @see #getAdditionalState(String)
  308.      * @see #getAdditionalStates()
  309.      */
  310.     public SpacecraftState addAdditionalState(final String name, final double... value) {
  311.         final Map<String, double[]> newMap = new HashMap<String, double[]>(additional.size() + 1);
  312.         newMap.putAll(additional);
  313.         newMap.put(name, value.clone());
  314.         if (absPva == null) {
  315.             return new SpacecraftState(orbit, attitude, mass, newMap);
  316.         } else {
  317.             return new SpacecraftState(absPva, attitude, mass, newMap);
  318.         }
  319.     }

  320.     /** Check orbit and attitude dates are equal.
  321.      * @param orbit the orbit
  322.      * @param attitude attitude
  323.      * @exception IllegalArgumentException if orbit and attitude dates
  324.      * are not equal
  325.      */
  326.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  327.         throws IllegalArgumentException {
  328.         if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
  329.             DATE_INCONSISTENCY_THRESHOLD) {
  330.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  331.                                                      orbit.getDate(), attitude.getDate());
  332.         }
  333.         if (orbit.getFrame() != attitude.getReferenceFrame()) {
  334.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  335.                                                      orbit.getFrame().getName(),
  336.                                                      attitude.getReferenceFrame().getName());
  337.         }
  338.     }

  339.     /** Check if the state contains an orbit part.
  340.      * <p>
  341.      * A state contains either an {@link AbsolutePVCoordinates absolute
  342.      * position-velocity-acceleration} or an {@link Orbit orbit}.
  343.      * </p>
  344.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  345.      * will not throw an exception), or false if the state contains an
  346.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  347.      * will not throw an exception)
  348.      */
  349.     public boolean isOrbitDefined() {
  350.         return orbit != null;
  351.     }

  352.     /** Check AbsolutePVCoordinates and attitude dates are equal.
  353.      * @param absPva position-velocity-acceleration
  354.      * @param attitude attitude
  355.      * @exception IllegalArgumentException if orbit and attitude dates
  356.      * are not equal
  357.      */
  358.     private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude)
  359.         throws IllegalArgumentException {
  360.         if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) >
  361.             DATE_INCONSISTENCY_THRESHOLD) {
  362.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  363.                                                      absPva.getDate(), attitude.getDate());
  364.         }
  365.         if (absPva.getFrame() != attitude.getReferenceFrame()) {
  366.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  367.                                                      absPva.getFrame().getName(),
  368.                                                      attitude.getReferenceFrame().getName());
  369.         }
  370.     }

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

  413.     /** {@inheritDoc}
  414.      * <p>
  415.      * The additional states that are interpolated are the ones already present
  416.      * in the instance. The sample instances must therefore have at least the same
  417.      * additional states has the instance. They may have more additional states,
  418.      * but the extra ones will be ignored.
  419.      * </p>
  420.      * <p>
  421.      * The instance and all the sample instances <em>must</em> be based on similar
  422.      * trajectory data, i.e. they must either all be based on orbits or all be based
  423.      * on absolute position-velocity-acceleration. Any inconsistency will trigger
  424.      * an {@link OrekitIllegalStateException}.
  425.      * </p>
  426.      * <p>
  427.      * As this implementation of interpolation is polynomial, it should be used only
  428.      * with small samples (about 10-20 points) in order to avoid <a
  429.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  430.      * and numerical problems (including NaN appearing).
  431.      * </p>
  432.      * @exception OrekitIllegalStateException if some instances are not based on
  433.      * similar trajectory data
  434.      */
  435.     public SpacecraftState interpolate(final AbsoluteDate date,
  436.                                        final Stream<SpacecraftState> sample) {

  437.         // prepare interpolators
  438.         final List<Orbit> orbits;
  439.         final List<AbsolutePVCoordinates> absPvas;
  440.         if (isOrbitDefined()) {
  441.             orbits  = new ArrayList<Orbit>();
  442.             absPvas = null;
  443.         } else {
  444.             orbits  = null;
  445.             absPvas = new ArrayList<AbsolutePVCoordinates>();
  446.         }
  447.         final List<Attitude> attitudes = new ArrayList<Attitude>();
  448.         final HermiteInterpolator massInterpolator = new HermiteInterpolator();
  449.         final Map<String, HermiteInterpolator> additionalInterpolators =
  450.                 new HashMap<String, HermiteInterpolator>(additional.size());
  451.         for (final String name : additional.keySet()) {
  452.             additionalInterpolators.put(name, new HermiteInterpolator());
  453.         }

  454.         // extract sample data
  455.         sample.forEach(state -> {
  456.             final double deltaT = state.getDate().durationFrom(date);
  457.             if (isOrbitDefined()) {
  458.                 orbits.add(state.getOrbit());
  459.             } else {
  460.                 absPvas.add(state.getAbsPVA());
  461.             }
  462.             attitudes.add(state.getAttitude());
  463.             massInterpolator.addSamplePoint(deltaT,
  464.                                             new double[] {
  465.                                                 state.getMass()
  466.                                                 });
  467.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  468.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  469.             }

  470.         });

  471.         // perform interpolations
  472.         final Orbit interpolatedOrbit;
  473.         final AbsolutePVCoordinates interpolatedAbsPva;
  474.         if (isOrbitDefined()) {
  475.             interpolatedOrbit  = orbit.interpolate(date, orbits);
  476.             interpolatedAbsPva = null;
  477.         } else {
  478.             interpolatedOrbit  = null;
  479.             interpolatedAbsPva = absPva.interpolate(date, absPvas);
  480.         }
  481.         final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
  482.         final double interpolatedMass       = massInterpolator.value(0)[0];
  483.         final Map<String, double[]> interpolatedAdditional;
  484.         if (additional.isEmpty()) {
  485.             interpolatedAdditional = null;
  486.         } else {
  487.             interpolatedAdditional = new HashMap<String, double[]>(additional.size());
  488.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  489.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
  490.             }
  491.         }

  492.         // create the complete interpolated state
  493.         if (isOrbitDefined()) {
  494.             return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
  495.                                        interpolatedMass, interpolatedAdditional);
  496.         } else {
  497.             return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude,
  498.                                        interpolatedMass, interpolatedAdditional);
  499.         }

  500.     }

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

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

  538.     /** Get the date.
  539.      * @return date
  540.      */
  541.     public AbsoluteDate getDate() {
  542.         return (absPva == null) ? orbit.getDate() : absPva.getDate();
  543.     }

  544.     /** Get the defining frame.
  545.      * @return the frame in which state is defined
  546.      */
  547.     public Frame getFrame() {
  548.         return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
  549.     }

  550.     /** Check if an additional state is available.
  551.      * @param name name of the additional state
  552.      * @return true if the additional state is available
  553.      * @see #addAdditionalState(String, double[])
  554.      * @see #getAdditionalState(String)
  555.      * @see #getAdditionalStates()
  556.      */
  557.     public boolean hasAdditionalState(final String name) {
  558.         return additional.containsKey(name);
  559.     }

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

  571.         // check instance additional states is a subset of the other one
  572.         for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  573.             final double[] other = state.additional.get(entry.getKey());
  574.             if (other == null) {
  575.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  576.                                           entry.getKey());
  577.             }
  578.             if (other.length != entry.getValue().length) {
  579.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  580.                                                     other.length, entry.getValue().length);
  581.             }
  582.         }

  583.         if (state.additional.size() > additional.size()) {
  584.             // the other state has more additional states
  585.             for (final String name : state.additional.keySet()) {
  586.                 if (!additional.containsKey(name)) {
  587.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  588.                                               name);
  589.                 }
  590.             }
  591.         }

  592.     }

  593.     /** Get an additional state.
  594.      * @param name name of the additional state
  595.      * @return value of the additional state
  596.           * @see #addAdditionalState(String, double[])
  597.      * @see #hasAdditionalState(String)
  598.      * @see #getAdditionalStates()
  599.      */
  600.     public double[] getAdditionalState(final String name) {
  601.         if (!additional.containsKey(name)) {
  602.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  603.         }
  604.         return additional.get(name).clone();
  605.     }

  606.     /** Get an unmodifiable map of additional states.
  607.      * @return unmodifiable map of additional states
  608.      * @see #addAdditionalState(String, double[])
  609.      * @see #hasAdditionalState(String)
  610.      * @see #getAdditionalState(String)
  611.      */
  612.     public Map<String, double[]> getAdditionalStates() {
  613.         return Collections.unmodifiableMap(additional);
  614.     }

  615.     /** Compute the transform from state defining frame to spacecraft frame.
  616.      * <p>The spacecraft frame origin is at the point defined by the orbit
  617.      * (or absolute position-velocity-acceleration), and its orientation is
  618.      * defined by the attitude.</p>
  619.      * @return transform from specified frame to current spacecraft frame
  620.      */
  621.     public Transform toTransform() {
  622.         final TimeStampedPVCoordinates pv = getPVCoordinates();
  623.         return new Transform(pv.getDate(),
  624.                              new Transform(pv.getDate(), pv.negate()),
  625.                              new Transform(pv.getDate(), attitude.getOrientation()));
  626.     }

  627.     /** Get the central attraction coefficient.
  628.      * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
  629.      * state is contains an absolute position-velocity-acceleration rather than an orbit
  630.      */
  631.     public double getMu() {
  632.         return (absPva == null) ? orbit.getMu() : Double.NaN;
  633.     }

  634.     /** Get the Keplerian period.
  635.      * <p>The Keplerian period is computed directly from semi major axis
  636.      * and central acceleration constant.</p>
  637.      * @return keplerian period in seconds, or {code Double.NaN} if the
  638.      * state is contains an absolute position-velocity-acceleration rather
  639.      * than an orbit
  640.      */
  641.     public double getKeplerianPeriod() {
  642.         return (absPva == null) ? orbit.getKeplerianPeriod() : Double.NaN;
  643.     }

  644.     /** Get the Keplerian mean motion.
  645.      * <p>The Keplerian mean motion is computed directly from semi major axis
  646.      * and central acceleration constant.</p>
  647.      * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
  648.      * state is contains an absolute position-velocity-acceleration rather
  649.      * than an orbit
  650.      */
  651.     public double getKeplerianMeanMotion() {
  652.         return (absPva == null) ? orbit.getKeplerianMeanMotion() : Double.NaN;
  653.     }

  654.     /** Get the semi-major axis.
  655.      * @return semi-major axis (m), or {code Double.NaN} if the
  656.      * state is contains an absolute position-velocity-acceleration rather
  657.      * than an orbit
  658.      */
  659.     public double getA() {
  660.         return (absPva == null) ? orbit.getA() : Double.NaN;
  661.     }

  662.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  663.      * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
  664.      * state is contains an absolute position-velocity-acceleration rather
  665.      * than an orbit
  666.      * @see #getE()
  667.      */
  668.     public double getEquinoctialEx() {
  669.         return (absPva == null) ? orbit.getEquinoctialEx() : Double.NaN;
  670.     }

  671.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  672.      * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
  673.      * state is contains an absolute position-velocity-acceleration rather
  674.      * than an orbit
  675.      * @see #getE()
  676.      */
  677.     public double getEquinoctialEy() {
  678.         return (absPva == null) ? orbit.getEquinoctialEy() : Double.NaN;
  679.     }

  680.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  681.      * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
  682.      * state is contains an absolute position-velocity-acceleration rather
  683.      * than an orbit
  684.      * @see #getI()
  685.      */
  686.     public double getHx() {
  687.         return (absPva == null) ? orbit.getHx() : Double.NaN;
  688.     }

  689.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  690.      * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
  691.      * state is contains an absolute position-velocity-acceleration rather
  692.      * than an orbit
  693.      * @see #getI()
  694.      */
  695.     public double getHy() {
  696.         return (absPva == null) ? orbit.getHy() : Double.NaN;
  697.     }

  698.     /** Get the true latitude argument (as per equinoctial parameters).
  699.      * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
  700.      * state is contains an absolute position-velocity-acceleration rather
  701.      * than an orbit
  702.      * @see #getLE()
  703.      * @see #getLM()
  704.      */
  705.     public double getLv() {
  706.         return (absPva == null) ? orbit.getLv() : Double.NaN;
  707.     }

  708.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  709.      * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
  710.      * state is contains an absolute position-velocity-acceleration rather
  711.      * than an orbit
  712.      * @see #getLv()
  713.      * @see #getLM()
  714.      */
  715.     public double getLE() {
  716.         return (absPva == null) ? orbit.getLE() : Double.NaN;
  717.     }

  718.     /** Get the mean longitude argument (as per equinoctial parameters).
  719.      * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
  720.      * state is contains an absolute position-velocity-acceleration rather
  721.      * than an orbit
  722.      * @see #getLv()
  723.      * @see #getLE()
  724.      */
  725.     public double getLM() {
  726.         return (absPva == null) ? orbit.getLM() : Double.NaN;
  727.     }

  728.     // Additional orbital elements

  729.     /** Get the eccentricity.
  730.      * @return eccentricity, or {code Double.NaN} if the
  731.      * state is contains an absolute position-velocity-acceleration rather
  732.      * than an orbit
  733.      * @see #getEquinoctialEx()
  734.      * @see #getEquinoctialEy()
  735.      */
  736.     public double getE() {
  737.         return (absPva == null) ? orbit.getE() : Double.NaN;
  738.     }

  739.     /** Get the inclination.
  740.      * @return inclination (rad)
  741.      * @see #getHx()
  742.      * @see #getHy()
  743.      */
  744.     public double getI() {
  745.         return (absPva == null) ? orbit.getI() : Double.NaN;
  746.     }

  747.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  748.      * <p>
  749.      * Compute the position and velocity of the satellite. This method caches its
  750.      * results, and recompute them only when the method is called with a new value
  751.      * for mu. The result is provided as a reference to the internally cached
  752.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  753.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  754.      * </p>
  755.      * @return pvCoordinates in orbit definition frame
  756.      */
  757.     public TimeStampedPVCoordinates getPVCoordinates() {
  758.         return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  759.     }

  760.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  761.      * <p>
  762.      * Compute the position and velocity of the satellite. This method caches its
  763.      * results, and recompute them only when the method is called with a new value
  764.      * for mu. The result is provided as a reference to the internally cached
  765.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  766.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  767.      * </p>
  768.      * @param outputFrame frame in which coordinates should be defined
  769.      * @return pvCoordinates in orbit definition frame
  770.      */
  771.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  772.         return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  773.     }

  774.     /** Get the attitude.
  775.      * @return the attitude.
  776.      */
  777.     public Attitude getAttitude() {
  778.         return attitude;
  779.     }

  780.     /** Gets the current mass.
  781.      * @return the mass (kg)
  782.      */
  783.     public double getMass() {
  784.         return mass;
  785.     }

  786.     /** Replace the instance with a data transfer object for serialization.
  787.      * @return data transfer object that will be serialized
  788.      */
  789.     private Object writeReplace() {
  790.         return isOrbitDefined() ? new DTOO(this) : new DTOA(this);
  791.     }

  792.     /** Internal class used only for serialization. */
  793.     private static class DTOO implements Serializable {

  794.         /** Serializable UID. */
  795.         private static final long serialVersionUID = 20150916L;

  796.         /** Orbit. */
  797.         private final Orbit orbit;

  798.         /** Attitude and mass double values. */
  799.         private double[] d;

  800.         /** Additional states. */
  801.         private final Map<String, double[]> additional;

  802.         /** Simple constructor.
  803.          * @param state instance to serialize
  804.          */
  805.         private DTOO(final SpacecraftState state) {

  806.             this.orbit      = state.orbit;
  807.             this.additional = state.additional.isEmpty() ? null : state.additional;

  808.             final Rotation rotation             = state.attitude.getRotation();
  809.             final Vector3D spin                 = state.attitude.getSpin();
  810.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  811.             this.d = new double[] {
  812.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  813.                 spin.getX(), spin.getY(), spin.getZ(),
  814.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  815.                 state.mass
  816.             };

  817.         }

  818.         /** Replace the de-serialized data transfer object with a {@link SpacecraftState}.
  819.          * @return replacement {@link SpacecraftState}
  820.          */
  821.         private Object readResolve() {
  822.             return new SpacecraftState(orbit,
  823.                                        new Attitude(orbit.getFrame(),
  824.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  825.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  826.                                                                                       new Vector3D(d[4], d[5], d[6]),
  827.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  828.                                        d[10], additional);
  829.         }

  830.     }

  831.     /** Internal class used only for serialization. */
  832.     private static class DTOA implements Serializable {

  833.         /** Serializable UID. */
  834.         private static final long serialVersionUID = 20150916L;

  835.         /** Absolute position-velocity-acceleration. */
  836.         private final AbsolutePVCoordinates absPva;

  837.         /** Attitude and mass double values. */
  838.         private double[] d;

  839.         /** Additional states. */
  840.         private final Map<String, double[]> additional;

  841.         /** Simple constructor.
  842.          * @param state instance to serialize
  843.          */
  844.         private DTOA(final SpacecraftState state) {

  845.             this.absPva     = state.absPva;
  846.             this.additional = state.additional.isEmpty() ? null : state.additional;

  847.             final Rotation rotation             = state.attitude.getRotation();
  848.             final Vector3D spin                 = state.attitude.getSpin();
  849.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  850.             this.d = new double[] {
  851.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  852.                 spin.getX(), spin.getY(), spin.getZ(),
  853.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  854.                 state.mass
  855.             };

  856.         }

  857.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  858.          * @return replacement {@link SpacecraftState}
  859.          */
  860.         private Object readResolve() {
  861.             return new SpacecraftState(absPva,
  862.                                        new Attitude(absPva.getFrame(),
  863.                                                     new TimeStampedAngularCoordinates(absPva.getDate(),
  864.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  865.                                                                                       new Vector3D(d[4], d[5], d[6]),
  866.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  867.                                        d[10], additional);
  868.         }
  869.     }

  870.     @Override
  871.     public String toString() {
  872.         return "SpacecraftState{" +
  873.                 "orbit=" + orbit +
  874.                 ", attitude=" + attitude +
  875.                 ", mass=" + mass +
  876.                 ", additional=" + additional +
  877.                 '}';
  878.     }
  879. }