SpacecraftState.java

  1. /* Copyright 2002-2019 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.OrekitIllegalArgumentException;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.frames.Frame;
  37. import org.orekit.frames.LOFType;
  38. import org.orekit.frames.Transform;
  39. import org.orekit.orbits.Orbit;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.time.TimeInterpolable;
  42. import org.orekit.time.TimeShiftable;
  43. import org.orekit.time.TimeStamped;
  44. import org.orekit.utils.TimeStampedAngularCoordinates;
  45. import org.orekit.utils.TimeStampedPVCoordinates;


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  284.         // prepare interpolators
  285.         final List<Orbit> orbits = new ArrayList<>();
  286.         final List<Attitude> attitudes = new ArrayList<>();
  287.         final HermiteInterpolator massInterpolator = new HermiteInterpolator();
  288.         final Map<String, HermiteInterpolator> additionalInterpolators =
  289.                 new HashMap<String, HermiteInterpolator>(additional.size());
  290.         for (final String name : additional.keySet()) {
  291.             additionalInterpolators.put(name, new HermiteInterpolator());
  292.         }

  293.         // extract sample data
  294.         sample.forEach(state -> {
  295.             final double deltaT = state.getDate().durationFrom(date);
  296.             orbits.add(state.getOrbit());
  297.             attitudes.add(state.getAttitude());
  298.             massInterpolator.addSamplePoint(deltaT,
  299.                                             new double[] {
  300.                                                  state.getMass()
  301.                                             });
  302.             for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
  303.                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  304.             }
  305.         });

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

  319.         // create the complete interpolated state
  320.         return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
  321.                                    interpolatedMass, interpolatedAdditional);

  322.     }

  323.     /** Gets the current orbit.
  324.      * @return the orbit
  325.      */
  326.     public Orbit getOrbit() {
  327.         return orbit;
  328.     }

  329.     /** Get the date.
  330.      * @return date
  331.      */
  332.     public AbsoluteDate getDate() {
  333.         return orbit.getDate();
  334.     }

  335.     /** Get the inertial frame.
  336.      * @return the frame
  337.      */
  338.     public Frame getFrame() {
  339.         return orbit.getFrame();
  340.     }

  341.     /** Check if an additional state is available.
  342.      * @param name name of the additional state
  343.      * @return true if the additional state is available
  344.      * @see #addAdditionalState(String, double[])
  345.      * @see #getAdditionalState(String)
  346.      * @see #getAdditionalStates()
  347.      */
  348.     public boolean hasAdditionalState(final String name) {
  349.         return additional.containsKey(name);
  350.     }

  351.     /** Check if two instances have the same set of additional states available.
  352.      * <p>
  353.      * Only the names and dimensions of the additional states are compared,
  354.      * not their values.
  355.      * </p>
  356.      * @param state state to compare to instance
  357.      * @exception MathIllegalStateException if an additional state does not have
  358.      * the same dimension in both states
  359.      */
  360.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  361.         throws MathIllegalStateException {

  362.         // check instance additional states is a subset of the other one
  363.         for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
  364.             final double[] other = state.additional.get(entry.getKey());
  365.             if (other == null) {
  366.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  367.                                           entry.getKey());
  368.             }
  369.             if (other.length != entry.getValue().length) {
  370.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  371.                                                     other.length, entry.getValue().length);
  372.             }
  373.         }

  374.         if (state.additional.size() > additional.size()) {
  375.             // the other state has more additional states
  376.             for (final String name : state.additional.keySet()) {
  377.                 if (!additional.containsKey(name)) {
  378.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  379.                                               name);
  380.                 }
  381.             }
  382.         }

  383.     }

  384.     /** Get an additional state.
  385.      * @param name name of the additional state
  386.      * @return value of the additional state
  387.           * @see #addAdditionalState(String, double[])
  388.      * @see #hasAdditionalState(String)
  389.      * @see #getAdditionalStates()
  390.      */
  391.     public double[] getAdditionalState(final String name) {
  392.         if (!additional.containsKey(name)) {
  393.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  394.         }
  395.         return additional.get(name).clone();
  396.     }

  397.     /** Get an unmodifiable map of additional states.
  398.      * @return unmodifiable map of additional states
  399.      * @see #addAdditionalState(String, double[])
  400.      * @see #hasAdditionalState(String)
  401.      * @see #getAdditionalState(String)
  402.      */
  403.     public Map<String, double[]> getAdditionalStates() {
  404.         return Collections.unmodifiableMap(additional);
  405.     }

  406.     /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
  407.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  408.      * and its orientation is defined by the attitude.</p>
  409.      * @return transform from specified frame to current spacecraft frame
  410.      */
  411.     public Transform toTransform() {
  412.         final AbsoluteDate date = orbit.getDate();
  413.         return new Transform(date,
  414.                              new Transform(date, orbit.getPVCoordinates().negate()),
  415.                              new Transform(date, attitude.getOrientation()));
  416.     }

  417.     /** Get the central attraction coefficient.
  418.      * @return mu central attraction coefficient (m^3/s^2)
  419.      */
  420.     public double getMu() {
  421.         return orbit.getMu();
  422.     }

  423.     /** Get the Keplerian period.
  424.      * <p>The Keplerian period is computed directly from semi major axis
  425.      * and central acceleration constant.</p>
  426.      * @return Keplerian period in seconds
  427.      */
  428.     public double getKeplerianPeriod() {
  429.         return orbit.getKeplerianPeriod();
  430.     }

  431.     /** Get the Keplerian mean motion.
  432.      * <p>The Keplerian mean motion is computed directly from semi major axis
  433.      * and central acceleration constant.</p>
  434.      * @return Keplerian mean motion in radians per second
  435.      */
  436.     public double getKeplerianMeanMotion() {
  437.         return orbit.getKeplerianMeanMotion();
  438.     }

  439.     /** Get the semi-major axis.
  440.      * @return semi-major axis (m)
  441.      */
  442.     public double getA() {
  443.         return orbit.getA();
  444.     }

  445.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  446.      * @return e cos(ω + Ω), first component of eccentricity vector
  447.      * @see #getE()
  448.      */
  449.     public double getEquinoctialEx() {
  450.         return orbit.getEquinoctialEx();
  451.     }

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

  459.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  460.      * @return tan(i/2) cos(Ω), first component of the inclination vector
  461.      * @see #getI()
  462.      */
  463.     public double getHx() {
  464.         return orbit.getHx();
  465.     }

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

  473.     /** Get the true longitude argument (as per equinoctial parameters).
  474.      * @return v + ω + Ω true longitude argument (rad)
  475.      * @see #getLE()
  476.      * @see #getLM()
  477.      */
  478.     public double getLv() {
  479.         return orbit.getLv();
  480.     }

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

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

  497.     // Additional orbital elements

  498.     /** Get the eccentricity.
  499.      * @return eccentricity
  500.      * @see #getEquinoctialEx()
  501.      * @see #getEquinoctialEy()
  502.      */
  503.     public double getE() {
  504.         return orbit.getE();
  505.     }

  506.     /** Get the inclination.
  507.      * @return inclination (rad)
  508.      * @see #getHx()
  509.      * @see #getHy()
  510.      */
  511.     public double getI() {
  512.         return orbit.getI();
  513.     }

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

  525.     /** Get the {@link TimeStampedPVCoordinates} in given output frame.
  526.      * Compute the position and velocity of the satellite. This method caches its
  527.      * results, and recompute them only when the method is called with a new value
  528.      * for mu. The result is provided as a reference to the internally cached
  529.      * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
  530.      * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
  531.      * @param outputFrame frame in which coordinates should be defined
  532.      * @return pvCoordinates in orbit definition frame
  533.      */
  534.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  535.         return orbit.getPVCoordinates(outputFrame);
  536.     }

  537.     /** Get the attitude.
  538.      * @return the attitude.
  539.      */
  540.     public Attitude getAttitude() {
  541.         return attitude;
  542.     }

  543.     /** Gets the current mass.
  544.      * @return the mass (kg)
  545.      */
  546.     public double getMass() {
  547.         return mass;
  548.     }

  549.     /** Replace the instance with a data transfer object for serialization.
  550.      * @return data transfer object that will be serialized
  551.      */
  552.     private Object writeReplace() {
  553.         return new DTO(this);
  554.     }

  555.     /** Internal class used only for serialization. */
  556.     private static class DTO implements Serializable {

  557.         /** Serializable UID. */
  558.         private static final long serialVersionUID = 20140617L;

  559.         /** Orbit. */
  560.         private final Orbit orbit;

  561.         /** Attitude and mass double values. */
  562.         private double[] d;

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

  565.         /** Simple constructor.
  566.          * @param state instance to serialize
  567.          */
  568.         private DTO(final SpacecraftState state) {

  569.             this.orbit      = state.orbit;
  570.             this.additional = state.additional.isEmpty() ? null : state.additional;

  571.             final Rotation rotation             = state.attitude.getRotation();
  572.             final Vector3D spin                 = state.attitude.getSpin();
  573.             final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
  574.             this.d = new double[] {
  575.                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  576.                 spin.getX(), spin.getY(), spin.getZ(),
  577.                 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
  578.                 state.mass
  579.             };

  580.         }

  581.         /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
  582.          * @return replacement {@link SpacecraftState}
  583.          */
  584.         private Object readResolve() {
  585.             return new SpacecraftState(orbit,
  586.                                        new Attitude(orbit.getFrame(),
  587.                                                     new TimeStampedAngularCoordinates(orbit.getDate(),
  588.                                                                                       new Rotation(d[0], d[1], d[2], d[3], false),
  589.                                                                                       new Vector3D(d[4], d[5], d[6]),
  590.                                                                                       new Vector3D(d[7], d[8], d[9]))),
  591.                                        d[10], additional);
  592.         }

  593.     }

  594. }