SpacecraftStateInterpolator.java

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

  44. import java.util.ArrayList;
  45. import java.util.Collection;
  46. import java.util.HashMap;
  47. import java.util.List;
  48. import java.util.Map;
  49. import java.util.Optional;

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

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

  67.     /** Orbit interpolator. */
  68.     private final TimeInterpolator<Orbit> orbitInterpolator;

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

  71.     /** Mass interpolator. */
  72.     private final TimeInterpolator<TimeStampedDouble> massInterpolator;

  73.     /** Attitude interpolator. */
  74.     private final TimeInterpolator<Attitude> attitudeInterpolator;

  75.     /** Additional state interpolator. */
  76.     private final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator;

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

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

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

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

  209.     /**
  210.      * Constructor to create a customizable Hermite interpolator for every spacecraft state field.
  211.      * <p>
  212.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  213.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  214.      * phenomenon</a> and numerical problems (including NaN appearing).
  215.      * <p>
  216.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if this instance is going to interpolate between
  217.      * tabulated spacecraft states defined by orbit, will throw an error otherwise.
  218.      *
  219.      * @param interpolationPoints number of interpolation points
  220.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  221.      * @param outputFrame output frame
  222.      * @param attitudeReferenceFrame reference frame from which attitude is defined
  223.      * @param pvaFilter filter for derivatives from the sample to use in position-velocity-acceleration interpolation
  224.      * @param angularFilter filter for derivatives from the sample to use in attitude interpolation
  225.      */
  226.     public SpacecraftStateInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  227.                                        final Frame outputFrame, final Frame attitudeReferenceFrame,
  228.                                        final CartesianDerivativesFilter pvaFilter,
  229.                                        final AngularDerivativesFilter angularFilter) {
  230.         this(interpolationPoints, extrapolationThreshold, outputFrame,
  231.              new OrbitHermiteInterpolator(interpolationPoints, extrapolationThreshold, outputFrame, pvaFilter),
  232.              new AbsolutePVCoordinatesHermiteInterpolator(interpolationPoints, extrapolationThreshold, outputFrame,
  233.                                                           pvaFilter),
  234.              new TimeStampedDoubleHermiteInterpolator(interpolationPoints, extrapolationThreshold),
  235.              new AttitudeInterpolator(attitudeReferenceFrame,
  236.                                       new TimeStampedAngularCoordinatesHermiteInterpolator(interpolationPoints,
  237.                                                                                            extrapolationThreshold,
  238.                                                                                            angularFilter)),
  239.              new TimeStampedDoubleHermiteInterpolator(interpolationPoints, extrapolationThreshold));
  240.     }

  241.     /**
  242.      * Constructor.
  243.      * <p>
  244.      * At least one interpolator for either orbit or absolute position-velocity-acceleration is needed. All the other
  245.      * interpolators can be left to null if the user do not want to interpolate these values.
  246.      * <p>
  247.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if interpolated spacecraft states are defined by orbit. Throws an
  248.      * error otherwise.
  249.      * <p>
  250.      * <b>BEWARE:</b> it is up to the user to check the consistency of input interpolators.
  251.      *
  252.      * @param interpolationPoints number of interpolation points
  253.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  254.      * @param outputFrame output frame (inertial if the user is planning to use the orbit interpolator)
  255.      * @param orbitInterpolator orbit interpolator (can be null if absPVAInterpolator is defined)
  256.      * @param absPVAInterpolator absolute position-velocity-acceleration (can be null if orbitInterpolator is defined)
  257.      * @param massInterpolator mass interpolator (can be null)
  258.      * @param attitudeInterpolator attitude interpolator (can be null)
  259.      * @param additionalStateInterpolator additional state interpolator (can be null)
  260.      *
  261.      * @since 12.0.1
  262.      */
  263.     public SpacecraftStateInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  264.                                        final Frame outputFrame, final TimeInterpolator<Orbit> orbitInterpolator,
  265.                                        final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator,
  266.                                        final TimeInterpolator<TimeStampedDouble> massInterpolator,
  267.                                        final TimeInterpolator<Attitude> attitudeInterpolator,
  268.                                        final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator) {
  269.         super(interpolationPoints, extrapolationThreshold);
  270.         checkAtLeastOneInterpolator(orbitInterpolator, absPVAInterpolator);
  271.         this.outputFrame                 = outputFrame;
  272.         this.orbitInterpolator           = orbitInterpolator;
  273.         this.absPVAInterpolator          = absPVAInterpolator;
  274.         this.massInterpolator            = massInterpolator;
  275.         this.attitudeInterpolator        = attitudeInterpolator;
  276.         this.additionalStateInterpolator = additionalStateInterpolator;
  277.     }

  278.     /**
  279.      * Check that an interpolator exist for given sample state definition.
  280.      *
  281.      * @param sample sample (non empty)
  282.      * @param orbitInterpolatorIsPresent flag defining if an orbit interpolator has been defined for this instance
  283.      * @param absPVInterpolatorIsPresent flag defining if an absolute position-velocity-acceleration interpolator has been
  284.      * defined for this instance
  285.      *
  286.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  287.      * definition type
  288.      */
  289.     public static void checkSampleAndInterpolatorConsistency(final List<SpacecraftState> sample,
  290.                                                              final boolean orbitInterpolatorIsPresent,
  291.                                                              final boolean absPVInterpolatorIsPresent) {
  292.         // Get first state definition
  293.         final SpacecraftState earliestState = sample.get(0);

  294.         if (earliestState.isOrbitDefined() && !orbitInterpolatorIsPresent ||
  295.                 !earliestState.isOrbitDefined() && !absPVInterpolatorIsPresent) {
  296.             throw new OrekitIllegalArgumentException(OrekitMessages.WRONG_INTERPOLATOR_DEFINED_FOR_STATE_INTERPOLATION);
  297.         }
  298.     }

  299.     /**
  300.      * Check that all state are either orbit defined or based on absolute position-velocity-acceleration.
  301.      *
  302.      * @param states spacecraft state sample
  303.      */
  304.     public static void checkStatesDefinitionsConsistency(final List<SpacecraftState> states) {
  305.         // Check all states handle the same additional states and are defined the same way (orbit or absolute PVA)
  306.         final SpacecraftState s0               = states.get(0);
  307.         final boolean         s0IsOrbitDefined = s0.isOrbitDefined();
  308.         for (final SpacecraftState state : states) {
  309.             s0.ensureCompatibleAdditionalStates(state);
  310.             if (s0IsOrbitDefined != state.isOrbitDefined()) {
  311.                 throw new OrekitIllegalArgumentException(OrekitMessages.DIFFERENT_STATE_DEFINITION);
  312.             }
  313.         }
  314.     }

  315.     /**
  316.      * {@inheritDoc}
  317.      * <p>
  318.      * The additional states that are interpolated are the ones already present in the first neighbor instance. The sample
  319.      * instances must therefore have at least the same additional states as this neighbor instance. They may have more
  320.      * additional states, but the extra ones will be ignored.
  321.      * <p>
  322.      * All the sample instances <em>must</em> be based on similar trajectory data, i.e. they must either all be based on
  323.      * orbits or all be based on absolute position-velocity-acceleration. Any inconsistency will trigger an
  324.      * {@link OrekitIllegalArgumentException}.
  325.      *
  326.      * @throws OrekitIllegalArgumentException if there are states defined by orbits and absolute
  327.      * position-velocity-acceleration coordinates
  328.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  329.      * definition type
  330.      */
  331.     @Override
  332.     public SpacecraftState interpolate(final AbsoluteDate interpolationDate, final Collection<SpacecraftState> sample) {

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

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

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

  339.             // Check interpolator and sample consistency
  340.             checkSampleAndInterpolatorConsistency(sampleList, orbitInterpolator != null, absPVAInterpolator != null);
  341.         }

  342.         return super.interpolate(interpolationDate, sample);
  343.     }

  344.     /** {@inheritDoc} */
  345.     @Override
  346.     public List<TimeInterpolator<? extends TimeStamped>> getSubInterpolators() {

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

  349.         addOptionalSubInterpolatorIfDefined(orbitInterpolator, subInterpolators);
  350.         addOptionalSubInterpolatorIfDefined(absPVAInterpolator, subInterpolators);
  351.         addOptionalSubInterpolatorIfDefined(massInterpolator, subInterpolators);
  352.         addOptionalSubInterpolatorIfDefined(attitudeInterpolator, subInterpolators);
  353.         addOptionalSubInterpolatorIfDefined(additionalStateInterpolator, subInterpolators);

  354.         return subInterpolators;

  355.     }

  356.     /**
  357.      * {@inheritDoc}
  358.      */
  359.     @Override
  360.     protected SpacecraftState interpolate(final InterpolationData interpolationData) {

  361.         // Get first state definition
  362.         final List<SpacecraftState> samples   = interpolationData.getNeighborList();
  363.         final SpacecraftState earliestState   = samples.get(0);
  364.         final boolean         areOrbitDefined = earliestState.isOrbitDefined();

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

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

  368.         final List<DataDictionary.Entry> additionalEntries = earliestState.getAdditionalDataValues().getData();
  369.         final Map<String, List<Pair<AbsoluteDate, Object>>> additionalSample =
  370.                 createAdditionalDataSample(additionalEntries);

  371.         final List<DoubleArrayDictionary.Entry> additionalDotEntries =
  372.                 earliestState.getAdditionalStatesDerivatives().getData();
  373.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalDotSample =
  374.                 createAdditionalStateSample(additionalDotEntries);

  375.         // Fill interpolators with samples
  376.         final List<Orbit>                 orbitSample  = new ArrayList<>();
  377.         final List<AbsolutePVCoordinates> absPVASample = new ArrayList<>();
  378.         for (SpacecraftState state : samples) {
  379.             final AbsoluteDate currentDate = state.getDate();

  380.             // Add orbit sample if state is defined with an orbit
  381.             if (state.isOrbitDefined()) {
  382.                 orbitSample.add(state.getOrbit());
  383.             }
  384.             // Add absolute position-velocity-acceleration sample if state is defined with an absolute position-velocity-acceleration
  385.             else {
  386.                 absPVASample.add(state.getAbsPVA());
  387.             }

  388.             // Add mass sample
  389.             if (massInterpolator != null) {
  390.                 masses.add(new TimeStampedDouble(state.getMass(), state.getDate()));
  391.             }

  392.             // Add attitude sample if it is interpolated
  393.             if (attitudeInterpolator != null) {
  394.                 attitudes.add(state.getAttitude());
  395.             }

  396.             if (additionalStateInterpolator != null) {

  397.                 // Add all additional state values if they are interpolated
  398.                 for (final Map.Entry<String, List<Pair<AbsoluteDate, Object>>> entry : additionalSample.entrySet()) {
  399.                     entry.getValue().add(new Pair<>(currentDate, state.getAdditionalState(entry.getKey())));
  400.                 }

  401.                 // Add all additional state derivative values if they are interpolated
  402.                 for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalDotSample.entrySet()) {
  403.                     entry.getValue().add(new Pair<>(currentDate, state.getAdditionalStateDerivative(entry.getKey())));
  404.                 }
  405.             }
  406.         }

  407.         // Interpolate mass
  408.         final AbsoluteDate interpolationDate = interpolationData.getInterpolationDate();
  409.         final double       interpolatedMass;
  410.         if (massInterpolator != null) {
  411.             interpolatedMass = massInterpolator.interpolate(interpolationDate, masses).getValue();
  412.         } else {
  413.             interpolatedMass = SpacecraftState.DEFAULT_MASS;
  414.         }

  415.         // Interpolate additional states and derivatives
  416.         final DataDictionary interpolatedAdditional;
  417.         final DoubleArrayDictionary interpolatedAdditionalDot;
  418.         if (additionalStateInterpolator != null) {
  419.             interpolatedAdditional    = interpolateAdditionalState(interpolationDate, additionalSample).orElse(null);
  420.             interpolatedAdditionalDot = interpolateAdditionalState(interpolationDate, additionalDotSample).map(DataDictionary::toDoubleDictionary).orElse(null);
  421.         } else {
  422.             interpolatedAdditional    = null;
  423.             interpolatedAdditionalDot = null;
  424.         }

  425.         // Interpolate orbit
  426.         if (areOrbitDefined && orbitInterpolator != null) {
  427.             final Orbit interpolatedOrbit = orbitInterpolator.interpolate(interpolationDate, orbitSample);

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

  429.             return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  430.                                        interpolatedAdditionalDot);
  431.         }
  432.         // Interpolate absolute position-velocity-acceleration
  433.         else if (!areOrbitDefined && absPVAInterpolator != null) {

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

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

  436.             return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  437.                                        interpolatedAdditionalDot);
  438.         }
  439.         // Should never happen
  440.         else {
  441.             throw new OrekitInternalError(null);
  442.         }

  443.     }

  444.     /**
  445.      * Get output frame.
  446.      *
  447.      * @return output frame
  448.      */
  449.     public Frame getOutputFrame() {
  450.         return outputFrame;
  451.     }

  452.     /**
  453.      * Get orbit interpolator.
  454.      *
  455.      * @return optional orbit interpolator
  456.      *
  457.      * @see Optional
  458.      */
  459.     public Optional<TimeInterpolator<Orbit>> getOrbitInterpolator() {
  460.         return Optional.ofNullable(orbitInterpolator);
  461.     }

  462.     /**
  463.      * Get absolute position-velocity-acceleration interpolator.
  464.      *
  465.      * @return optional absolute position-velocity-acceleration interpolator
  466.      *
  467.      * @see Optional
  468.      */
  469.     public Optional<TimeInterpolator<AbsolutePVCoordinates>> getAbsPVAInterpolator() {
  470.         return Optional.ofNullable(absPVAInterpolator);
  471.     }

  472.     /**
  473.      * Get mass interpolator.
  474.      *
  475.      * @return optional mass interpolator
  476.      *
  477.      * @see Optional
  478.      */
  479.     public Optional<TimeInterpolator<TimeStampedDouble>> getMassInterpolator() {
  480.         return Optional.ofNullable(massInterpolator);
  481.     }

  482.     /**
  483.      * Get attitude interpolator.
  484.      *
  485.      * @return optional attitude interpolator
  486.      *
  487.      * @see Optional
  488.      */
  489.     public Optional<TimeInterpolator<Attitude>> getAttitudeInterpolator() {
  490.         return Optional.ofNullable(attitudeInterpolator);
  491.     }

  492.     /**
  493.      * Get additional state interpolator.
  494.      *
  495.      * @return optional additional state interpolator
  496.      *
  497.      * @see Optional
  498.      */
  499.     public Optional<TimeInterpolator<TimeStampedDouble>> getAdditionalStateInterpolator() {
  500.         return Optional.ofNullable(additionalStateInterpolator);
  501.     }

  502.     /**
  503.      * Check that at least one interpolator is defined.
  504.      *
  505.      * @param orbitInterpolatorToCheck orbit interpolator
  506.      * @param absPVAInterpolatorToCheck absolute position-velocity-acceleration interpolator
  507.      */
  508.     private void checkAtLeastOneInterpolator(final TimeInterpolator<Orbit> orbitInterpolatorToCheck,
  509.                                              final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolatorToCheck) {
  510.         if (orbitInterpolatorToCheck == null && absPVAInterpolatorToCheck == null) {
  511.             throw new OrekitIllegalArgumentException(OrekitMessages.NO_INTERPOLATOR_FOR_STATE_DEFINITION);
  512.         }
  513.     }

  514.     /**
  515.      * Create empty samples for given additional entries.
  516.      *
  517.      * @param additionalEntries tabulated additional entries
  518.      *
  519.      * @return empty samples for given additional entries
  520.      */
  521.     private Map<String, List<Pair<AbsoluteDate, double[]>>> createAdditionalStateSample(
  522.             final List<DoubleArrayDictionary.Entry> additionalEntries) {
  523.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSamples = new HashMap<>(additionalEntries.size());

  524.         for (final DoubleArrayDictionary.Entry entry : additionalEntries) {
  525.             additionalSamples.put(entry.getKey(), new ArrayList<>());
  526.         }
  527.         return additionalSamples;
  528.     }

  529.     /**
  530.      * Create empty samples for given additional entries.
  531.      *
  532.      * @param additionalEntries tabulated additional entries
  533.      *
  534.      * @return empty samples for given additional entries
  535.      */
  536.     private Map<String, List<Pair<AbsoluteDate, Object>>> createAdditionalDataSample(
  537.             final List<DataDictionary.Entry> additionalEntries) {
  538.         final Map<String, List<Pair<AbsoluteDate, Object>>> additionalSamples = new HashMap<>(additionalEntries.size());

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

  544.     /**
  545.      * Interpolate additional values.
  546.      *  Double arrays are interpolated whereas objects values are kept identical until change.
  547.      *
  548.      * @param <T> type of the data
  549.      * @param interpolationDate interpolation date
  550.      * @param additionalSamples additional data samples
  551.      *
  552.      * @return interpolated additional data values
  553.      */
  554.     private <T> Optional<DataDictionary> interpolateAdditionalState(final AbsoluteDate interpolationDate,
  555.                                                                     final Map<String, List<Pair<AbsoluteDate, T>>> additionalSamples) {
  556.         final Optional<DataDictionary> interpolatedAdditional;

  557.         if (additionalSamples.isEmpty()) {
  558.             interpolatedAdditional = Optional.empty();
  559.         } else {
  560.             interpolatedAdditional = Optional.of(new DataDictionary(additionalSamples.size()));
  561.             for (final Map.Entry<String, List<Pair<AbsoluteDate, T>>> entry : additionalSamples.entrySet()) {
  562.                 // Get current entry
  563.                 final List<Pair<AbsoluteDate, T>> currentAdditionalSamples = entry.getValue();
  564.                 final T currentInterpolatedAdditional;
  565.                 if (currentAdditionalSamples.get(0).getValue() instanceof double[]) {
  566.                     currentInterpolatedAdditional = (T) interpolateAdditionalSamples(interpolationDate, currentAdditionalSamples);
  567.                 } else {
  568.                     currentInterpolatedAdditional = currentAdditionalSamples.stream()
  569.                                                                             .filter(pair -> pair.getKey().isAfter(interpolationDate))
  570.                                                                             .min(Comparator.comparing(Pair::getKey))
  571.                                                                             .get()
  572.                                                                             .getValue();
  573.                 }
  574.                 interpolatedAdditional.get().put(entry.getKey(), currentInterpolatedAdditional);
  575.             }
  576.         }
  577.         return interpolatedAdditional;
  578.     }

  579.     private <T> double[] interpolateAdditionalSamples(final AbsoluteDate interpolationDate, final List<Pair<AbsoluteDate, T>> currentAdditionalSamples) {
  580.         // Extract number of values for this specific entry
  581.         final int nbOfValues = ((double[]) currentAdditionalSamples.get(0).getValue()).length;

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

  585.             // Create final index for lambda expression use
  586.             final int currentIndex = i;

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

  589.             currentAdditionalSamples.forEach(
  590.                     currentSamples -> currentValueSample.add(new TimeStampedDouble(((double[]) currentSamples.getValue())[currentIndex], currentSamples.getFirst())));

  591.             // Interpolate
  592.             currentInterpolatedAdditional[i] = additionalStateInterpolator.interpolate(interpolationDate, currentValueSample).getValue();
  593.         }
  594.         return currentInterpolatedAdditional;
  595.     }

  596.     /**
  597.      * Interpolate attitude.
  598.      * <p>
  599.      * If no attitude interpolator were defined, create a default inertial provider with respect to the output frame.
  600.      *
  601.      * @param interpolationDate interpolation date
  602.      * @param attitudes attitudes sample
  603.      * @param pvProvider position-velocity-acceleration coordinates provider
  604.      *
  605.      * @return interpolated attitude if attitude interpolator is present, default attitude otherwise
  606.      */
  607.     private Attitude interpolateAttitude(final AbsoluteDate interpolationDate, final List<Attitude> attitudes,
  608.                                          final PVCoordinatesProvider pvProvider) {
  609.         if (attitudes.isEmpty()) {
  610.             final AttitudeProvider attitudeProvider = new FrameAlignedProvider(outputFrame);
  611.             return attitudeProvider.getAttitude(pvProvider, interpolationDate, outputFrame);
  612.         } else {
  613.             return attitudeInterpolator.interpolate(interpolationDate, attitudes);
  614.         }
  615.     }
  616. }