Ephemeris.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.analytical;

  18. import org.hipparchus.exception.LocalizedCoreFormats;
  19. import org.hipparchus.exception.MathIllegalArgumentException;
  20. import org.hipparchus.linear.RealMatrix;
  21. import org.orekit.attitudes.Attitude;
  22. import org.orekit.attitudes.AttitudeProvider;
  23. import org.orekit.attitudes.FrameAlignedProvider;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.errors.OrekitIllegalArgumentException;
  26. import org.orekit.errors.OrekitIllegalStateException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.frames.Frame;
  29. import org.orekit.orbits.Orbit;
  30. import org.orekit.propagation.AbstractMatricesHarvester;
  31. import org.orekit.propagation.BoundedPropagator;
  32. import org.orekit.propagation.SpacecraftState;
  33. import org.orekit.propagation.SpacecraftStateInterpolator;
  34. import org.orekit.propagation.StateCovariance;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.AbstractTimeInterpolator;
  37. import org.orekit.time.TimeInterpolator;
  38. import org.orekit.time.TimeStampedPair;
  39. import org.orekit.utils.DoubleArrayDictionary;
  40. import org.orekit.utils.ImmutableTimeStampedCache;

  41. import java.util.ArrayList;
  42. import java.util.List;
  43. import java.util.Optional;
  44. import org.orekit.utils.DataDictionary;

  45. /**
  46.  * This class is designed to accept and handle tabulated orbital entries. Tabulated entries are classified and then
  47.  * extrapolated in way to obtain continuous output, with accuracy and computation methods configured by the user.
  48.  *
  49.  * @author Fabien Maussion
  50.  * @author Véronique Pommier-Maurussane
  51.  * @author Luc Maisonobe
  52.  * @author Vincent Cucchietti
  53.  */
  54. public class Ephemeris extends AbstractAnalyticalPropagator implements BoundedPropagator {

  55.     /** First date in range. */
  56.     private final AbsoluteDate minDate;

  57.     /** Last date in range. */
  58.     private final AbsoluteDate maxDate;

  59.     /** Reference frame. */
  60.     private final Frame frame;

  61.     /** Names of the additional states. */
  62.     private final String[] additional;

  63.     /** List of spacecraft states. */
  64.     private final transient ImmutableTimeStampedCache<SpacecraftState> statesCache;

  65.     /** List of covariances. **/
  66.     private final transient ImmutableTimeStampedCache<StateCovariance> covariancesCache;

  67.     /** Spacecraft state interpolator. */
  68.     private final transient TimeInterpolator<SpacecraftState> stateInterpolator;

  69.     /** State covariance interpolator. */
  70.     private final transient TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator;

  71.     /** Flag defining if states are defined using an orbit or an absolute position-velocity-acceleration. */
  72.     private final transient boolean statesAreOrbitDefined;

  73.     /**
  74.      * Legacy constructor with tabulated states and default Hermite interpolation.
  75.      * <p>
  76.      * As this implementation of interpolation is polynomial, it should be used only with small samples (about 10-20 points)
  77.      * in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a> and numerical
  78.      * problems (including NaN appearing).
  79.      *
  80.      * @param states list of spacecraft states
  81.      * @param interpolationPoints number of points to use in interpolation
  82.      *
  83.      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
  84.      *                                        interpolation
  85.      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
  86.      *                                        position-velocity-acceleration)
  87.      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
  88.      * @see SpacecraftStateInterpolator
  89.      */
  90.     public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints)
  91.             throws MathIllegalArgumentException {
  92.         // If states is empty an exception will be thrown in the other constructor
  93.         this(states, new SpacecraftStateInterpolator(interpolationPoints,
  94.                                                      states.get(0).getFrame(),
  95.                                                      states.get(0).getFrame()),
  96.              new ArrayList<>(), null);
  97.     }

  98.     /**
  99.      * Constructor with tabulated states.
  100.      *
  101.      * @param states list of spacecraft states
  102.      * @param stateInterpolator spacecraft state interpolator
  103.      *
  104.      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
  105.      *                                        interpolation
  106.      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
  107.      *                                        position-velocity-acceleration)
  108.      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
  109.      */
  110.     public Ephemeris(final List<SpacecraftState> states, final TimeInterpolator<SpacecraftState> stateInterpolator)
  111.             throws MathIllegalArgumentException {
  112.         this(states, stateInterpolator, new ArrayList<>(), null);
  113.     }

  114.     /**
  115.      * Constructor with tabulated states.
  116.      *
  117.      * @param states list of spacecraft states
  118.      * @param stateInterpolator spacecraft state interpolator
  119.      * @param attitudeProvider attitude law to use, null by default
  120.      *
  121.      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
  122.      *                                        interpolation
  123.      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
  124.      *                                        position-velocity-acceleration)
  125.      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
  126.      */
  127.     public Ephemeris(final List<SpacecraftState> states, final TimeInterpolator<SpacecraftState> stateInterpolator,
  128.                      final AttitudeProvider attitudeProvider)
  129.             throws MathIllegalArgumentException {
  130.         this(states, stateInterpolator, new ArrayList<>(), null, attitudeProvider);
  131.     }

  132.     /**
  133.      * Constructor with tabulated states and associated covariances.
  134.      *
  135.      * @param states list of spacecraft states
  136.      * @param stateInterpolator spacecraft state interpolator
  137.      * @param covariances tabulated covariances associated to tabulated states ephemeris bounds to be doing extrapolation
  138.      * @param covarianceInterpolator covariance interpolator
  139.      *
  140.      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
  141.      *                                        interpolation
  142.      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
  143.      *                                        position-velocity-acceleration)
  144.      * @throws OrekitIllegalArgumentException if number of states is different from the number of covariances
  145.      * @throws OrekitIllegalStateException    if dates between states and associated covariances are different
  146.      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator, AttitudeProvider)
  147.      * @since 9.0
  148.      */
  149.     public Ephemeris(final List<SpacecraftState> states,
  150.                      final TimeInterpolator<SpacecraftState> stateInterpolator,
  151.                      final List<StateCovariance> covariances,
  152.                      final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator)
  153.             throws MathIllegalArgumentException {
  154.         this(states, stateInterpolator, covariances, covarianceInterpolator,
  155.              // if states is empty an exception will be thrown in the other constructor
  156.              states.isEmpty() ? null : FrameAlignedProvider.of(states.get(0).getFrame()));
  157.     }

  158.     /**
  159.      * Constructor with tabulated states and associated covariances.
  160.      * <p>
  161.      * The user is expected to explicitly define an attitude provider if they want to use one. Otherwise, it is null by
  162.      * default
  163.      *
  164.      * @param states list of spacecraft states
  165.      * @param stateInterpolator spacecraft state interpolator
  166.      * @param covariances tabulated covariances associated to tabulated states
  167.      * @param covarianceInterpolator covariance interpolator
  168.      * @param attitudeProvider attitude law to use, null by default
  169.      *
  170.      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
  171.      *                                        interpolation
  172.      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
  173.      *                                        position-velocity-acceleration)
  174.      * @throws OrekitIllegalArgumentException if number of states is different from the number of covariances
  175.      * @throws OrekitIllegalStateException    if dates between states and associated covariances are different
  176.      * @since 10.1
  177.      */
  178.     public Ephemeris(final List<SpacecraftState> states,
  179.                      final TimeInterpolator<SpacecraftState> stateInterpolator,
  180.                      final List<StateCovariance> covariances,
  181.                      final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator,
  182.                      final AttitudeProvider attitudeProvider)
  183.             throws MathIllegalArgumentException {

  184.         super(attitudeProvider);

  185.         // Check input consistency
  186.         checkInputConsistency(states, stateInterpolator, covariances, covarianceInterpolator);

  187.         // Initialize variables
  188.         final SpacecraftState s0 = states.get(0);
  189.         minDate = s0.getDate();
  190.         maxDate = states.get(states.size() - 1).getDate();
  191.         frame   = s0.getFrame();

  192.         final List<DataDictionary.Entry> as = s0.getAdditionalDataValues().getData();
  193.         additional = new String[as.size()];
  194.         for (int i = 0; i < additional.length; ++i) {
  195.             additional[i] = as.get(i).getKey();
  196.         }

  197.         this.statesCache       = new ImmutableTimeStampedCache<>(stateInterpolator.getNbInterpolationPoints(), states);
  198.         this.stateInterpolator = stateInterpolator;

  199.         this.covarianceInterpolator = covarianceInterpolator;
  200.         if (covarianceInterpolator != null) {
  201.             this.covariancesCache = new ImmutableTimeStampedCache<>(covarianceInterpolator.getNbInterpolationPoints(),
  202.                                                                     covariances);
  203.         } else {
  204.             this.covariancesCache = null;
  205.         }
  206.         this.statesAreOrbitDefined = s0.isOrbitDefined();

  207.         // Initialize initial state
  208.         super.resetInitialState(getInitialState());
  209.     }

  210.     /**
  211.      * Check input consistency between states, covariances and their associated interpolators.
  212.      *
  213.      * @param states spacecraft states sample
  214.      * @param stateInterpolator spacecraft state interpolator
  215.      * @param covariances covariances sample
  216.      * @param covarianceInterpolator covariance interpolator
  217.      */
  218.     public static void checkInputConsistency(final List<SpacecraftState> states,
  219.                                              final TimeInterpolator<SpacecraftState> stateInterpolator,
  220.                                              final List<StateCovariance> covariances,
  221.                                              final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator) {
  222.         // Checks to perform is states are provided
  223.         if (!states.isEmpty()) {
  224.             // Check given that given states definition are consistent
  225.             // (all defined by either orbits or absolute position-velocity-acceleration coordinates)
  226.             SpacecraftStateInterpolator.checkStatesDefinitionsConsistency(states);

  227.             // Check that every interpolator used in the state interpolator are compatible with the sample size
  228.             AbstractTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(stateInterpolator, states.size());

  229.             // Additional checks if covariances are provided
  230.             if (!covariances.isEmpty()) {
  231.                 // Check that every interpolator used in the state covariance interpolator are compatible with the sample size
  232.                 AbstractTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(covarianceInterpolator,
  233.                                                                                       covariances.size());
  234.                 // Check states and covariances consistency
  235.                 checkStatesAndCovariancesConsistency(states, covariances);
  236.             }
  237.         }
  238.         else {
  239.             throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, 0);
  240.         }
  241.     }

  242.     /**
  243.      * Check that given states and covariances are consistent.
  244.      *
  245.      * @param states tabulates states to check
  246.      * @param covariances tabulated covariances associated to tabulated states to check
  247.      */
  248.     public static void checkStatesAndCovariancesConsistency(final List<SpacecraftState> states,
  249.                                                             final List<StateCovariance> covariances) {
  250.         final int nbStates = states.size();

  251.         // Check that we have an equal number of states and covariances
  252.         if (nbStates != covariances.size()) {
  253.             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  254.                                                      states.size(),
  255.                                                      covariances.size());
  256.         }

  257.         // Check that states and covariance are defined at the same date
  258.         for (int i = 0; i < nbStates; i++) {
  259.             if (!states.get(i).getDate().isCloseTo(covariances.get(i).getDate(),
  260.                                                    TimeStampedPair.DEFAULT_DATE_EQUALITY_THRESHOLD)) {
  261.                 throw new OrekitIllegalStateException(OrekitMessages.STATE_AND_COVARIANCE_DATES_MISMATCH,
  262.                                                       states.get(i).getDate(), covariances.get(i).getDate());
  263.             }
  264.         }
  265.     }

  266.     /**
  267.      * Get the first date of the range.
  268.      *
  269.      * @return the first date of the range
  270.      */
  271.     public AbsoluteDate getMinDate() {
  272.         return minDate;
  273.     }

  274.     /**
  275.      * Get the last date of the range.
  276.      *
  277.      * @return the last date of the range
  278.      */
  279.     public AbsoluteDate getMaxDate() {
  280.         return maxDate;
  281.     }

  282.     /** {@inheritDoc} */
  283.     @Override
  284.     public Frame getFrame() {
  285.         return frame;
  286.     }

  287.     /**
  288.      * Get the covariance at given date.
  289.      * <p>
  290.      * BEWARE : If this instance has been created without sample of covariances and/or with spacecraft states defined with
  291.      * absolute position-velocity-acceleration, it will return an empty {@link Optional}.
  292.      *
  293.      * @param date date at which the covariance is desired
  294.      *
  295.      * @return covariance at given date
  296.      *
  297.      * @see Optional
  298.      */
  299.     public Optional<StateCovariance> getCovariance(final AbsoluteDate date) {
  300.         if (covarianceInterpolator != null && covariancesCache != null && statesAreOrbitDefined) {

  301.             // Build list of time stamped pair of orbits and their associated covariances
  302.             final List<TimeStampedPair<Orbit, StateCovariance>> sample = buildOrbitAndCovarianceSample();

  303.             // Interpolate
  304.             final TimeStampedPair<Orbit, StateCovariance> interpolatedOrbitAndCovariance =
  305.                     covarianceInterpolator.interpolate(date, sample);

  306.             return Optional.of(interpolatedOrbitAndCovariance.getSecond());
  307.         }
  308.         else {
  309.             return Optional.empty();
  310.         }

  311.     }

  312.     /** @return sample of orbits and their associated covariances */
  313.     private List<TimeStampedPair<Orbit, StateCovariance>> buildOrbitAndCovarianceSample() {
  314.         final List<TimeStampedPair<Orbit, StateCovariance>> sample      = new ArrayList<>();
  315.         final List<SpacecraftState>                         states      = statesCache.getAll();
  316.         final List<StateCovariance>                         covariances = covariancesCache.getAll();
  317.         for (int i = 0; i < states.size(); i++) {
  318.             sample.add(new TimeStampedPair<>(states.get(i).getOrbit(), covariances.get(i)));
  319.         }

  320.         return sample;
  321.     }

  322.     /** {@inheritDoc} */
  323.     @Override
  324.     public SpacecraftState basicPropagate(final AbsoluteDate date) {

  325.         final AbsoluteDate centralDate =
  326.                 AbstractTimeInterpolator.getCentralDate(date, statesCache, stateInterpolator.getExtrapolationThreshold());
  327.         final SpacecraftState  evaluatedState   = stateInterpolator.interpolate(date, statesCache.getNeighbors(centralDate));
  328.         final AttitudeProvider attitudeProvider = getAttitudeProvider();
  329.         if (attitudeProvider == null) {
  330.             return evaluatedState;
  331.         }
  332.         else {
  333.             final Attitude calculatedAttitude;
  334.             // Verify if orbit is defined
  335.             if (evaluatedState.isOrbitDefined()) {
  336.                 calculatedAttitude =
  337.                         attitudeProvider.getAttitude(evaluatedState.getOrbit(), date, evaluatedState.getFrame());
  338.                 return new SpacecraftState(evaluatedState.getOrbit(), calculatedAttitude, evaluatedState.getMass(),
  339.                                            evaluatedState.getAdditionalDataValues(),
  340.                                            evaluatedState.getAdditionalStatesDerivatives());
  341.             }
  342.             else {
  343.                 calculatedAttitude =
  344.                         attitudeProvider.getAttitude(evaluatedState.getAbsPVA(), date, evaluatedState.getFrame());
  345.                 return new SpacecraftState(evaluatedState.getAbsPVA(), calculatedAttitude, evaluatedState.getMass(),
  346.                                            evaluatedState.getAdditionalDataValues(),
  347.                                            evaluatedState.getAdditionalStatesDerivatives());
  348.             }

  349.         }
  350.     }

  351.     /** {@inheritDoc} */
  352.     public Orbit propagateOrbit(final AbsoluteDate date) {
  353.         return basicPropagate(date).getOrbit();
  354.     }

  355.     /** {@inheritDoc} */
  356.     protected double getMass(final AbsoluteDate date) {
  357.         return basicPropagate(date).getMass();
  358.     }

  359.     /**
  360.      * Try (and fail) to reset the initial state.
  361.      * <p>
  362.      * This method always throws an exception, as ephemerides cannot be reset.
  363.      * </p>
  364.      *
  365.      * @param state new initial state to consider
  366.      */
  367.     public void resetInitialState(final SpacecraftState state) {
  368.         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
  369.     }

  370.     /** {@inheritDoc} */
  371.     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
  372.         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
  373.     }

  374.     /** {@inheritDoc} */
  375.     public SpacecraftState getInitialState() {
  376.         return basicPropagate(getMinDate());
  377.     }

  378.     /** {@inheritDoc} */
  379.     @Override
  380.     public boolean isAdditionalDataManaged(final String name) {

  381.         // the additional state may be managed by a specific provider in the base class
  382.         if (super.isAdditionalDataManaged(name)) {
  383.             return true;
  384.         }

  385.         // the additional state may be managed in the states sample
  386.         for (final String a : additional) {
  387.             if (a.equals(name)) {
  388.                 return true;
  389.             }
  390.         }

  391.         return false;

  392.     }

  393.     /** {@inheritDoc} */
  394.     @Override
  395.     public String[] getManagedAdditionalData() {
  396.         final String[] upperManaged = super.getManagedAdditionalData();
  397.         final String[] managed      = new String[upperManaged.length + additional.length];
  398.         System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
  399.         System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
  400.         return managed;
  401.     }

  402.     /** {@inheritDoc} */
  403.     @Override
  404.     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  405.                                                         final DoubleArrayDictionary initialJacobianColumns) {
  406.         // In order to not throw an Orekit exception during ephemeris based orbit determination
  407.         // The default behavior of the method is overridden to return a null parameter
  408.         return null;
  409.     }

  410.     /** Get state interpolator.
  411.      * @return state interpolator
  412.      */
  413.     public TimeInterpolator<SpacecraftState> getStateInterpolator() {
  414.         return stateInterpolator;
  415.     }

  416.     /** Get covariance interpolator.
  417.      * @return optional covariance interpolator
  418.      * @see Optional
  419.      */
  420.     public Optional<TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>>> getCovarianceInterpolator() {
  421.         return Optional.ofNullable(covarianceInterpolator);
  422.     }

  423. }