FieldSpacecraftState.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.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.stream.Stream;

  24. import org.hipparchus.Field;
  25. import org.hipparchus.RealFieldElement;
  26. import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
  27. import org.hipparchus.exception.LocalizedCoreFormats;
  28. import org.hipparchus.exception.MathIllegalArgumentException;
  29. import org.hipparchus.exception.MathIllegalStateException;
  30. import org.hipparchus.util.MathArrays;
  31. import org.orekit.attitudes.FieldAttitude;
  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.FieldTransform;
  38. import org.orekit.frames.Frame;
  39. import org.orekit.frames.LOFType;
  40. import org.orekit.orbits.FieldOrbit;
  41. import org.orekit.orbits.PositionAngle;
  42. import org.orekit.time.FieldAbsoluteDate;
  43. import org.orekit.time.FieldTimeInterpolable;
  44. import org.orekit.time.FieldTimeShiftable;
  45. import org.orekit.time.FieldTimeStamped;
  46. import org.orekit.utils.TimeStampedFieldPVCoordinates;


  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 FieldOrbit orbital state} at a current
  51.  * {@link FieldAbsoluteDate} both handled by an {@link FieldOrbit}, plus the current
  52.  * mass and attitude. FieldOrbitand 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 FieldSpacecraftState} 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 FieldSpacecraftState <T extends RealFieldElement<T>>
  73.     implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T>, FieldTimeInterpolable<FieldSpacecraftState<T>, T> {

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

  76.     /**
  77.      * tolerance on date comparison in {@link #checkConsistency(FieldOrbit<T>, FieldAttitude<T>)}. 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 FieldOrbit<T> orbit;

  83.     /** FieldAttitude<T>. */
  84.     private final FieldAttitude<T> attitude;

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

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

  89.     /** Build a spacecraft state from orbit only.
  90.      * <p>FieldAttitude<T> 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 FieldSpacecraftState(final FieldOrbit<T> orbit)
  95.         throws OrekitException {
  96.         this(orbit,
  97.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  98.              orbit.getA().getField().getZero().add(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 FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
  108.         throws IllegalArgumentException {
  109.         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), null);
  110.     }

  111.     /** Create a new instance from orbit and mass.
  112.      * <p>FieldAttitude<T> 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 FieldSpacecraftState(final FieldOrbit<T> orbit, final T 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 FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass)
  131.         throws IllegalArgumentException {
  132.         this(orbit, attitude, mass, null);
  133.     }

  134.     /** Build a spacecraft state from orbit only.
  135.      * <p>FieldAttitude<T> 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 FieldSpacecraftState(final FieldOrbit<T> orbit, final Map<String, T[]> additional)
  141.         throws OrekitException {
  142.         this(orbit,
  143.              new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  144.              orbit.getA().getField().getZero().add(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 FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
  155.         throws IllegalArgumentException {
  156.         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
  157.     }

  158.     /** Create a new instance from orbit and mass.
  159.      * <p>FieldAttitude<T> 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 FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass, final Map<String, T[]> 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 FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude,
  180.                                 final T mass, final Map<String, T[]> 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, T[]>(additional.size());
  190.             for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  191.                 this.additional.put(entry.getKey(), entry.getValue().clone());
  192.             }
  193.         }
  194.     }

  195.     /** Convert a {@link SpacecraftState}.
  196.      * @param field field to which the elements belong
  197.      * @param state state to convert
  198.      */
  199.     public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {

  200.         final double[] stateD    = new double[6];
  201.         final double[] stateDotD = state.getOrbit().hasDerivatives() ? new double[6] : null;
  202.         state.getOrbit().getType().mapOrbitToArray(state.getOrbit(), PositionAngle.TRUE, stateD, stateDotD);
  203.         final T[] stateF    = MathArrays.buildArray(field, 6);
  204.         for (int i = 0; i < stateD.length; ++i) {
  205.             stateF[i]    = field.getZero().add(stateD[i]);
  206.         }
  207.         final T[] stateDotF;
  208.         if (stateDotD == null) {
  209.             stateDotF = null;
  210.         } else {
  211.             stateDotF = MathArrays.buildArray(field, 6);
  212.             for (int i = 0; i < stateDotD.length; ++i) {
  213.                 stateDotF[i] = field.getZero().add(stateDotD[i]);
  214.             }
  215.         }

  216.         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());

  217.         this.orbit    = state.getOrbit().getType().mapArrayToOrbit(stateF, stateDotF,
  218.                                                                    PositionAngle.TRUE,
  219.                                                                    dateF, state.getMu(), state.getFrame());
  220.         this.attitude = new FieldAttitude<>(field, state.getAttitude());
  221.         this.mass     = field.getZero().add(state.getMass());

  222.         final Map<String, double[]> additionalD = state.getAdditionalStates();
  223.         if (additionalD.isEmpty()) {
  224.             this.additional = Collections.emptyMap();
  225.         } else {
  226.             this.additional = new HashMap<String, T[]>(additionalD.size());
  227.             for (final Map.Entry<String, double[]> entry : additionalD.entrySet()) {
  228.                 final T[] array = MathArrays.buildArray(field, entry.getValue().length);
  229.                 for (int k = 0; k < array.length; ++k) {
  230.                     array[k] = field.getZero().add(entry.getValue()[k]);
  231.                 }
  232.                 this.additional.put(entry.getKey(), array);
  233.             }
  234.         }

  235.     }

  236.     /** Add an additional state.
  237.      * <p>
  238.      * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
  239.      * so this method does <em>not</em> change the instance, but rather
  240.      * creates a new instance, which has the same orbit, attitude, mass
  241.      * and additional states as the original instance, except it also
  242.      * has the specified state. If the original instance already had an
  243.      * additional state with the same name, it will be overridden. If it
  244.      * did not have any additional state with that name, the new instance
  245.      * will have one more additional state than the original instance.
  246.      * </p>
  247.      * @param name name of the additional state
  248.      * @param value value of the additional state
  249.      * @return a new instance, with the additional state added
  250.      * @see #hasAdditionalState(String)
  251.      * @see #getAdditionalState(String)
  252.      * @see #getAdditionalStates()
  253.      */
  254.     @SafeVarargs
  255.     public final FieldSpacecraftState<T> addAdditionalState(final String name, final T... value) {
  256.         final Map<String, T[]> newMap = new HashMap<String, T[]>(additional.size() + 1);
  257.         newMap.putAll(additional);
  258.         newMap.put(name, value.clone());
  259.         return new FieldSpacecraftState<>(orbit, attitude, mass, newMap);
  260.     }

  261.     /** Check orbit and attitude dates are equal.
  262.      * @param orbitN the orbit
  263.      * @param attitudeN attitude
  264.      * @exception IllegalArgumentException if orbit and attitude dates
  265.      * are not equal
  266.      */
  267.     private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
  268.         throws IllegalArgumentException {
  269.         if (orbitN.getDate().durationFrom(attitudeN.getDate()).abs().getReal() >
  270.             DATE_INCONSISTENCY_THRESHOLD) {

  271.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  272.                                                      orbitN.getDate(), attitudeN.getDate());
  273.         }

  274.         if (orbitN.getFrame() != attitudeN.getReferenceFrame()) {
  275.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  276.                                                      orbitN.getFrame().getName(),
  277.                                                      attitudeN.getReferenceFrame().getName());
  278.         }
  279.     }

  280.     /** Get a time-shifted state.
  281.      * <p>
  282.      * The state can be slightly shifted to close dates. This shift is based on
  283.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  284.      * taking the spin rate into account and neither mass nor additional states
  285.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  286.      * and attitude propagation but should be sufficient for small time shifts
  287.      * or coarse accuracy.
  288.      * </p>
  289.      * <p>
  290.      * As a rough order of magnitude, the following table shows the extrapolation
  291.      * errors obtained between this simple shift method and an {@link
  292.      * org.orekit.propagation.numerical.NumericalPropagator numerical
  293.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  294.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  295.      * Beware that these results will be different for other orbits.
  296.      * </p>
  297.      * <table border="1" cellpadding="5">
  298.      * <caption>Extrapolation Error</caption>
  299.      * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
  300.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  301.      * <tr><td bgcolor="#eeeeff"> 60</td><td>  18</td><td> 1.1</td></tr>
  302.      * <tr><td bgcolor="#eeeeff">120</td><td>  72</td><td> 9.1</td></tr>
  303.      * <tr><td bgcolor="#eeeeff">300</td><td> 447</td><td> 140</td></tr>
  304.      * <tr><td bgcolor="#eeeeff">600</td><td>1601</td><td>1067</td></tr>
  305.      * <tr><td bgcolor="#eeeeff">900</td><td>3141</td><td>3307</td></tr>
  306.      * </table>
  307.      * @param dt time shift in seconds
  308.      * @return a new state, shifted with respect to the instance (which is immutable)
  309.      * except for the mass which stay unchanged
  310.      */
  311.     public FieldSpacecraftState<T> shiftedBy(final double dt) {
  312.         return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  313.                                           mass, additional);
  314.     }

  315.     /** Get a time-shifted state.
  316.      * <p>
  317.      * The state can be slightly shifted to close dates. This shift is based on
  318.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  319.      * taking the spin rate into account and neither mass nor additional states
  320.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  321.      * and attitude propagation but should be sufficient for small time shifts
  322.      * or coarse accuracy.
  323.      * </p>
  324.      * <p>
  325.      * As a rough order of magnitude, the following table shows the extrapolation
  326.      * errors obtained between this simple shift method and an {@link
  327.      * org.orekit.propagation.numerical.NumericalPropagator numerical
  328.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  329.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  330.      * Beware that these results will be different for other orbits.
  331.      * </p>
  332.      * <table border="1" cellpadding="5">
  333.      * <caption>Extrapolation Error</caption>
  334.      * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
  335.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  336.      * <tr><td bgcolor="#eeeeff"> 60</td><td>  18</td><td> 1.1</td></tr>
  337.      * <tr><td bgcolor="#eeeeff">120</td><td>  72</td><td> 9.1</td></tr>
  338.      * <tr><td bgcolor="#eeeeff">300</td><td> 447</td><td> 140</td></tr>
  339.      * <tr><td bgcolor="#eeeeff">600</td><td>1601</td><td>1067</td></tr>
  340.      * <tr><td bgcolor="#eeeeff">900</td><td>3141</td><td>3307</td></tr>
  341.      * </table>
  342.      * @param dt time shift in seconds
  343.      * @return a new state, shifted with respect to the instance (which is immutable)
  344.      * except for the mass which stay unchanged
  345.      */
  346.     public FieldSpacecraftState<T> shiftedBy(final T dt) {
  347.         return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  348.                                           mass, additional);
  349.     }

  350.     /** Get an interpolated instance.
  351.      * <p>
  352.      * The additional states that are interpolated are the ones already present
  353.      * in the instance. The sample instances must therefore have at least the same
  354.      * additional states has the instance. They may have more additional states,
  355.      * but the extra ones will be ignored.
  356.      * </p>
  357.      * <p>
  358.      * As this implementation of interpolation is polynomial, it should be used only
  359.      * with small samples (about 10-20 points) in order to avoid <a
  360.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  361.      * and numerical problems (including NaN appearing).
  362.      * </p>
  363.      * @param date interpolation date
  364.      * @param sample sample points on which interpolation should be done
  365.      * @return a new instance, interpolated at specified date
  366.      * @exception OrekitException if the number of point is too small for interpolating
  367.      */
  368.     public FieldSpacecraftState<T> interpolate(final FieldAbsoluteDate<T> date,
  369.                                                final Stream<FieldSpacecraftState<T>> sample)
  370.         throws OrekitException {

  371.         // prepare interpolators
  372.         final List<FieldOrbit<T>> orbits = new ArrayList<>();
  373.         final List<FieldAttitude<T>> attitudes = new ArrayList<>();
  374.         final FieldHermiteInterpolator<T> massInterpolator = new FieldHermiteInterpolator<>();
  375.         final Map<String, FieldHermiteInterpolator<T>> additionalInterpolators =
  376.                 new HashMap<String, FieldHermiteInterpolator<T>>(additional.size());
  377.         for (final String name : additional.keySet()) {
  378.             additionalInterpolators.put(name, new FieldHermiteInterpolator<>());
  379.         }

  380.         // extract sample data
  381.         try {
  382.             sample.forEach(state -> {
  383.                 try {
  384.                     final T deltaT = state.getDate().durationFrom(date);
  385.                     orbits.add(state.getOrbit());
  386.                     attitudes.add(state.getAttitude());
  387.                     final T[] mm = MathArrays.buildArray(orbit.getA().getField(), 1);
  388.                     mm[0] = state.getMass();
  389.                     massInterpolator.addSamplePoint(deltaT,
  390.                                                     mm);
  391.                     for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
  392.                         entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
  393.                     }
  394.                 } catch (OrekitException oe) {
  395.                     throw new OrekitExceptionWrapper(oe);
  396.                 }
  397.             });
  398.         } catch (OrekitExceptionWrapper oew) {
  399.             throw oew.getException();
  400.         }

  401.         // perform interpolations
  402.         final FieldOrbit<T> interpolatedOrbit       = orbit.interpolate(date, orbits);
  403.         final FieldAttitude<T> interpolatedAttitude = attitude.interpolate(date, attitudes);
  404.         final T interpolatedMass       = massInterpolator.value(orbit.getA().getField().getZero())[0];
  405.         final Map<String, T[]> interpolatedAdditional;
  406.         if (additional.isEmpty()) {
  407.             interpolatedAdditional = null;
  408.         } else {
  409.             interpolatedAdditional = new HashMap<String, T[]>(additional.size());
  410.             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
  411.                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(orbit.getA().getField().getZero()));
  412.             }
  413.         }

  414.         // create the complete interpolated state
  415.         return new FieldSpacecraftState<>(interpolatedOrbit, interpolatedAttitude,
  416.                                           interpolatedMass, interpolatedAdditional);

  417.     }

  418.     /** Gets the current orbit.
  419.      * @return the orbit
  420.      */
  421.     public FieldOrbit<T> getOrbit() {
  422.         return orbit;
  423.     }

  424.     /** Get the date.
  425.      * @return date
  426.      */
  427.     public FieldAbsoluteDate<T> getDate() {
  428.         return orbit.getDate();
  429.     }

  430.     /** Get the inertial frame.
  431.      * @return the frame
  432.      */
  433.     public Frame getFrame() {
  434.         return orbit.getFrame();
  435.     }

  436.     /** Check if an additional state is available.
  437.      * @param name name of the additional state
  438.      * @return true if the additional state is available
  439.      * @see #addAdditionalState(String, RealFieldElement...)
  440.      * @see #getAdditionalState(String)
  441.      * @see #getAdditionalStates()
  442.      */
  443.     public boolean hasAdditionalState(final String name) {
  444.         return additional.containsKey(name);
  445.     }

  446.     /** Check if two instances have the same set of additional states available.
  447.      * <p>
  448.      * Only the names and dimensions of the additional states are compared,
  449.      * not their values.
  450.      * </p>
  451.      * @param state state to compare to instance
  452.      * @exception OrekitException if either instance or state supports an additional
  453.      * state not supported by the other one
  454.      * @exception MathIllegalArgumentException if an additional state does not have
  455.      * the same dimension in both states
  456.      */
  457.     public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
  458.         throws OrekitException, MathIllegalArgumentException {

  459.         // check instance additional states is a subset of the other one
  460.         for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  461.             final T[] other = state.additional.get(entry.getKey());
  462.             if (other == null) {
  463.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  464.                                           entry.getKey());
  465.             }
  466.             if (other.length != entry.getValue().length) {
  467.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  468.                                                     other.length, entry.getValue().length);
  469.             }
  470.         }

  471.         if (state.additional.size() > additional.size()) {
  472.             // the other state has more additional states
  473.             for (final String name : state.additional.keySet()) {
  474.                 if (!additional.containsKey(name)) {
  475.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
  476.                                               name);
  477.                 }
  478.             }
  479.         }

  480.     }

  481.     /** Get an additional state.
  482.      * @param name name of the additional state
  483.      * @return value of the additional state
  484.      * @exception OrekitException if no additional state with that name exists
  485.      * @see #addAdditionalState(String, RealFieldElement...)
  486.      * @see #hasAdditionalState(String)
  487.      * @see #getAdditionalStates()
  488.      */
  489.     public T[] getAdditionalState(final String name) throws OrekitException {
  490.         if (!additional.containsKey(name)) {
  491.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
  492.         }
  493.         return additional.get(name).clone();
  494.     }

  495.     /** Get an unmodifiable map of additional states.
  496.      * @return unmodifiable map of additional states
  497.      * @see #addAdditionalState(String, RealFieldElement...)
  498.      * @see #hasAdditionalState(String)
  499.      * @see #getAdditionalState(String)
  500.      */
  501.     public Map<String, T[]> getAdditionalStates() {
  502.         return Collections.unmodifiableMap(additional);
  503.     }

  504.     /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
  505.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  506.      * and its orientation is defined by the attitude.</p>
  507.      * @return transform from specified frame to current spacecraft frame
  508.      */
  509.     public FieldTransform<T> toTransform() {
  510.         final FieldAbsoluteDate<T> date = orbit.getDate();
  511.         return new FieldTransform<>(date,
  512.                                     new FieldTransform<>(date, orbit.getPVCoordinates().negate()),
  513.                                     new FieldTransform<>(date, attitude.getOrientation()));
  514.     }

  515.     /** Get the central attraction coefficient.
  516.      * @return mu central attraction coefficient (m^3/s^2)
  517.      */
  518.     public double getMu() {
  519.         return orbit.getMu();
  520.     }

  521.     /** Get the Keplerian period.
  522.      * <p>The Keplerian period is computed directly from semi major axis
  523.      * and central acceleration constant.</p>
  524.      * @return Keplerian period in seconds
  525.      */
  526.     public T getKeplerianPeriod() {
  527.         return orbit.getKeplerianPeriod();
  528.     }

  529.     /** Get the Keplerian mean motion.
  530.      * <p>The Keplerian mean motion is computed directly from semi major axis
  531.      * and central acceleration constant.</p>
  532.      * @return Keplerian mean motion in radians per second
  533.      */
  534.     public T getKeplerianMeanMotion() {
  535.         return orbit.getKeplerianMeanMotion();
  536.     }

  537.     /** Get the semi-major axis.
  538.      * @return semi-major axis (m)
  539.      */
  540.     public T getA() {
  541.         return orbit.getA();
  542.     }

  543.     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
  544.      * @return e cos(ω + Ω), first component of eccentricity vector
  545.      * @see #getE()
  546.      */
  547.     public T getEquinoctialEx() {
  548.         return orbit.getEquinoctialEx();
  549.     }

  550.     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
  551.      * @return e sin(ω + Ω), second component of the eccentricity vector
  552.      * @see #getE()
  553.      */
  554.     public T getEquinoctialEy() {
  555.         return orbit.getEquinoctialEy();
  556.     }

  557.     /** Get the first component of the inclination vector (as per equinoctial parameters).
  558.      * @return tan(i/2) cos(Ω), first component of the inclination vector
  559.      * @see #getI()
  560.      */
  561.     public T getHx() {
  562.         return orbit.getHx();
  563.     }

  564.     /** Get the second component of the inclination vector (as per equinoctial parameters).
  565.      * @return tan(i/2) sin(Ω), second component of the inclination vector
  566.      * @see #getI()
  567.      */
  568.     public T getHy() {
  569.         return orbit.getHy();
  570.     }

  571.     /** Get the true latitude argument (as per equinoctial parameters).
  572.      * @return v + ω + Ω true latitude argument (rad)
  573.      * @see #getLE()
  574.      * @see #getLM()
  575.      */
  576.     public T getLv() {
  577.         return orbit.getLv();
  578.     }

  579.     /** Get the eccentric latitude argument (as per equinoctial parameters).
  580.      * @return E + ω + Ω eccentric latitude argument (rad)
  581.      * @see #getLv()
  582.      * @see #getLM()
  583.      */
  584.     public T getLE() {
  585.         return orbit.getLE();
  586.     }

  587.     /** Get the mean latitude argument (as per equinoctial parameters).
  588.      * @return M + ω + Ω mean latitude argument (rad)
  589.      * @see #getLv()
  590.      * @see #getLE()
  591.      */
  592.     public T getLM() {
  593.         return orbit.getLM();
  594.     }

  595.     // Additional orbital elements

  596.     /** Get the eccentricity.
  597.      * @return eccentricity
  598.      * @see #getEquinoctialEx()
  599.      * @see #getEquinoctialEy()
  600.      */
  601.     public T getE() {
  602.         return orbit.getE();
  603.     }

  604.     /** Get the inclination.
  605.      * @return inclination (rad)
  606.      * @see #getHx()
  607.      * @see #getHy()
  608.      */
  609.     public T getI() {
  610.         return orbit.getI();
  611.     }

  612.     /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
  613.      * Compute the position and velocity of the satellite. This method caches its
  614.      * results, and recompute them only when the method is called with a new value
  615.      * for mu. The result is provided as a reference to the internally cached
  616.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  617.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  618.      * @return pvCoordinates in orbit definition frame
  619.      */
  620.     public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
  621.         return orbit.getPVCoordinates();
  622.     }

  623.     /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
  624.      * Compute the position and velocity of the satellite. This method caches its
  625.      * results, and recompute them only when the method is called with a new value
  626.      * for mu. The result is provided as a reference to the internally cached
  627.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  628.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  629.      * @param outputFrame frame in which coordinates should be defined
  630.      * @return pvCoordinates in orbit definition frame
  631.      * @exception OrekitException if the transformation between frames cannot be computed
  632.      */
  633.     public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final Frame outputFrame)
  634.         throws OrekitException {
  635.         return orbit.getPVCoordinates(outputFrame);
  636.     }

  637.     /** Get the attitude.
  638.      * @return the attitude.
  639.      */
  640.     public FieldAttitude<T> getAttitude() {
  641.         return attitude;
  642.     }

  643.     /** Gets the current mass.
  644.      * @return the mass (kg)
  645.      */
  646.     public T getMass() {
  647.         return mass;
  648.     }

  649.     /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
  650.      *
  651.      * @return SpacecraftState instance with the same properties
  652.      */
  653.     public SpacecraftState toSpacecraftState() {
  654.         final Map<String, double[]> map;
  655.         if (additional.isEmpty()) {
  656.             map = Collections.emptyMap();
  657.         } else {
  658.             map = new HashMap<>(additional.size());
  659.             for (final Map.Entry<String, T[]> entry : additional.entrySet()) {
  660.                 final double[] array = new double[entry.getValue().length];
  661.                 for (int k = 0; k < array.length; ++k) {
  662.                     array[k] = entry.getValue()[k].getReal();
  663.                 }
  664.                 map.put(entry.getKey(), array);
  665.             }
  666.         }
  667.         return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(), mass.getReal(), map);
  668.     }

  669. }