SpacecraftState.java

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

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


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

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

  74.     /** Default mass. */
  75.     private static final double DEFAULT_MASS = 1000.0;

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

  81.     /** Orbital state. */
  82.     private final Orbit orbit;

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

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

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

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

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

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

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

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

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

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

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

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

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

  238.     /** Get a time-shifted state.
  239.      * <p>
  240.      * The state can be slightly shifted to close dates. This shift is based on
  241.      * a simple keplerian model for orbit, a linear extrapolation for attitude
  242.      * taking the spin rate into account and neither mass nor additional states
  243.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  244.      * and attitude propagation but should be sufficient for small time shifts
  245.      * or coarse accuracy.
  246.      * </p>
  247.      * <p>
  248.      * As a rough order of magnitude, the following table shows the interpolation
  249.      * errors obtained between this simple shift method and an {@link
  250.      * org.orekit.propagation.analytical.EcksteinHechlerPropagator Eckstein-Heschler
  251.      * propagator} for an 800km altitude nearly circular polar Earth orbit with
  252.      * {@link org.orekit.attitudes.BodyCenterPointing body center pointing}. Beware
  253.      * that these results may be different for other orbits.
  254.      * </p>
  255.      * <table border="1" cellpadding="5">
  256.      * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
  257.      * <th>position error (m)</th><th>velocity error (m/s)</th>
  258.      * <th>attitude error (&deg;)</th></tr>
  259.      * <tr><td bgcolor="#eeeeff"> 60</td><td>  20</td><td>1</td><td>0.001</td></tr>
  260.      * <tr><td bgcolor="#eeeeff">120</td><td> 100</td><td>2</td><td>0.002</td></tr>
  261.      * <tr><td bgcolor="#eeeeff">300</td><td> 600</td><td>4</td><td>0.005</td></tr>
  262.      * <tr><td bgcolor="#eeeeff">600</td><td>2000</td><td>6</td><td>0.008</td></tr>
  263.      * <tr><td bgcolor="#eeeeff">900</td><td>4000</td><td>6</td><td>0.010</td></tr>
  264.      * </table>
  265.      * @param dt time shift in seconds
  266.      * @return a new state, shifted with respect to the instance (which is immutable)
  267.      * except for the mass which stay unchanged
  268.      */
  269.     public SpacecraftState shiftedBy(final double dt) {
  270.         return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  271.                                    mass, additional);
  272.     }

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

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

  299.         // extract sample data
  300.         for (final SpacecraftState state : sample) {
  301.             final double deltaT = state.getDate().durationFrom(date);
  302.             orbits.add(state.getOrbit());
  303.             attitudes.add(state.getAttitude());
  304.             massInterpolator.addSamplePoint(deltaT,
  305.                                             new double[] {
  306.                                                 state.getMass()
  307.                                             });
  308.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  309.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  310.             }
  311.         }

  312.         // perform interpolations
  313.         final Orbit interpolatedOrbit       = orbit.interpolate(date, orbits);
  314.         final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
  315.         final double interpolatedMass       = massInterpolator.value(0)[0];
  316.         final Map<String, double[]> interpolatedAdditional;
  317.         if (additional.isEmpty()) {
  318.             interpolatedAdditional = null;
  319.         } else {
  320.             interpolatedAdditional = new HashMap<String, double[]>(additional.size());
  321.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  322.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
  323.             }
  324.         }

  325.         // create the complete interpolated state
  326.         return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
  327.                                    interpolatedMass, interpolatedAdditional);

  328.     }

  329.     /** Gets the current orbit.
  330.      * @return the orbit
  331.      */
  332.     public Orbit getOrbit() {
  333.         return orbit;
  334.     }

  335.     /** Get the date.
  336.      * @return date
  337.      */
  338.     public AbsoluteDate getDate() {
  339.         return orbit.getDate();
  340.     }

  341.     /** Get the inertial frame.
  342.      * @return the frame
  343.      */
  344.     public Frame getFrame() {
  345.         return orbit.getFrame();
  346.     }

  347.     /** Check if an additional state is available.
  348.      * @param name name of the additional state
  349.      * @return true if the additional state is available
  350.      * @see #addAdditionalState(String, double[])
  351.      * @see #getAdditionalState(String)
  352.      * @see #getAdditionalStates()
  353.      */
  354.     public boolean hasAdditionalState(final String name) {
  355.         return additional.containsKey(name);
  356.     }

  357.     /** Check if two instances have the same set of additional states available.
  358.      * <p>
  359.      * Only the names and dimensions of the additional states are compared,
  360.      * not their values.
  361.      * </p>
  362.      * @param state state to compare to instance
  363.      * @exception OrekitException if either instance or state supports an additional
  364.      * state not supported by the other one
  365.      * @exception DimensionMismatchException if an additional state does not have
  366.      * the same dimension in both states
  367.      */
  368.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  369.         throws OrekitException, DimensionMismatchException {

  370.         // check instance additional states is a subset of the other one
  371.         for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  372.             final double[] other = state.additional.get(entry.getKey());
  373.             if (other == null) {
  374.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  375.                                           entry.getKey());
  376.             }
  377.             if (other.length != entry.getValue().length) {
  378.                 throw new DimensionMismatchException(other.length, entry.getValue().length);
  379.             }
  380.         }

  381.         if (state.additional.size() > additional.size()) {
  382.             // the other state has more additional states
  383.             for (final String name : state.additional.keySet()) {
  384.                 if (!additional.containsKey(name)) {
  385.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  386.                                               name);
  387.                 }
  388.             }
  389.         }

  390.     }

  391.     /** Get an additional state.
  392.      * @param name name of the additional state
  393.      * @return value of the additional state
  394.      * @exception OrekitException if no additional state with that name exists
  395.      * @see #addAdditionalState(String, double[])
  396.      * @see #hasAdditionalState(String)
  397.      * @see #getAdditionalStates()
  398.      */
  399.     public double[] getAdditionalState(final String name) throws OrekitException {
  400.         if (!additional.containsKey(name)) {
  401.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  402.         }
  403.         return additional.get(name).clone();
  404.     }

  405.     /** Get an unmodifiable map of additional states.
  406.      * @return unmodifiable map of additional states
  407.      * @see #addAdditionalState(String, double[])
  408.      * @see #hasAdditionalState(String)
  409.      * @see #getAdditionalState(String)
  410.      */
  411.     public Map<String, double[]> getAdditionalStates() {
  412.         return Collections.unmodifiableMap(additional);
  413.     }

  414.     /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
  415.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  416.      * and its orientation is defined by the attitude.</p>
  417.      * @return transform from specified frame to current spacecraft frame
  418.      */
  419.     public Transform toTransform() {
  420.         final AbsoluteDate date = orbit.getDate();
  421.         return new Transform(date,
  422.                              new Transform(date, orbit.getPVCoordinates().negate()),
  423.                              new Transform(date, attitude.getOrientation()));
  424.     }

  425.     /** Get the central attraction coefficient.
  426.      * @return mu central attraction coefficient (m^3/s^2)
  427.      */
  428.     public double getMu() {
  429.         return orbit.getMu();
  430.     }

  431.     /** Get the keplerian period.
  432.      * <p>The keplerian period is computed directly from semi major axis
  433.      * and central acceleration constant.</p>
  434.      * @return keplerian period in seconds
  435.      */
  436.     public double getKeplerianPeriod() {
  437.         return orbit.getKeplerianPeriod();
  438.     }

  439.     /** Get the keplerian mean motion.
  440.      * <p>The keplerian mean motion is computed directly from semi major axis
  441.      * and central acceleration constant.</p>
  442.      * @return keplerian mean motion in radians per second
  443.      */
  444.     public double getKeplerianMeanMotion() {
  445.         return orbit.getKeplerianMeanMotion();
  446.     }

  447.     /** Get the semi-major axis.
  448.      * @return semi-major axis (m)
  449.      */
  450.     public double getA() {
  451.         return orbit.getA();
  452.     }

  453.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  454.      * @return e cos(ω + Ω), first component of eccentricity vector
  455.      * @see #getE()
  456.      */
  457.     public double getEquinoctialEx() {
  458.         return orbit.getEquinoctialEx();
  459.     }

  460.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  461.      * @return e sin(ω + Ω), second component of the eccentricity vector
  462.      * @see #getE()
  463.      */
  464.     public double getEquinoctialEy() {
  465.         return orbit.getEquinoctialEy();
  466.     }

  467.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  468.      * @return tan(i/2) cos(Ω), first component of the inclination vector
  469.      * @see #getI()
  470.      */
  471.     public double getHx() {
  472.         return orbit.getHx();
  473.     }

  474.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  475.      * @return tan(i/2) sin(Ω), second component of the inclination vector
  476.      * @see #getI()
  477.      */
  478.     public double getHy() {
  479.         return orbit.getHy();
  480.     }

  481.     /** Get the true latitude argument (as per equinoctial parameters).
  482.      * @return v + ω + Ω true latitude argument (rad)
  483.      * @see #getLE()
  484.      * @see #getLM()
  485.      */
  486.     public double getLv() {
  487.         return orbit.getLv();
  488.     }

  489.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  490.      * @return E + ω + Ω eccentric latitude argument (rad)
  491.      * @see #getLv()
  492.      * @see #getLM()
  493.      */
  494.     public double getLE() {
  495.         return orbit.getLE();
  496.     }

  497.     /** Get the mean latitude argument (as per equinoctial parameters).
  498.      * @return M + ω + Ω mean latitude argument (rad)
  499.      * @see #getLv()
  500.      * @see #getLE()
  501.      */
  502.     public double getLM() {
  503.         return orbit.getLM();
  504.     }

  505.     // Additional orbital elements

  506.     /** Get the eccentricity.
  507.      * @return eccentricity
  508.      * @see #getEquinoctialEx()
  509.      * @see #getEquinoctialEy()
  510.      */
  511.     public double getE() {
  512.         return orbit.getE();
  513.     }

  514.     /** Get the inclination.
  515.      * @return inclination (rad)
  516.      * @see #getHx()
  517.      * @see #getHy()
  518.      */
  519.     public double getI() {
  520.         return orbit.getI();
  521.     }

  522.     /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
  523.      * Compute the position and velocity of the satellite. This method caches its
  524.      * results, and recompute them only when the method is called with a new value
  525.      * for mu. The result is provided as a reference to the internally cached
  526.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  527.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  528.      * @return pvCoordinates in orbit definition frame
  529.      */
  530.     public TimeStampedPVCoordinates getPVCoordinates() {
  531.         return orbit.getPVCoordinates();
  532.     }

  533.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  534.      * Compute the position and velocity of the satellite. This method caches its
  535.      * results, and recompute them only when the method is called with a new value
  536.      * for mu. The result is provided as a reference to the internally cached
  537.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  538.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  539.      * @param outputFrame frame in which coordinates should be defined
  540.      * @return pvCoordinates in orbit definition frame
  541.      * @exception OrekitException if the transformation between frames cannot be computed
  542.      */
  543.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame)
  544.         throws OrekitException {
  545.         return orbit.getPVCoordinates(outputFrame);
  546.     }

  547.     /** Get the attitude.
  548.      * @return the attitude.
  549.      */
  550.     public Attitude getAttitude() {
  551.         return attitude;
  552.     }

  553.     /** Gets the current mass.
  554.      * @return the mass (kg)
  555.      */
  556.     public double getMass() {
  557.         return mass;
  558.     }

  559.     /** Replace the instance with a data transfer object for serialization.
  560.      * @return data transfer object that will be serialized
  561.      */
  562.     private Object writeReplace() {
  563.         return new DTO(this);
  564.     }

  565.     /** Internal class used only for serialization. */
  566.     private static class DTO implements Serializable {

  567.         /** Serializable UID. */
  568.         private static final long serialVersionUID = 20140617L;

  569.         /** Orbit. */
  570.         private final Orbit orbit;

  571.         /** Attitude and mass double values. */
  572.         private double[] d;

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

  575.         /** Simple constructor.
  576.          * @param state instance to serialize
  577.          */
  578.         private DTO(final SpacecraftState state) {

  579.             this.orbit      = state.orbit;
  580.             this.additional = state.additional.isEmpty() ? null : state.additional;

  581.             final Rotation rotation             = state.attitude.getRotation();
  582.             final Vector3D spin                 = state.attitude.getSpin();
  583.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  584.             this.d = new double[] {
  585.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  586.                 spin.getX(), spin.getY(), spin.getZ(),
  587.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  588.                 state.mass
  589.             };

  590.         }

  591.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  592.          * @return replacement {@link SpacecraftState}
  593.          */
  594.         private Object readResolve() {
  595.             return new SpacecraftState(orbit,
  596.                                        new Attitude(orbit.getFrame(),
  597.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  598.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  599.                                                                                       new Vector3D(d[4], d[5], d[6]),
  600.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  601.                                        d[10], additional);
  602.         }

  603.     }

  604. }