SpacecraftStateInterpolator.java

  1. /* Copyright 2002-2024 CS GROUP
  2.  * Licensed to CS GROUP (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 org.hipparchus.util.Pair;
  19. import org.orekit.attitudes.Attitude;
  20. import org.orekit.attitudes.AttitudeInterpolator;
  21. import org.orekit.attitudes.AttitudeProvider;
  22. import org.orekit.attitudes.FrameAlignedProvider;
  23. import org.orekit.errors.OrekitIllegalArgumentException;
  24. import org.orekit.errors.OrekitInternalError;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.frames.Frame;
  27. import org.orekit.orbits.Orbit;
  28. import org.orekit.orbits.OrbitHermiteInterpolator;
  29. import org.orekit.time.AbsoluteDate;
  30. import org.orekit.time.AbstractTimeInterpolator;
  31. import org.orekit.time.TimeInterpolator;
  32. import org.orekit.time.TimeStamped;
  33. import org.orekit.time.TimeStampedDouble;
  34. import org.orekit.time.TimeStampedDoubleHermiteInterpolator;
  35. import org.orekit.utils.AbsolutePVCoordinates;
  36. import org.orekit.utils.AbsolutePVCoordinatesHermiteInterpolator;
  37. import org.orekit.utils.AngularDerivativesFilter;
  38. import org.orekit.utils.CartesianDerivativesFilter;
  39. import org.orekit.utils.DoubleArrayDictionary;
  40. import org.orekit.utils.PVCoordinatesProvider;
  41. import org.orekit.utils.TimeStampedAngularCoordinatesHermiteInterpolator;

  42. import java.util.ArrayList;
  43. import java.util.Collection;
  44. import java.util.HashMap;
  45. import java.util.List;
  46. import java.util.Map;
  47. import java.util.Optional;

  48. /**
  49.  * Generic class for spacecraft state interpolator.
  50.  * <p>
  51.  * The user can specify what interpolator to use for each attribute of the spacecraft state. However, at least one
  52.  * interpolator for either orbit or absolute position-velocity-acceleration is needed. All the other interpolators can be
  53.  * left to null if the user do not want to interpolate these values.
  54.  *
  55.  * @author Luc Maisonobe
  56.  * @author Vincent Cucchietti
  57.  * @see SpacecraftState
  58.  */
  59. public class SpacecraftStateInterpolator extends AbstractTimeInterpolator<SpacecraftState> {

  60.     /**
  61.      * Output frame.
  62.      * <p><b>Must be inertial</b> if interpolating spacecraft states defined by orbit</p>
  63.      */
  64.     private final Frame outputFrame;

  65.     /** Orbit interpolator. */
  66.     private final TimeInterpolator<Orbit> orbitInterpolator;

  67.     /** Absolute position-velocity-acceleration interpolator. */
  68.     private final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator;

  69.     /** Mass interpolator. */
  70.     private final TimeInterpolator<TimeStampedDouble> massInterpolator;

  71.     /** Attitude interpolator. */
  72.     private final TimeInterpolator<Attitude> attitudeInterpolator;

  73.     /** Additional state interpolator. */
  74.     private final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator;

  75.     /**
  76.      * Simplest constructor to create a default Hermite interpolator for every spacecraft state field.
  77.      * <p>
  78.      * The interpolators will have the following configuration :
  79.      * <ul>
  80.      *     <li>Same frame for coordinates and attitude </li>
  81.      *     <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li>
  82.      *     <li>Default extrapolation threshold of {@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s</li>
  83.      *     <li>Use of position and two time derivatives for absolute position-velocity-acceleration coordinates interpolation</li>
  84.      *     <li>Use of angular and first time derivative for attitude interpolation</li>
  85.      * </ul>
  86.      * <p>
  87.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  88.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  89.      * phenomenon</a> and numerical problems (including NaN appearing).
  90.      * <p>
  91.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  92.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  93.      *
  94.      * @param outputFrame output frame
  95.      *
  96.      * @see AbstractTimeInterpolator
  97.      */
  98.     public SpacecraftStateInterpolator(final Frame outputFrame) {
  99.         this(DEFAULT_INTERPOLATION_POINTS, outputFrame);
  100.     }

  101.     /**
  102.      * Constructor to create a customizable Hermite interpolator for every spacecraft state field.
  103.      * <p>
  104.      * The interpolators will have the following configuration :
  105.      * <ul>
  106.      *     <li>Same frame for coordinates and attitude </li>
  107.      *     <li>Default extrapolation threshold of {@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s</li>
  108.      *     <li>Use of position and two time derivatives for absolute position-velocity-acceleration coordinates interpolation</li>
  109.      *     <li>Use of angular and first time derivative for attitude interpolation</li>
  110.      * </ul>
  111.      * <p>
  112.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  113.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  114.      * phenomenon</a> and numerical problems (including NaN appearing).
  115.      * <p>
  116.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  117.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  118.      *
  119.      * @param interpolationPoints number of interpolation points
  120.      * @param outputFrame output frame
  121.      *
  122.      * @see AbstractTimeInterpolator
  123.      */
  124.     public SpacecraftStateInterpolator(final int interpolationPoints, final Frame outputFrame) {
  125.         this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputFrame, outputFrame);
  126.     }

  127.     /**
  128.      * Constructor to create a customizable Hermite interpolator for every spacecraft state field.
  129.      * <p>
  130.      * The interpolators will have the following configuration :
  131.      * <ul>
  132.      *     <li>Default extrapolation threshold of {@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s</li>
  133.      *     <li>Use of position and two time derivatives for absolute position-velocity-acceleration coordinates interpolation</li>
  134.      *     <li>Use of angular and first time derivative for attitude interpolation</li>
  135.      * </ul>
  136.      * <p>
  137.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  138.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  139.      * phenomenon</a> and numerical problems (including NaN appearing).
  140.      * <p>
  141.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  142.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  143.      *
  144.      * @param interpolationPoints number of interpolation points
  145.      * @param outputFrame output frame
  146.      * @param attitudeReferenceFrame reference frame from which attitude is defined
  147.      *
  148.      * @see AbstractTimeInterpolator
  149.      */
  150.     public SpacecraftStateInterpolator(final int interpolationPoints, final Frame outputFrame,
  151.                                        final Frame attitudeReferenceFrame) {
  152.         this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputFrame, attitudeReferenceFrame,
  153.              CartesianDerivativesFilter.USE_PVA, AngularDerivativesFilter.USE_RR);
  154.     }

  155.     /**
  156.      * Constructor to create a customizable Hermite interpolator for every spacecraft state field.
  157.      * <p>
  158.      * The interpolators will have the following configuration :
  159.      * <ul>
  160.      *     <li>Use of position and two time derivatives for absolute position-velocity-acceleration coordinates interpolation</li>
  161.      *     <li>Use of angular and first time derivative for attitude interpolation</li>
  162.      * </ul>
  163.      * <p>
  164.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  165.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  166.      * phenomenon</a> and numerical problems (including NaN appearing).
  167.      * <p>
  168.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  169.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  170.      *
  171.      * @param interpolationPoints number of interpolation points
  172.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  173.      * @param outputFrame output frame
  174.      * @param attitudeReferenceFrame reference frame from which attitude is defined
  175.      */
  176.     public SpacecraftStateInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  177.                                        final Frame outputFrame, final Frame attitudeReferenceFrame) {
  178.         this(interpolationPoints, extrapolationThreshold, outputFrame, attitudeReferenceFrame,
  179.              CartesianDerivativesFilter.USE_PVA, AngularDerivativesFilter.USE_RR);
  180.     }

  181.     /**
  182.      * Constructor to create a customizable Hermite interpolator for every spacecraft state field.
  183.      * <p>
  184.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  185.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  186.      * phenomenon</a> and numerical problems (including NaN appearing).
  187.      * <p>
  188.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  189.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  190.      *
  191.      * @param interpolationPoints number of interpolation points
  192.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  193.      * @param outputFrame output frame
  194.      * @param attitudeReferenceFrame reference frame from which attitude is defined
  195.      * @param pvaFilter filter for derivatives from the sample to use in position-velocity-acceleration interpolation
  196.      * @param angularFilter filter for derivatives from the sample to use in attitude interpolation
  197.      */
  198.     public SpacecraftStateInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  199.                                        final Frame outputFrame, final Frame attitudeReferenceFrame,
  200.                                        final CartesianDerivativesFilter pvaFilter,
  201.                                        final AngularDerivativesFilter angularFilter) {
  202.         this(interpolationPoints, extrapolationThreshold, outputFrame,
  203.              new OrbitHermiteInterpolator(interpolationPoints, extrapolationThreshold, outputFrame, pvaFilter),
  204.              new AbsolutePVCoordinatesHermiteInterpolator(interpolationPoints, extrapolationThreshold, outputFrame,
  205.                                                           pvaFilter),
  206.              new TimeStampedDoubleHermiteInterpolator(interpolationPoints, extrapolationThreshold),
  207.              new AttitudeInterpolator(attitudeReferenceFrame,
  208.                                       new TimeStampedAngularCoordinatesHermiteInterpolator(interpolationPoints,
  209.                                                                                            extrapolationThreshold,
  210.                                                                                            angularFilter)),
  211.              new TimeStampedDoubleHermiteInterpolator(interpolationPoints, extrapolationThreshold));
  212.     }

  213.     /**
  214.      * Constructor with:
  215.      * <ul>
  216.      *     <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li>
  217.      *     <li>Default extrapolation threshold of {@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s</li>
  218.      * </ul>
  219.      * <p>
  220.      * At least one interpolator for either orbit or absolute position-velocity-acceleration is needed. All the other
  221.      * interpolators can be left to null if the user do not want to interpolate these values.
  222.      * <p>
  223.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if interpolated spacecraft states are defined by orbit. Throws an
  224.      * error otherwise.
  225.      * <p>
  226.      * <b>BEWARE:</b> it is up to the user to check the consistency of input interpolators.
  227.      *
  228.      * @param outputFrame output frame (inertial if the user is planning to use the orbit interpolator)
  229.      * @param orbitInterpolator orbit interpolator (can be null if absPVAInterpolator is defined)
  230.      * @param absPVAInterpolator absolute position-velocity-acceleration (can be null if orbitInterpolator is defined)
  231.      * @param massInterpolator mass interpolator (can be null)
  232.      * @param attitudeInterpolator attitude interpolator (can be null)
  233.      * @param additionalStateInterpolator additional state interpolator (can be null)
  234.      *
  235.      * @see AbstractTimeInterpolator
  236.      *
  237.      * @deprecated using this constructor may throw an exception if any given interpolator
  238.      * does not use {@link #DEFAULT_INTERPOLATION_POINTS} and {@link
  239.      * #DEFAULT_EXTRAPOLATION_THRESHOLD_SEC}. Use {@link #SpacecraftStateInterpolator(int,
  240.      * double, Frame, TimeInterpolator, TimeInterpolator, TimeInterpolator,
  241.      * TimeInterpolator, TimeInterpolator)} instead.
  242.      */
  243.     @Deprecated
  244.     public SpacecraftStateInterpolator(final Frame outputFrame, final TimeInterpolator<Orbit> orbitInterpolator,
  245.                                        final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator,
  246.                                        final TimeInterpolator<TimeStampedDouble> massInterpolator,
  247.                                        final TimeInterpolator<Attitude> attitudeInterpolator,
  248.                                        final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator) {
  249.         super(DEFAULT_INTERPOLATION_POINTS, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
  250.         checkAtLeastOneInterpolator(orbitInterpolator, absPVAInterpolator);
  251.         this.outputFrame                 = outputFrame;
  252.         this.orbitInterpolator           = orbitInterpolator;
  253.         this.absPVAInterpolator          = absPVAInterpolator;
  254.         this.massInterpolator            = massInterpolator;
  255.         this.attitudeInterpolator        = attitudeInterpolator;
  256.         this.additionalStateInterpolator = additionalStateInterpolator;
  257.     }

  258.     /**
  259.      * Constructor.
  260.      * <p>
  261.      * At least one interpolator for either orbit or absolute position-velocity-acceleration is needed. All the other
  262.      * interpolators can be left to null if the user do not want to interpolate these values.
  263.      * <p>
  264.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if interpolated spacecraft states are defined by orbit. Throws an
  265.      * error otherwise.
  266.      * <p>
  267.      * <b>BEWARE:</b> it is up to the user to check the consistency of input interpolators.
  268.      *
  269.      * @param interpolationPoints number of interpolation points
  270.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  271.      * @param outputFrame output frame (inertial if the user is planning to use the orbit interpolator)
  272.      * @param orbitInterpolator orbit interpolator (can be null if absPVAInterpolator is defined)
  273.      * @param absPVAInterpolator absolute position-velocity-acceleration (can be null if orbitInterpolator is defined)
  274.      * @param massInterpolator mass interpolator (can be null)
  275.      * @param attitudeInterpolator attitude interpolator (can be null)
  276.      * @param additionalStateInterpolator additional state interpolator (can be null)
  277.      *
  278.      * @since 12.0.1
  279.      */
  280.     public SpacecraftStateInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  281.                                        final Frame outputFrame, final TimeInterpolator<Orbit> orbitInterpolator,
  282.                                        final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator,
  283.                                        final TimeInterpolator<TimeStampedDouble> massInterpolator,
  284.                                        final TimeInterpolator<Attitude> attitudeInterpolator,
  285.                                        final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator) {
  286.         super(interpolationPoints, extrapolationThreshold);
  287.         checkAtLeastOneInterpolator(orbitInterpolator, absPVAInterpolator);
  288.         this.outputFrame                 = outputFrame;
  289.         this.orbitInterpolator           = orbitInterpolator;
  290.         this.absPVAInterpolator          = absPVAInterpolator;
  291.         this.massInterpolator            = massInterpolator;
  292.         this.attitudeInterpolator        = attitudeInterpolator;
  293.         this.additionalStateInterpolator = additionalStateInterpolator;
  294.     }

  295.     /**
  296.      * Check that an interpolator exist for given sample state definition.
  297.      *
  298.      * @param sample sample (non empty)
  299.      * @param orbitInterpolatorIsPresent flag defining if an orbit interpolator has been defined for this instance
  300.      * @param absPVInterpolatorIsPresent flag defining if an absolute position-velocity-acceleration interpolator has been
  301.      * defined for this instance
  302.      *
  303.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  304.      * definition type
  305.      */
  306.     public static void checkSampleAndInterpolatorConsistency(final List<SpacecraftState> sample,
  307.                                                              final boolean orbitInterpolatorIsPresent,
  308.                                                              final boolean absPVInterpolatorIsPresent) {
  309.         // Get first state definition
  310.         final SpacecraftState earliestState = sample.get(0);

  311.         if (earliestState.isOrbitDefined() && !orbitInterpolatorIsPresent ||
  312.                 !earliestState.isOrbitDefined() && !absPVInterpolatorIsPresent) {
  313.             throw new OrekitIllegalArgumentException(OrekitMessages.WRONG_INTERPOLATOR_DEFINED_FOR_STATE_INTERPOLATION);
  314.         }
  315.     }

  316.     /**
  317.      * Check that all state are either orbit defined or based on absolute position-velocity-acceleration.
  318.      *
  319.      * @param states spacecraft state sample
  320.      */
  321.     public static void checkStatesDefinitionsConsistency(final List<SpacecraftState> states) {
  322.         // Check all states handle the same additional states and are defined the same way (orbit or absolute PVA)
  323.         final SpacecraftState s0               = states.get(0);
  324.         final boolean         s0IsOrbitDefined = s0.isOrbitDefined();
  325.         for (final SpacecraftState state : states) {
  326.             s0.ensureCompatibleAdditionalStates(state);
  327.             if (s0IsOrbitDefined != state.isOrbitDefined()) {
  328.                 throw new OrekitIllegalArgumentException(OrekitMessages.DIFFERENT_STATE_DEFINITION);
  329.             }
  330.         }
  331.     }

  332.     /**
  333.      * {@inheritDoc}
  334.      * <p>
  335.      * The additional states that are interpolated are the ones already present in the first neighbor instance. The sample
  336.      * instances must therefore have at least the same additional states as this neighbor instance. They may have more
  337.      * additional states, but the extra ones will be ignored.
  338.      * <p>
  339.      * All the sample instances <em>must</em> be based on similar trajectory data, i.e. they must either all be based on
  340.      * orbits or all be based on absolute position-velocity-acceleration. Any inconsistency will trigger an
  341.      * {@link OrekitIllegalArgumentException}.
  342.      *
  343.      * @throws OrekitIllegalArgumentException if there are states defined by orbits and absolute
  344.      * position-velocity-acceleration coordinates
  345.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  346.      * definition type
  347.      */
  348.     @Override
  349.     public SpacecraftState interpolate(final AbsoluteDate interpolationDate, final Collection<SpacecraftState> sample) {

  350.         final List<SpacecraftState> sampleList = new ArrayList<>(sample);

  351.         // If sample is empty, an error will be thrown in super method
  352.         if (!sample.isEmpty()) {

  353.             // Check given that given states definition are consistent
  354.             // (all defined by either orbits or absolute position-velocity-acceleration coordinates)
  355.             checkStatesDefinitionsConsistency(sampleList);

  356.             // Check interpolator and sample consistency
  357.             checkSampleAndInterpolatorConsistency(sampleList, orbitInterpolator != null, absPVAInterpolator != null);
  358.         }

  359.         return super.interpolate(interpolationDate, sample);
  360.     }

  361.     /** {@inheritDoc} */
  362.     @Override
  363.     public List<TimeInterpolator<? extends TimeStamped>> getSubInterpolators() {

  364.         // Add all sub interpolators that are defined
  365.         final List<TimeInterpolator<? extends TimeStamped>> subInterpolators = new ArrayList<>();

  366.         addOptionalSubInterpolatorIfDefined(orbitInterpolator, subInterpolators);
  367.         addOptionalSubInterpolatorIfDefined(absPVAInterpolator, subInterpolators);
  368.         addOptionalSubInterpolatorIfDefined(massInterpolator, subInterpolators);
  369.         addOptionalSubInterpolatorIfDefined(attitudeInterpolator, subInterpolators);
  370.         addOptionalSubInterpolatorIfDefined(additionalStateInterpolator, subInterpolators);

  371.         return subInterpolators;

  372.     }

  373.     /**
  374.      * {@inheritDoc}
  375.      */
  376.     @Override
  377.     protected SpacecraftState interpolate(final InterpolationData interpolationData) {

  378.         // Get first state definition
  379.         final List<SpacecraftState> samples   = interpolationData.getNeighborList();
  380.         final SpacecraftState earliestState   = samples.get(0);
  381.         final boolean         areOrbitDefined = earliestState.isOrbitDefined();

  382.         // Prepare samples
  383.         final List<Attitude> attitudes = new ArrayList<>();

  384.         final List<TimeStampedDouble> masses = new ArrayList<>();

  385.         final List<DoubleArrayDictionary.Entry> additionalEntries = earliestState.getAdditionalStatesValues().getData();
  386.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSample =
  387.                 createAdditionalStateSample(additionalEntries);

  388.         final List<DoubleArrayDictionary.Entry> additionalDotEntries =
  389.                 earliestState.getAdditionalStatesDerivatives().getData();
  390.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalDotSample =
  391.                 createAdditionalStateSample(additionalDotEntries);

  392.         // Fill interpolators with samples
  393.         final List<Orbit>                 orbitSample  = new ArrayList<>();
  394.         final List<AbsolutePVCoordinates> absPVASample = new ArrayList<>();
  395.         for (SpacecraftState state : samples) {
  396.             final AbsoluteDate currentDate = state.getDate();

  397.             // Add orbit sample if state is defined with an orbit
  398.             if (state.isOrbitDefined()) {
  399.                 orbitSample.add(state.getOrbit());
  400.             }
  401.             // Add absolute position-velocity-acceleration sample if state is defined with an absolute position-velocity-acceleration
  402.             else {
  403.                 absPVASample.add(state.getAbsPVA());
  404.             }

  405.             // Add mass sample
  406.             if (massInterpolator != null) {
  407.                 masses.add(new TimeStampedDouble(state.getMass(), state.getDate()));
  408.             }

  409.             // Add attitude sample if it is interpolated
  410.             if (attitudeInterpolator != null) {
  411.                 attitudes.add(state.getAttitude());
  412.             }

  413.             if (additionalStateInterpolator != null) {

  414.                 // Add all additional state values if they are interpolated
  415.                 for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalSample.entrySet()) {
  416.                     entry.getValue().add(new Pair<>(currentDate, state.getAdditionalState(entry.getKey())));
  417.                 }

  418.                 // Add all additional state derivative values if they are interpolated
  419.                 for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalDotSample.entrySet()) {
  420.                     entry.getValue().add(new Pair<>(currentDate, state.getAdditionalStateDerivative(entry.getKey())));
  421.                 }
  422.             }
  423.         }

  424.         // Interpolate mass
  425.         final AbsoluteDate interpolationDate = interpolationData.getInterpolationDate();
  426.         final double       interpolatedMass;
  427.         if (massInterpolator != null) {
  428.             interpolatedMass = massInterpolator.interpolate(interpolationDate, masses).getValue();
  429.         } else {
  430.             interpolatedMass = SpacecraftState.DEFAULT_MASS;
  431.         }

  432.         // Interpolate additional states and derivatives
  433.         final DoubleArrayDictionary interpolatedAdditional;
  434.         final DoubleArrayDictionary interpolatedAdditionalDot;
  435.         if (additionalStateInterpolator != null) {
  436.             interpolatedAdditional    = interpolateAdditionalState(interpolationDate, additionalSample);
  437.             interpolatedAdditionalDot = interpolateAdditionalState(interpolationDate, additionalDotSample);
  438.         } else {
  439.             interpolatedAdditional    = null;
  440.             interpolatedAdditionalDot = null;
  441.         }

  442.         // Interpolate orbit
  443.         if (areOrbitDefined && orbitInterpolator != null) {
  444.             final Orbit interpolatedOrbit = orbitInterpolator.interpolate(interpolationDate, orbitSample);

  445.             final Attitude interpolatedAttitude = interpolateAttitude(interpolationDate, attitudes, interpolatedOrbit);

  446.             return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  447.                                        interpolatedAdditionalDot);
  448.         }
  449.         // Interpolate absolute position-velocity-acceleration
  450.         else if (!areOrbitDefined && absPVAInterpolator != null) {

  451.             final AbsolutePVCoordinates interpolatedAbsPva = absPVAInterpolator.interpolate(interpolationDate, absPVASample);

  452.             final Attitude interpolatedAttitude = interpolateAttitude(interpolationDate, attitudes, interpolatedAbsPva);

  453.             return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  454.                                        interpolatedAdditionalDot);
  455.         }
  456.         // Should never happen
  457.         else {
  458.             throw new OrekitInternalError(null);
  459.         }

  460.     }

  461.     /**
  462.      * Get output frame.
  463.      *
  464.      * @return output frame
  465.      */
  466.     public Frame getOutputFrame() {
  467.         return outputFrame;
  468.     }

  469.     /**
  470.      * Get orbit interpolator.
  471.      *
  472.      * @return optional orbit interpolator
  473.      *
  474.      * @see Optional
  475.      */
  476.     public Optional<TimeInterpolator<Orbit>> getOrbitInterpolator() {
  477.         return Optional.ofNullable(orbitInterpolator);
  478.     }

  479.     /**
  480.      * Get absolute position-velocity-acceleration interpolator.
  481.      *
  482.      * @return optional absolute position-velocity-acceleration interpolator
  483.      *
  484.      * @see Optional
  485.      */
  486.     public Optional<TimeInterpolator<AbsolutePVCoordinates>> getAbsPVAInterpolator() {
  487.         return Optional.ofNullable(absPVAInterpolator);
  488.     }

  489.     /**
  490.      * Get mass interpolator.
  491.      *
  492.      * @return optional mass interpolator
  493.      *
  494.      * @see Optional
  495.      */
  496.     public Optional<TimeInterpolator<TimeStampedDouble>> getMassInterpolator() {
  497.         return Optional.ofNullable(massInterpolator);
  498.     }

  499.     /**
  500.      * Get attitude interpolator.
  501.      *
  502.      * @return optional attitude interpolator
  503.      *
  504.      * @see Optional
  505.      */
  506.     public Optional<TimeInterpolator<Attitude>> getAttitudeInterpolator() {
  507.         return Optional.ofNullable(attitudeInterpolator);
  508.     }

  509.     /**
  510.      * Get additional state interpolator.
  511.      *
  512.      * @return optional additional state interpolator
  513.      *
  514.      * @see Optional
  515.      */
  516.     public Optional<TimeInterpolator<TimeStampedDouble>> getAdditionalStateInterpolator() {
  517.         return Optional.ofNullable(additionalStateInterpolator);
  518.     }

  519.     /**
  520.      * Check that at least one interpolator is defined.
  521.      *
  522.      * @param orbitInterpolatorToCheck orbit interpolator
  523.      * @param absPVAInterpolatorToCheck absolute position-velocity-acceleration interpolator
  524.      */
  525.     private void checkAtLeastOneInterpolator(final TimeInterpolator<Orbit> orbitInterpolatorToCheck,
  526.                                              final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolatorToCheck) {
  527.         if (orbitInterpolatorToCheck == null && absPVAInterpolatorToCheck == null) {
  528.             throw new OrekitIllegalArgumentException(OrekitMessages.NO_INTERPOLATOR_FOR_STATE_DEFINITION);
  529.         }
  530.     }

  531.     /**
  532.      * Create empty samples for given additional entries.
  533.      *
  534.      * @param additionalEntries tabulated additional entries
  535.      *
  536.      * @return empty samples for given additional entries
  537.      */
  538.     private Map<String, List<Pair<AbsoluteDate, double[]>>> createAdditionalStateSample(
  539.             final List<DoubleArrayDictionary.Entry> additionalEntries) {
  540.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSamples = new HashMap<>(additionalEntries.size());

  541.         for (final DoubleArrayDictionary.Entry entry : additionalEntries) {
  542.             additionalSamples.put(entry.getKey(), new ArrayList<>());
  543.         }

  544.         return additionalSamples;
  545.     }

  546.     /**
  547.      * Interpolate additional state values.
  548.      *
  549.      * @param interpolationDate interpolation date
  550.      * @param additionalSamples additional state samples
  551.      *
  552.      * @return interpolated additional state values
  553.      */
  554.     private DoubleArrayDictionary interpolateAdditionalState(final AbsoluteDate interpolationDate,
  555.                                                              final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSamples) {
  556.         final DoubleArrayDictionary interpolatedAdditional;

  557.         if (additionalSamples.isEmpty()) {
  558.             interpolatedAdditional = null;
  559.         } else {
  560.             interpolatedAdditional = new DoubleArrayDictionary(additionalSamples.size());
  561.             for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalSamples.entrySet()) {

  562.                 // Get current entry
  563.                 final List<Pair<AbsoluteDate, double[]>> currentAdditionalSamples = entry.getValue();

  564.                 // Extract number of values for this specific entry
  565.                 final int nbOfValues = currentAdditionalSamples.get(0).getValue().length;

  566.                 // For each value of current additional state entry
  567.                 final double[] currentInterpolatedAdditional = new double[nbOfValues];
  568.                 for (int i = 0; i < nbOfValues; i++) {

  569.                     // Create final index for lambda expression use
  570.                     final int currentIndex = i;

  571.                     // Create sample for specific value of current additional state values
  572.                     final List<TimeStampedDouble> currentValueSample = new ArrayList<>();

  573.                     currentAdditionalSamples.forEach(currentSamples -> currentValueSample.add(
  574.                             new TimeStampedDouble(currentSamples.getValue()[currentIndex], currentSamples.getFirst())));

  575.                     // Interpolate
  576.                     currentInterpolatedAdditional[i] =
  577.                             additionalStateInterpolator.interpolate(interpolationDate, currentValueSample).getValue();
  578.                 }

  579.                 interpolatedAdditional.put(entry.getKey(), currentInterpolatedAdditional);
  580.             }
  581.         }
  582.         return interpolatedAdditional;
  583.     }

  584.     /**
  585.      * Interpolate attitude.
  586.      * <p>
  587.      * If no attitude interpolator were defined, create a default inertial provider with respect to the output frame.
  588.      *
  589.      * @param interpolationDate interpolation date
  590.      * @param attitudes attitudes sample
  591.      * @param pvProvider position-velocity-acceleration coordinates provider
  592.      *
  593.      * @return interpolated attitude if attitude interpolator is present, default attitude otherwise
  594.      */
  595.     private Attitude interpolateAttitude(final AbsoluteDate interpolationDate, final List<Attitude> attitudes,
  596.                                          final PVCoordinatesProvider pvProvider) {
  597.         if (attitudes.isEmpty()) {
  598.             final AttitudeProvider attitudeProvider = new FrameAlignedProvider(outputFrame);
  599.             return attitudeProvider.getAttitude(pvProvider, interpolationDate, outputFrame);
  600.         } else {
  601.             return attitudeInterpolator.interpolate(interpolationDate, attitudes);
  602.         }
  603.     }
  604. }