SpacecraftStateInterpolator.java

  1. /* Copyright 2002-2023 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.OrekitException;
  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.PVCoordinatesProvider;
  42. import org.orekit.utils.TimeStampedAngularCoordinatesHermiteInterpolator;

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

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

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

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

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

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

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

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

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

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

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

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

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

  207.     /**
  208.      * Constructor.
  209.      * <p>
  210.      * At least one interpolator for either orbit or absolute position-velocity-acceleration is needed. All the other
  211.      * interpolators can be left to null if the user do not want to interpolate these values.
  212.      * <p>
  213.      * <b>BEWARE:</b> output frame <b>must be inertial</b> if interpolated spacecraft states are defined by orbit. Throws an
  214.      * error otherwise.
  215.      * <p>
  216.      * <b>BEWARE:</b> it is up to the user to check the consistency of input interpolators.
  217.      *
  218.      * @param outputFrame output frame (inertial if the user is planning to use the orbit interpolator)
  219.      * @param orbitInterpolator orbit interpolator (can be null if absPVAInterpolator is defined)
  220.      * @param absPVAInterpolator absolute position-velocity-acceleration (can be null if orbitInterpolator is defined)
  221.      * @param massInterpolator mass interpolator (can be null)
  222.      * @param attitudeInterpolator attitude interpolator (can be null)
  223.      * @param additionalStateInterpolator additional state interpolator (can be null)
  224.      */
  225.     public SpacecraftStateInterpolator(final Frame outputFrame, final TimeInterpolator<Orbit> orbitInterpolator,
  226.                                        final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator,
  227.                                        final TimeInterpolator<TimeStampedDouble> massInterpolator,
  228.                                        final TimeInterpolator<Attitude> attitudeInterpolator,
  229.                                        final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator) {
  230.         super(DEFAULT_INTERPOLATION_POINTS, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
  231.         checkAtLeastOneInterpolator(orbitInterpolator, absPVAInterpolator);
  232.         this.outputFrame                 = outputFrame;
  233.         this.orbitInterpolator           = orbitInterpolator;
  234.         this.absPVAInterpolator          = absPVAInterpolator;
  235.         this.massInterpolator            = massInterpolator;
  236.         this.attitudeInterpolator        = attitudeInterpolator;
  237.         this.additionalStateInterpolator = additionalStateInterpolator;
  238.     }

  239.     /**
  240.      * Check that an interpolator exist for given sample state definition.
  241.      *
  242.      * @param sample sample (non empty)
  243.      * @param orbitInterpolatorIsPresent flag defining if an orbit interpolator has been defined for this instance
  244.      * @param absPVInterpolatorIsPresent flag defining if an absolute position-velocity-acceleration interpolator has been
  245.      * defined for this instance
  246.      *
  247.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  248.      * definition type
  249.      */
  250.     public static void checkSampleAndInterpolatorConsistency(final List<SpacecraftState> sample,
  251.                                                              final boolean orbitInterpolatorIsPresent,
  252.                                                              final boolean absPVInterpolatorIsPresent) {
  253.         // Get first state definition
  254.         final SpacecraftState earliestState = sample.get(0);

  255.         if (earliestState.isOrbitDefined() && !orbitInterpolatorIsPresent ||
  256.                 !earliestState.isOrbitDefined() && !absPVInterpolatorIsPresent) {
  257.             throw new OrekitIllegalArgumentException(OrekitMessages.WRONG_INTERPOLATOR_DEFINED_FOR_STATE_INTERPOLATION);
  258.         }
  259.     }

  260.     /**
  261.      * Check that all state are either orbit defined or based on absolute position-velocity-acceleration.
  262.      *
  263.      * @param states spacecraft state sample
  264.      */
  265.     public static void checkStatesDefinitionsConsistency(final List<SpacecraftState> states) {
  266.         // Check all states handle the same additional states and are defined the same way (orbit or absolute PVA)
  267.         final SpacecraftState s0               = states.get(0);
  268.         final boolean         s0IsOrbitDefined = s0.isOrbitDefined();
  269.         for (final SpacecraftState state : states) {
  270.             s0.ensureCompatibleAdditionalStates(state);
  271.             if (s0IsOrbitDefined != state.isOrbitDefined()) {
  272.                 throw new OrekitIllegalArgumentException(OrekitMessages.DIFFERENT_STATE_DEFINITION);
  273.             }
  274.         }
  275.     }

  276.     /**
  277.      * {@inheritDoc}
  278.      * <p>
  279.      * The additional states that are interpolated are the ones already present in the first neighbor instance. The sample
  280.      * instances must therefore have at least the same additional states as this neighbor instance. They may have more
  281.      * additional states, but the extra ones will be ignored.
  282.      * <p>
  283.      * All the sample instances <em>must</em> be based on similar trajectory data, i.e. they must either all be based on
  284.      * orbits or all be based on absolute position-velocity-acceleration. Any inconsistency will trigger an
  285.      * {@link OrekitIllegalArgumentException}.
  286.      *
  287.      * @throws OrekitIllegalArgumentException if there are states defined by orbits and absolute
  288.      * position-velocity-acceleration coordinates
  289.      * @throws OrekitIllegalArgumentException if there is no defined interpolator for given sample spacecraft state
  290.      * definition type
  291.      */
  292.     @Override
  293.     public SpacecraftState interpolate(final AbsoluteDate interpolationDate, final Collection<SpacecraftState> sample) {

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

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

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

  300.             // Check interpolator and sample consistency
  301.             checkSampleAndInterpolatorConsistency(sampleList, orbitInterpolator != null, absPVAInterpolator != null);
  302.         }

  303.         return super.interpolate(interpolationDate, sample);
  304.     }

  305.     /** {@inheritDoc} */
  306.     @Override
  307.     public List<TimeInterpolator<? extends TimeStamped>> getSubInterpolators() {

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

  310.         addOptionalSubInterpolatorIfDefined(orbitInterpolator, subInterpolators);
  311.         addOptionalSubInterpolatorIfDefined(absPVAInterpolator, subInterpolators);
  312.         addOptionalSubInterpolatorIfDefined(massInterpolator, subInterpolators);
  313.         addOptionalSubInterpolatorIfDefined(attitudeInterpolator, subInterpolators);
  314.         addOptionalSubInterpolatorIfDefined(additionalStateInterpolator, subInterpolators);

  315.         return subInterpolators;

  316.     }

  317.     /**
  318.      * @return fail to return number of interpolation points used by this interpolator.
  319.      *
  320.      * @throws OrekitException because multiple interpolator are defined so the number of interpolation points used may
  321.      * differ.
  322.      */
  323.     @Override
  324.     public int getNbInterpolationPoints() {
  325.         throw new OrekitException(OrekitMessages.MULTIPLE_INTERPOLATOR_USED);
  326.     }

  327.     /**
  328.      * {@inheritDoc}
  329.      */
  330.     @Override
  331.     protected SpacecraftState interpolate(final InterpolationData interpolationData) {

  332.         // Get first state definition
  333.         final SpacecraftState earliestState   = interpolationData.getNeighborList().get(0);
  334.         final boolean         areOrbitDefined = earliestState.isOrbitDefined();

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

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

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

  341.         final List<DoubleArrayDictionary.Entry> additionalDotEntries =
  342.                 earliestState.getAdditionalStatesDerivatives().getData();
  343.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalDotSample =
  344.                 createAdditionalStateSample(additionalDotEntries);

  345.         // Fill interpolators with samples
  346.         final List<SpacecraftState>       samples      = interpolationData.getCachedSamples().getAll();
  347.         final List<Orbit>                 orbitSample  = new ArrayList<>();
  348.         final List<AbsolutePVCoordinates> absPVASample = new ArrayList<>();
  349.         for (SpacecraftState state : samples) {
  350.             final AbsoluteDate currentDate = state.getDate();

  351.             // Add orbit sample if state is defined with an orbit
  352.             if (state.isOrbitDefined()) {
  353.                 orbitSample.add(state.getOrbit());
  354.             }
  355.             // Add absolute position-velocity-acceleration sample if state is defined with an absolute position-velocity-acceleration
  356.             else {
  357.                 absPVASample.add(state.getAbsPVA());
  358.             }

  359.             // Add mass sample
  360.             if (massInterpolator != null) {
  361.                 masses.add(new TimeStampedDouble(state.getMass(), state.getDate()));
  362.             }

  363.             // Add attitude sample if it is interpolated
  364.             if (attitudeInterpolator != null) {
  365.                 attitudes.add(state.getAttitude());
  366.             }

  367.             if (additionalStateInterpolator != null) {

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

  372.                 // Add all additional state derivative values if they are interpolated
  373.                 for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalDotSample.entrySet()) {
  374.                     entry.getValue().add(new Pair<>(currentDate, state.getAdditionalStateDerivative(entry.getKey())));
  375.                 }
  376.             }
  377.         }

  378.         // Interpolate mass
  379.         final AbsoluteDate interpolationDate = interpolationData.getInterpolationDate();
  380.         final double       interpolatedMass;
  381.         if (massInterpolator != null) {
  382.             interpolatedMass = massInterpolator.interpolate(interpolationDate, masses).getValue();
  383.         } else {
  384.             interpolatedMass = SpacecraftState.DEFAULT_MASS;
  385.         }

  386.         // Interpolate additional states and derivatives
  387.         final DoubleArrayDictionary interpolatedAdditional;
  388.         final DoubleArrayDictionary interpolatedAdditionalDot;
  389.         if (additionalStateInterpolator != null) {
  390.             interpolatedAdditional    = interpolateAdditionalState(interpolationDate, additionalSample);
  391.             interpolatedAdditionalDot = interpolateAdditionalState(interpolationDate, additionalDotSample);
  392.         } else {
  393.             interpolatedAdditional    = null;
  394.             interpolatedAdditionalDot = null;
  395.         }

  396.         // Interpolate orbit
  397.         if (areOrbitDefined && orbitInterpolator != null) {
  398.             final Orbit interpolatedOrbit = orbitInterpolator.interpolate(interpolationDate, orbitSample);

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

  400.             return new SpacecraftState(interpolatedOrbit, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  401.                                        interpolatedAdditionalDot);
  402.         }
  403.         // Interpolate absolute position-velocity-acceleration
  404.         else if (!areOrbitDefined && absPVAInterpolator != null) {

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

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

  407.             return new SpacecraftState(interpolatedAbsPva, interpolatedAttitude, interpolatedMass, interpolatedAdditional,
  408.                                        interpolatedAdditionalDot);
  409.         }
  410.         // Should never happen
  411.         else {
  412.             throw new OrekitInternalError(null);
  413.         }

  414.     }

  415.     /**
  416.      * Get output frame.
  417.      *
  418.      * @return output frame
  419.      */
  420.     public Frame getOutputFrame() {
  421.         return outputFrame;
  422.     }

  423.     /**
  424.      * Get orbit interpolator.
  425.      *
  426.      * @return optional orbit interpolator
  427.      *
  428.      * @see Optional
  429.      */
  430.     public Optional<TimeInterpolator<Orbit>> getOrbitInterpolator() {
  431.         return Optional.ofNullable(orbitInterpolator);
  432.     }

  433.     /**
  434.      * Get absolute position-velocity-acceleration interpolator.
  435.      *
  436.      * @return optional absolute position-velocity-acceleration interpolator
  437.      *
  438.      * @see Optional
  439.      */
  440.     public Optional<TimeInterpolator<AbsolutePVCoordinates>> getAbsPVAInterpolator() {
  441.         return Optional.ofNullable(absPVAInterpolator);
  442.     }

  443.     /**
  444.      * Get mass interpolator.
  445.      *
  446.      * @return optional mass interpolator
  447.      *
  448.      * @see Optional
  449.      */
  450.     public Optional<TimeInterpolator<TimeStampedDouble>> getMassInterpolator() {
  451.         return Optional.ofNullable(massInterpolator);
  452.     }

  453.     /**
  454.      * Get attitude interpolator.
  455.      *
  456.      * @return optional attitude interpolator
  457.      *
  458.      * @see Optional
  459.      */
  460.     public Optional<TimeInterpolator<Attitude>> getAttitudeInterpolator() {
  461.         return Optional.ofNullable(attitudeInterpolator);
  462.     }

  463.     /**
  464.      * Get additional state interpolator.
  465.      *
  466.      * @return optional additional state interpolator
  467.      *
  468.      * @see Optional
  469.      */
  470.     public Optional<TimeInterpolator<TimeStampedDouble>> getAdditionalStateInterpolator() {
  471.         return Optional.ofNullable(additionalStateInterpolator);
  472.     }

  473.     /**
  474.      * Check that at least one interpolator is defined.
  475.      *
  476.      * @param orbitInterpolatorToCheck orbit interpolator
  477.      * @param absPVAInterpolatorToCheck absolute position-velocity-acceleration interpolator
  478.      */
  479.     private void checkAtLeastOneInterpolator(final TimeInterpolator<Orbit> orbitInterpolatorToCheck,
  480.                                              final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolatorToCheck) {
  481.         if (orbitInterpolatorToCheck == null && absPVAInterpolatorToCheck == null) {
  482.             throw new OrekitIllegalArgumentException(OrekitMessages.NO_INTERPOLATOR_FOR_STATE_DEFINITION);
  483.         }
  484.     }

  485.     /**
  486.      * Create empty samples for given additional entries.
  487.      *
  488.      * @param additionalEntries tabulated additional entries
  489.      *
  490.      * @return empty samples for given additional entries
  491.      */
  492.     private Map<String, List<Pair<AbsoluteDate, double[]>>> createAdditionalStateSample(
  493.             final List<DoubleArrayDictionary.Entry> additionalEntries) {
  494.         final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSamples = new HashMap<>(additionalEntries.size());

  495.         for (final DoubleArrayDictionary.Entry entry : additionalEntries) {
  496.             additionalSamples.put(entry.getKey(), new ArrayList<>());
  497.         }

  498.         return additionalSamples;
  499.     }

  500.     /**
  501.      * Interpolate additional state values.
  502.      *
  503.      * @param interpolationDate interpolation date
  504.      * @param additionalSamples additional state samples
  505.      *
  506.      * @return interpolated additional state values
  507.      */
  508.     private DoubleArrayDictionary interpolateAdditionalState(final AbsoluteDate interpolationDate,
  509.                                                              final Map<String, List<Pair<AbsoluteDate, double[]>>> additionalSamples) {
  510.         final DoubleArrayDictionary interpolatedAdditional;

  511.         if (additionalSamples.isEmpty()) {
  512.             interpolatedAdditional = null;
  513.         } else {
  514.             interpolatedAdditional = new DoubleArrayDictionary(additionalSamples.size());
  515.             for (final Map.Entry<String, List<Pair<AbsoluteDate, double[]>>> entry : additionalSamples.entrySet()) {

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

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

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

  523.                     // Create final index for lambda expression use
  524.                     final int currentIndex = i;

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

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

  529.                     // Interpolate
  530.                     currentInterpolatedAdditional[i] =
  531.                             additionalStateInterpolator.interpolate(interpolationDate, currentValueSample).getValue();
  532.                 }

  533.                 interpolatedAdditional.put(entry.getKey(), currentInterpolatedAdditional);
  534.             }
  535.         }
  536.         return interpolatedAdditional;
  537.     }

  538.     /**
  539.      * Interpolate attitude.
  540.      * <p>
  541.      * If no attitude interpolator were defined, create a default inertial provider with respect to the output frame.
  542.      *
  543.      * @param interpolationDate interpolation date
  544.      * @param attitudes attitudes sample
  545.      * @param pvProvider position-velocity-acceleration coordinates provider
  546.      *
  547.      * @return interpolated attitude if attitude interpolator is present, default attitude otherwise
  548.      */
  549.     private Attitude interpolateAttitude(final AbsoluteDate interpolationDate, final List<Attitude> attitudes,
  550.                                          final PVCoordinatesProvider pvProvider) {
  551.         if (attitudes.isEmpty()) {
  552.             final AttitudeProvider attitudeProvider = new FrameAlignedProvider(outputFrame);
  553.             return attitudeProvider.getAttitude(pvProvider, interpolationDate, outputFrame);
  554.         } else {
  555.             return attitudeInterpolator.interpolate(interpolationDate, attitudes);
  556.         }
  557.     }
  558. }