SpacecraftState.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.OrekitExceptionWrapper;
  35. import org.orekit.errors.OrekitIllegalArgumentException;
  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.TimeStampedAngularCoordinates;
  46. import org.orekit.utils.TimeStampedPVCoordinates;


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

  74.     /** Serializable UID. */
  75.     private static final long serialVersionUID = 20130407L;

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

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

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

  85.     /** Attitude. */
  86.     private final Attitude attitude;

  87.     /** Current mass (kg). */
  88.     private final double mass;

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

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

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

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

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

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

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

  160.     /** Create a new instance from orbit and mass.
  161.      * <p>Attitude law is set to an unspecified default attitude.</p>
  162.      * @param orbit the orbit
  163.      * @param mass the mass (kg)
  164.      * @param additional additional states
  165.      * @exception OrekitException if default attitude cannot be computed
  166.      */
  167.     public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional)
  168.         throws OrekitException {
  169.         this(orbit,
  170.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  171.              mass, additional);
  172.     }

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

  197.     /** Add an additional state.
  198.      * <p>
  199.      * {@link SpacecraftState SpacecraftState} instances are immutable,
  200.      * so this method does <em>not</em> change the instance, but rather
  201.      * creates a new instance, which has the same orbit, attitude, mass
  202.      * and additional states as the original instance, except it also
  203.      * has the specified state. If the original instance already had an
  204.      * additional state with the same name, it will be overridden. If it
  205.      * did not have any additional state with that name, the new instance
  206.      * will have one more additional state than the original instance.
  207.      * </p>
  208.      * @param name name of the additional state
  209.      * @param value value of the additional state
  210.      * @return a new instance, with the additional state added
  211.      * @see #hasAdditionalState(String)
  212.      * @see #getAdditionalState(String)
  213.      * @see #getAdditionalStates()
  214.      */
  215.     public SpacecraftState addAdditionalState(final String name, final double... value) {
  216.         final Map<String, double[]> newMap = new HashMap<String, double[]>(additional.size() + 1);
  217.         newMap.putAll(additional);
  218.         newMap.put(name, value.clone());
  219.         return new SpacecraftState(orbit, attitude, mass, newMap);
  220.     }

  221.     /** Check orbit and attitude dates are equal.
  222.      * @param orbit the orbit
  223.      * @param attitude attitude
  224.      * @exception IllegalArgumentException if orbit and attitude dates
  225.      * are not equal
  226.      */
  227.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  228.         throws IllegalArgumentException {
  229.         if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
  230.             DATE_INCONSISTENCY_THRESHOLD) {
  231.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  232.                                                      orbit.getDate(), attitude.getDate());
  233.         }
  234.         if (orbit.getFrame() != attitude.getReferenceFrame()) {
  235.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  236.                                                      orbit.getFrame().getName(),
  237.                                                      attitude.getReferenceFrame().getName());
  238.         }
  239.     }

  240.     /** Get a time-shifted state.
  241.      * <p>
  242.      * The state can be slightly shifted to close dates. This shift is based on
  243.      * simple models. For orbits, the model is a Keplerian one if no derivatives
  244.      * are available in the orbit, or Keplerian plus quadratic effect of the
  245.      * non-Keplerian acceleration if derivatives are available. For attitude,
  246.      * a polynomial model is used. Neither mass nor additional states change.
  247.      * Shifting is <em>not</em> intended as a replacement for proper orbit
  248.      * and attitude propagation but should be sufficient for small time shifts
  249.      * or coarse accuracy.
  250.      * </p>
  251.      * <p>
  252.      * As a rough order of magnitude, the following table shows the extrapolation
  253.      * errors obtained between this simple shift method and an {@link
  254.      * org.orekit.propagation.numerical.NumericalPropagator numerical
  255.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  256.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  257.      * Beware that these results will be different for other orbits.
  258.      * </p>
  259.      * <table border="1" cellpadding="5">
  260.      * <caption>Extrapolation Error</caption>
  261.      * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
  262.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  263.      * <tr><td bgcolor="#eeeeff"> 60</td><td>  18</td><td> 1.1</td></tr>
  264.      * <tr><td bgcolor="#eeeeff">120</td><td>  72</td><td> 9.1</td></tr>
  265.      * <tr><td bgcolor="#eeeeff">300</td><td> 447</td><td> 140</td></tr>
  266.      * <tr><td bgcolor="#eeeeff">600</td><td>1601</td><td>1067</td></tr>
  267.      * <tr><td bgcolor="#eeeeff">900</td><td>3141</td><td>3307</td></tr>
  268.      * </table>
  269.      * @param dt time shift in seconds
  270.      * @return a new state, shifted with respect to the instance (which is immutable)
  271.      * except for the mass and additional states which stay unchanged
  272.      */
  273.     public SpacecraftState shiftedBy(final double dt) {
  274.         return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  275.                                    mass, additional);
  276.     }

  277.     /** {@inheritDoc}
  278.      * <p>
  279.      * The additional states that are interpolated are the ones already present
  280.      * in the instance. The sample instances must therefore have at least the same
  281.      * additional states has the instance. They may have more additional states,
  282.      * but the extra ones will be ignored.
  283.      * </p>
  284.      * <p>
  285.      * As this implementation of interpolation is polynomial, it should be used only
  286.      * with small samples (about 10-20 points) in order to avoid <a
  287.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  288.      * and numerical problems (including NaN appearing).
  289.      * </p>
  290.      */
  291.     public SpacecraftState interpolate(final AbsoluteDate date,
  292.                                        final Stream<SpacecraftState> sample)
  293.             throws OrekitException {

  294.         // prepare interpolators
  295.         final List<Orbit> orbits = new ArrayList<>();
  296.         final List<Attitude> attitudes = new ArrayList<>();
  297.         final HermiteInterpolator massInterpolator = new HermiteInterpolator();
  298.         final Map<String, HermiteInterpolator> additionalInterpolators =
  299.                 new HashMap<String, HermiteInterpolator>(additional.size());
  300.         for (final String name : additional.keySet()) {
  301.             additionalInterpolators.put(name, new HermiteInterpolator());
  302.         }

  303.         // extract sample data
  304.         try {
  305.             sample.forEach(state -> {
  306.                 try {
  307.                     final double deltaT = state.getDate().durationFrom(date);
  308.                     orbits.add(state.getOrbit());
  309.                     attitudes.add(state.getAttitude());
  310.                     massInterpolator.addSamplePoint(deltaT,
  311.                                                     new double[] {
  312.                                                          state.getMass()
  313.                                                     });
  314.                     for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  315.                         entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  316.                     }
  317.                 } catch (OrekitException oe) {
  318.                     throw new OrekitExceptionWrapper(oe);
  319.                 }
  320.             });
  321.         } catch (OrekitExceptionWrapper oew) {
  322.             throw oew.getException();
  323.         }

  324.         // perform interpolations
  325.         final Orbit interpolatedOrbit       = orbit.interpolate(date, orbits);
  326.         final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
  327.         final double interpolatedMass       = massInterpolator.value(0)[0];
  328.         final Map<String, double[]> interpolatedAdditional;
  329.         if (additional.isEmpty()) {
  330.             interpolatedAdditional = null;
  331.         } else {
  332.             interpolatedAdditional = new HashMap<String, double[]>(additional.size());
  333.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  334.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
  335.             }
  336.         }

  337.         // create the complete interpolated state
  338.         return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
  339.                                    interpolatedMass, interpolatedAdditional);

  340.     }

  341.     /** Gets the current orbit.
  342.      * @return the orbit
  343.      */
  344.     public Orbit getOrbit() {
  345.         return orbit;
  346.     }

  347.     /** Get the date.
  348.      * @return date
  349.      */
  350.     public AbsoluteDate getDate() {
  351.         return orbit.getDate();
  352.     }

  353.     /** Get the inertial frame.
  354.      * @return the frame
  355.      */
  356.     public Frame getFrame() {
  357.         return orbit.getFrame();
  358.     }

  359.     /** Check if an additional state is available.
  360.      * @param name name of the additional state
  361.      * @return true if the additional state is available
  362.      * @see #addAdditionalState(String, double[])
  363.      * @see #getAdditionalState(String)
  364.      * @see #getAdditionalStates()
  365.      */
  366.     public boolean hasAdditionalState(final String name) {
  367.         return additional.containsKey(name);
  368.     }

  369.     /** Check if two instances have the same set of additional states available.
  370.      * <p>
  371.      * Only the names and dimensions of the additional states are compared,
  372.      * not their values.
  373.      * </p>
  374.      * @param state state to compare to instance
  375.      * @exception OrekitException if either instance or state supports an additional
  376.      * state not supported by the other one
  377.      * @exception MathIllegalStateException if an additional state does not have
  378.      * the same dimension in both states
  379.      */
  380.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  381.         throws OrekitException, MathIllegalStateException {

  382.         // check instance additional states is a subset of the other one
  383.         for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  384.             final double[] other = state.additional.get(entry.getKey());
  385.             if (other == null) {
  386.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  387.                                           entry.getKey());
  388.             }
  389.             if (other.length != entry.getValue().length) {
  390.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  391.                                                     other.length, entry.getValue().length);
  392.             }
  393.         }

  394.         if (state.additional.size() > additional.size()) {
  395.             // the other state has more additional states
  396.             for (final String name : state.additional.keySet()) {
  397.                 if (!additional.containsKey(name)) {
  398.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  399.                                               name);
  400.                 }
  401.             }
  402.         }

  403.     }

  404.     /** Get an additional state.
  405.      * @param name name of the additional state
  406.      * @return value of the additional state
  407.      * @exception OrekitException if no additional state with that name exists
  408.      * @see #addAdditionalState(String, double[])
  409.      * @see #hasAdditionalState(String)
  410.      * @see #getAdditionalStates()
  411.      */
  412.     public double[] getAdditionalState(final String name) throws OrekitException {
  413.         if (!additional.containsKey(name)) {
  414.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  415.         }
  416.         return additional.get(name).clone();
  417.     }

  418.     /** Get an unmodifiable map of additional states.
  419.      * @return unmodifiable map of additional states
  420.      * @see #addAdditionalState(String, double[])
  421.      * @see #hasAdditionalState(String)
  422.      * @see #getAdditionalState(String)
  423.      */
  424.     public Map<String, double[]> getAdditionalStates() {
  425.         return Collections.unmodifiableMap(additional);
  426.     }

  427.     /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
  428.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  429.      * and its orientation is defined by the attitude.</p>
  430.      * @return transform from specified frame to current spacecraft frame
  431.      */
  432.     public Transform toTransform() {
  433.         final AbsoluteDate date = orbit.getDate();
  434.         return new Transform(date,
  435.                              new Transform(date, orbit.getPVCoordinates().negate()),
  436.                              new Transform(date, attitude.getOrientation()));
  437.     }

  438.     /** Get the central attraction coefficient.
  439.      * @return mu central attraction coefficient (m^3/s^2)
  440.      */
  441.     public double getMu() {
  442.         return orbit.getMu();
  443.     }

  444.     /** Get the Keplerian period.
  445.      * <p>The Keplerian period is computed directly from semi major axis
  446.      * and central acceleration constant.</p>
  447.      * @return Keplerian period in seconds
  448.      */
  449.     public double getKeplerianPeriod() {
  450.         return orbit.getKeplerianPeriod();
  451.     }

  452.     /** Get the Keplerian mean motion.
  453.      * <p>The Keplerian mean motion is computed directly from semi major axis
  454.      * and central acceleration constant.</p>
  455.      * @return Keplerian mean motion in radians per second
  456.      */
  457.     public double getKeplerianMeanMotion() {
  458.         return orbit.getKeplerianMeanMotion();
  459.     }

  460.     /** Get the semi-major axis.
  461.      * @return semi-major axis (m)
  462.      */
  463.     public double getA() {
  464.         return orbit.getA();
  465.     }

  466.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  467.      * @return e cos(ω + Ω), first component of eccentricity vector
  468.      * @see #getE()
  469.      */
  470.     public double getEquinoctialEx() {
  471.         return orbit.getEquinoctialEx();
  472.     }

  473.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  474.      * @return e sin(ω + Ω), second component of the eccentricity vector
  475.      * @see #getE()
  476.      */
  477.     public double getEquinoctialEy() {
  478.         return orbit.getEquinoctialEy();
  479.     }

  480.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  481.      * @return tan(i/2) cos(Ω), first component of the inclination vector
  482.      * @see #getI()
  483.      */
  484.     public double getHx() {
  485.         return orbit.getHx();
  486.     }

  487.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  488.      * @return tan(i/2) sin(Ω), second component of the inclination vector
  489.      * @see #getI()
  490.      */
  491.     public double getHy() {
  492.         return orbit.getHy();
  493.     }

  494.     /** Get the true latitude argument (as per equinoctial parameters).
  495.      * @return v + ω + Ω true latitude argument (rad)
  496.      * @see #getLE()
  497.      * @see #getLM()
  498.      */
  499.     public double getLv() {
  500.         return orbit.getLv();
  501.     }

  502.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  503.      * @return E + ω + Ω eccentric latitude argument (rad)
  504.      * @see #getLv()
  505.      * @see #getLM()
  506.      */
  507.     public double getLE() {
  508.         return orbit.getLE();
  509.     }

  510.     /** Get the mean latitude argument (as per equinoctial parameters).
  511.      * @return M + ω + Ω mean latitude argument (rad)
  512.      * @see #getLv()
  513.      * @see #getLE()
  514.      */
  515.     public double getLM() {
  516.         return orbit.getLM();
  517.     }

  518.     // Additional orbital elements

  519.     /** Get the eccentricity.
  520.      * @return eccentricity
  521.      * @see #getEquinoctialEx()
  522.      * @see #getEquinoctialEy()
  523.      */
  524.     public double getE() {
  525.         return orbit.getE();
  526.     }

  527.     /** Get the inclination.
  528.      * @return inclination (rad)
  529.      * @see #getHx()
  530.      * @see #getHy()
  531.      */
  532.     public double getI() {
  533.         return orbit.getI();
  534.     }

  535.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  536.      * Compute the position and velocity of the satellite. This method caches its
  537.      * results, and recompute them only when the method is called with a new value
  538.      * for mu. The result is provided as a reference to the internally cached
  539.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  540.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  541.      * @return pvCoordinates in orbit definition frame
  542.      */
  543.     public TimeStampedPVCoordinates getPVCoordinates() {
  544.         return orbit.getPVCoordinates();
  545.     }

  546.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  547.      * Compute the position and velocity of the satellite. This method caches its
  548.      * results, and recompute them only when the method is called with a new value
  549.      * for mu. The result is provided as a reference to the internally cached
  550.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  551.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  552.      * @param outputFrame frame in which coordinates should be defined
  553.      * @return pvCoordinates in orbit definition frame
  554.      * @exception OrekitException if the transformation between frames cannot be computed
  555.      */
  556.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame)
  557.         throws OrekitException {
  558.         return orbit.getPVCoordinates(outputFrame);
  559.     }

  560.     /** Get the attitude.
  561.      * @return the attitude.
  562.      */
  563.     public Attitude getAttitude() {
  564.         return attitude;
  565.     }

  566.     /** Gets the current mass.
  567.      * @return the mass (kg)
  568.      */
  569.     public double getMass() {
  570.         return mass;
  571.     }

  572.     /** Replace the instance with a data transfer object for serialization.
  573.      * @return data transfer object that will be serialized
  574.      */
  575.     private Object writeReplace() {
  576.         return new DTO(this);
  577.     }

  578.     /** Internal class used only for serialization. */
  579.     private static class DTO implements Serializable {

  580.         /** Serializable UID. */
  581.         private static final long serialVersionUID = 20140617L;

  582.         /** Orbit. */
  583.         private final Orbit orbit;

  584.         /** Attitude and mass double values. */
  585.         private double[] d;

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

  588.         /** Simple constructor.
  589.          * @param state instance to serialize
  590.          */
  591.         private DTO(final SpacecraftState state) {

  592.             this.orbit      = state.orbit;
  593.             this.additional = state.additional.isEmpty() ? null : state.additional;

  594.             final Rotation rotation             = state.attitude.getRotation();
  595.             final Vector3D spin                 = state.attitude.getSpin();
  596.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  597.             this.d = new double[] {
  598.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  599.                 spin.getX(), spin.getY(), spin.getZ(),
  600.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  601.                 state.mass
  602.             };

  603.         }

  604.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  605.          * @return replacement {@link SpacecraftState}
  606.          */
  607.         private Object readResolve() {
  608.             return new SpacecraftState(orbit,
  609.                                        new Attitude(orbit.getFrame(),
  610.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  611.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  612.                                                                                       new Vector3D(d[4], d[5], d[6]),
  613.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  614.                                        d[10], additional);
  615.         }

  616.     }

  617. }