SpacecraftState.java

  1. /* Copyright 2002-2013 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.orekit.attitudes.Attitude;
  28. import org.orekit.attitudes.LofOffset;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.frames.Frame;
  32. import org.orekit.frames.LOFType;
  33. import org.orekit.frames.Transform;
  34. import org.orekit.orbits.Orbit;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.TimeInterpolable;
  37. import org.orekit.time.TimeShiftable;
  38. import org.orekit.time.TimeStamped;
  39. import org.orekit.utils.PVCoordinates;


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

  67.     /** Serializable UID. */
  68.     private static final long serialVersionUID = 20130407L;

  69.     /** Default mass. */
  70.     private static final double DEFAULT_MASS = 1000.0;

  71.     /** Orbital state. */
  72.     private final Orbit orbit;

  73.     /** Attitude. */
  74.     private final Attitude attitude;

  75.     /** Current mass (kg). */
  76.     private final double mass;

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

  79.     /** Build a spacecraft state from orbit only.
  80.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  81.      * @param orbit the orbit
  82.      * @exception OrekitException if default attitude cannot be computed
  83.      */
  84.     public SpacecraftState(final Orbit orbit)
  85.         throws OrekitException {
  86.         this(orbit,
  87.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  88.              DEFAULT_MASS, null);
  89.     }

  90.     /** Build a spacecraft state from orbit and attitude provider.
  91.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  92.      * @param orbit the orbit
  93.      * @param attitude attitude
  94.      * @exception IllegalArgumentException if orbit and attitude dates
  95.      * or frames are not equal
  96.      */
  97.     public SpacecraftState(final Orbit orbit, final Attitude attitude)
  98.         throws IllegalArgumentException {
  99.         this(orbit, attitude, DEFAULT_MASS, null);
  100.     }

  101.     /** Create a new instance from orbit and mass.
  102.      * <p>Attitude law is set to an unspecified default attitude.</p>
  103.      * @param orbit the orbit
  104.      * @param mass the mass (kg)
  105.      * @exception OrekitException if default attitude cannot be computed
  106.      */
  107.     public SpacecraftState(final Orbit orbit, final double mass)
  108.         throws OrekitException {
  109.         this(orbit,
  110.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  111.              mass, null);
  112.     }

  113.     /** Build a spacecraft state from orbit, attitude provider and mass.
  114.      * @param orbit the orbit
  115.      * @param attitude attitude
  116.      * @param mass the mass (kg)
  117.      * @exception IllegalArgumentException if orbit and attitude dates
  118.      * or frames are not equal
  119.      */
  120.     public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
  121.         throws IllegalArgumentException {
  122.         this(orbit, attitude, mass, null);
  123.     }

  124.     /** Build a spacecraft state from orbit only.
  125.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  126.      * @param orbit the orbit
  127.      * @param additional additional states
  128.      * @exception OrekitException if default attitude cannot be computed
  129.      */
  130.     public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional)
  131.         throws OrekitException {
  132.         this(orbit,
  133.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  134.              DEFAULT_MASS, additional);
  135.     }

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

  148.     /** Create a new instance from orbit and mass.
  149.      * <p>Attitude law is set to an unspecified default attitude.</p>
  150.      * @param orbit the orbit
  151.      * @param mass the mass (kg)
  152.      * @param additional additional states
  153.      * @exception OrekitException if default attitude cannot be computed
  154.      */
  155.     public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional)
  156.         throws OrekitException {
  157.         this(orbit,
  158.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  159.              mass, additional);
  160.     }

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

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

  209.     /** Check orbit and attitude dates are equal.
  210.      * @param orbit the orbit
  211.      * @param attitude attitude
  212.      * @exception IllegalArgumentException if orbit and attitude dates
  213.      * are not equal
  214.      */
  215.     private static void checkConsistency(final Orbit orbit, final Attitude attitude)
  216.         throws IllegalArgumentException {
  217.         if (!orbit.getDate().equals(attitude.getDate())) {
  218.             throw OrekitException.createIllegalArgumentException(
  219.                   OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  220.                   orbit.getDate(), attitude.getDate());
  221.         }
  222.         if (orbit.getFrame() != attitude.getReferenceFrame()) {
  223.             throw OrekitException.createIllegalArgumentException(
  224.                   OrekitMessages.FRAMES_MISMATCH,
  225.                   orbit.getFrame().getName(), attitude.getReferenceFrame().getName());
  226.         }
  227.     }

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

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

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

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

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

  315.         // create the complete interpolated state
  316.         return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
  317.                                    interpolatedMass, interpolatedAdditional);

  318.     }

  319.     /** Gets the current orbit.
  320.      * @return the orbit
  321.      */
  322.     public Orbit getOrbit() {
  323.         return orbit;
  324.     }

  325.     /** Get the date.
  326.      * @return date
  327.      */
  328.     public AbsoluteDate getDate() {
  329.         return orbit.getDate();
  330.     }

  331.     /** Get the inertial frame.
  332.      * @return the frame
  333.      */
  334.     public Frame getFrame() {
  335.         return orbit.getFrame();
  336.     }

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

  347.     /** Check if two instances have the same set of additional states available.
  348.      * <p>
  349.      * Only the names and dimensions of the additional states are compared,
  350.      * not their values.
  351.      * </p>
  352.      * @param state state to compare to instance
  353.      * @exception OrekitException if either instance or state supports an additional
  354.      * state not supported by the other one
  355.      * @exception DimensionMismatchException if an additional state does not have
  356.      * the same dimension in both states
  357.      */
  358.     public void ensureCompatibleAdditionalStates(final SpacecraftState state)
  359.         throws OrekitException, DimensionMismatchException {

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

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

  380.     }

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

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

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

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

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

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

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

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

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

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

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

  471.     /** Get the true latitude argument (as per equinoctial parameters).
  472.      * @return v + &omega; + &Omega; true latitude argument (rad)
  473.      * @see #getLE()
  474.      * @see #getLM()
  475.      */
  476.     public double getLv() {
  477.         return orbit.getLv();
  478.     }

  479.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  480.      * @return E + &omega; + &Omega; eccentric latitude argument (rad)
  481.      * @see #getLv()
  482.      * @see #getLM()
  483.      */
  484.     public double getLE() {
  485.         return orbit.getLE();
  486.     }

  487.     /** Get the mean latitude argument (as per equinoctial parameters).
  488.      * @return M + &omega; + &Omega; mean latitude argument (rad)
  489.      * @see #getLv()
  490.      * @see #getLE()
  491.      */
  492.     public double getLM() {
  493.         return orbit.getLM();
  494.     }

  495.     // Additional orbital elements

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

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

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

  523.     /** Get the {@link PVCoordinates} in given output frame.
  524.      * Compute the position and velocity of the satellite. This method caches its
  525.      * results, and recompute them only when the method is called with a new value
  526.      * for mu. The result is provided as a reference to the internally cached
  527.      * {@link PVCoordinates}, so the caller is responsible to copy it in a separate
  528.      * {@link PVCoordinates} if it needs to keep the value for a while.
  529.      * @param outputFrame frame in which coordinates should be defined
  530.      * @return pvCoordinates in orbit definition frame
  531.      * @exception OrekitException if the transformation between frames cannot be computed
  532.      */
  533.     public PVCoordinates getPVCoordinates(final Frame outputFrame)
  534.         throws OrekitException {
  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. }