OrbitHermiteInterpolator.java

  1. /* Copyright 2002-2024 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.orbits;

  18. import org.hipparchus.analysis.interpolation.HermiteInterpolator;
  19. import org.hipparchus.util.MathUtils;
  20. import org.orekit.errors.OrekitInternalError;
  21. import org.orekit.frames.Frame;
  22. import org.orekit.time.AbsoluteDate;
  23. import org.orekit.time.TimeInterpolator;
  24. import org.orekit.utils.CartesianDerivativesFilter;
  25. import org.orekit.utils.TimeStampedPVCoordinates;
  26. import org.orekit.utils.TimeStampedPVCoordinatesHermiteInterpolator;

  27. import java.util.List;
  28. import java.util.stream.Stream;

  29. /**
  30.  * Class using a Hermite interpolator to interpolate orbits.
  31.  * <p>
  32.  * Depending on given sample orbit type, the interpolation may differ :
  33.  * <ul>
  34.  *    <li>For Keplerian, Circular and Equinoctial orbits, the interpolated instance is created by polynomial Hermite
  35.  *    interpolation, using derivatives when available. </li>
  36.  *    <li>For Cartesian orbits, the interpolated instance is created using the cartesian derivatives filter given at
  37.  *    instance construction. Hence, it will fall back to Lagrange interpolation if this instance has been designed to not
  38.  *    use derivatives.
  39.  * </ul>
  40.  * <p>
  41.  * In any case, it should be used only with small number of interpolation points (about 10-20 points) in order to avoid
  42.  * <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a> and numerical problems
  43.  * (including NaN appearing).
  44.  *
  45.  * @author Luc Maisonobe
  46.  * @author Vincent Cucchietti
  47.  * @see Orbit
  48.  * @see HermiteInterpolator
  49.  */
  50. public class OrbitHermiteInterpolator extends AbstractOrbitInterpolator {

  51.     /** Filter for derivatives from the sample to use in position-velocity-acceleration interpolation. */
  52.     private final CartesianDerivativesFilter pvaFilter;

  53.     /**
  54.      * Constructor with :
  55.      * <ul>
  56.      *     <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li>
  57.      *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
  58.      *     <li>Use of position and two time derivatives during interpolation</li>
  59.      * </ul>
  60.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  61.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  62.      * phenomenon</a> and numerical problems (including NaN appearing).
  63.      *
  64.      * @param outputInertialFrame output inertial frame
  65.      */
  66.     public OrbitHermiteInterpolator(final Frame outputInertialFrame) {
  67.         this(DEFAULT_INTERPOLATION_POINTS, outputInertialFrame);
  68.     }

  69.     /**
  70.      * Constructor with :
  71.      * <ul>
  72.      *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
  73.      *     <li>Use of position and two time derivatives during interpolation</li>
  74.      * </ul>
  75.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  76.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  77.      * phenomenon</a> and numerical problems (including NaN appearing).
  78.      *
  79.      * @param interpolationPoints number of interpolation points
  80.      * @param outputInertialFrame output inertial frame
  81.      */
  82.     public OrbitHermiteInterpolator(final int interpolationPoints, final Frame outputInertialFrame) {
  83.         this(interpolationPoints, outputInertialFrame, CartesianDerivativesFilter.USE_PVA);
  84.     }

  85.     /**
  86.      * Constructor with default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s).
  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.      *
  92.      * @param interpolationPoints number of interpolation points
  93.      * @param outputInertialFrame output inertial frame
  94.      * @param pvaFilter filter for derivatives from the sample to use in position-velocity-acceleration interpolation. Used
  95.      * only when interpolating Cartesian orbits.
  96.      */
  97.     public OrbitHermiteInterpolator(final int interpolationPoints, final Frame outputInertialFrame,
  98.                                     final CartesianDerivativesFilter pvaFilter) {
  99.         this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputInertialFrame, pvaFilter);
  100.     }

  101.     /**
  102.      * Constructor.
  103.      * <p>
  104.      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
  105.      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
  106.      * phenomenon</a> and numerical problems (including NaN appearing).
  107.      *
  108.      * @param interpolationPoints number of interpolation points
  109.      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
  110.      * @param outputInertialFrame output inertial frame
  111.      * @param pvaFilter filter for derivatives from the sample to use in position-velocity-acceleration interpolation. Used
  112.      * only when interpolating Cartesian orbits.
  113.      */
  114.     public OrbitHermiteInterpolator(final int interpolationPoints, final double extrapolationThreshold,
  115.                                     final Frame outputInertialFrame, final CartesianDerivativesFilter pvaFilter) {
  116.         super(interpolationPoints, extrapolationThreshold, outputInertialFrame);
  117.         this.pvaFilter = pvaFilter;
  118.     }

  119.     /** Get filter for derivatives from the sample to use in position-velocity-acceleration interpolation.
  120.      * @return filter for derivatives from the sample to use in position-velocity-acceleration interpolation
  121.      */
  122.     public CartesianDerivativesFilter getPVAFilter() {
  123.         return pvaFilter;
  124.     }

  125.     /**
  126.      * {@inheritDoc}
  127.      * <p>
  128.      * Depending on given sample orbit type, the interpolation may differ :
  129.      * <ul>
  130.      *    <li>For Keplerian, Circular and Equinoctial orbits, the interpolated instance is created by polynomial Hermite
  131.      *    interpolation, using derivatives when available. </li>
  132.      *    <li>For Cartesian orbits, the interpolated instance is created using the cartesian derivatives filter given at
  133.      *    instance construction. Hence, it will fall back to Lagrange interpolation if this instance has been designed to not
  134.      *    use derivatives.
  135.      * </ul>
  136.      * If orbit interpolation on large samples is needed, using the {@link
  137.      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
  138.      * low-level interpolation. The Ephemeris class automatically handles selection of
  139.      * a neighboring sub-sample with a predefined number of point from a large global sample
  140.      * in a thread-safe way.
  141.      *
  142.      * @param interpolationData interpolation data
  143.      *
  144.      * @return interpolated instance at given date
  145.      */
  146.     @Override
  147.     protected Orbit interpolate(final InterpolationData interpolationData) {
  148.         // Get orbit sample
  149.         final List<Orbit> sample = interpolationData.getNeighborList();

  150.         // Get orbit type for interpolation
  151.         final OrbitType orbitType = sample.get(0).getType();

  152.         if (orbitType == OrbitType.CARTESIAN) {
  153.             return interpolateCartesian(interpolationData.getInterpolationDate(), sample);
  154.         }
  155.         else {
  156.             return interpolateCommon(interpolationData.getInterpolationDate(), sample, orbitType);
  157.         }

  158.     }

  159.     /**
  160.      * Interpolate Cartesian orbit using specific method for Cartesian orbit.
  161.      *
  162.      * @param interpolationDate interpolation date
  163.      * @param sample orbits sample
  164.      *
  165.      * @return interpolated Cartesian orbit
  166.      */
  167.     private CartesianOrbit interpolateCartesian(final AbsoluteDate interpolationDate, final List<Orbit> sample) {

  168.         // Create time stamped position-velocity-acceleration Hermite interpolator
  169.         final TimeInterpolator<TimeStampedPVCoordinates> interpolator =
  170.                 new TimeStampedPVCoordinatesHermiteInterpolator(getNbInterpolationPoints(), getExtrapolationThreshold(),
  171.                                                                 pvaFilter);

  172.         // Convert sample to stream
  173.         final Stream<Orbit> sampleStream = sample.stream();

  174.         // Map time stamped position-velocity-acceleration coordinates
  175.         final Stream<TimeStampedPVCoordinates> sampleTimeStampedPV = sampleStream.map(Orbit::getPVCoordinates);

  176.         // Interpolate PVA
  177.         final TimeStampedPVCoordinates interpolated = interpolator.interpolate(interpolationDate, sampleTimeStampedPV);

  178.         // Use first entry gravitational parameter
  179.         final double mu = sample.get(0).getMu();

  180.         return new CartesianOrbit(interpolated, getOutputInertialFrame(), interpolationDate, mu);
  181.     }

  182.     /**
  183.      * Method gathering common parts of interpolation between circular, equinoctial and keplerian orbit.
  184.      *
  185.      * @param interpolationDate interpolation date
  186.      * @param orbits orbits sample
  187.      * @param orbitType interpolation method to use
  188.      *
  189.      * @return interpolated orbit
  190.      */
  191.     private Orbit interpolateCommon(final AbsoluteDate interpolationDate, final List<Orbit> orbits,
  192.                                     final OrbitType orbitType) {

  193.         // First pass to check if derivatives are available throughout the sample
  194.         boolean useDerivatives = true;
  195.         for (final Orbit orbit : orbits) {
  196.             useDerivatives = useDerivatives && orbit.hasDerivatives();
  197.         }

  198.         // Use first entry gravitational parameter
  199.         final double mu = orbits.get(0).getMu();

  200.         // Interpolate and build a new instance
  201.         final double[][] interpolated;
  202.         switch (orbitType) {
  203.             case CIRCULAR:
  204.                 interpolated = interpolateCircular(interpolationDate, orbits, useDerivatives);
  205.                 return new CircularOrbit(interpolated[0][0], interpolated[0][1], interpolated[0][2],
  206.                                          interpolated[0][3], interpolated[0][4], interpolated[0][5],
  207.                                          interpolated[1][0], interpolated[1][1], interpolated[1][2],
  208.                                          interpolated[1][3], interpolated[1][4], interpolated[1][5],
  209.                                          PositionAngleType.MEAN, getOutputInertialFrame(), interpolationDate, mu);
  210.             case KEPLERIAN:
  211.                 interpolated = interpolateKeplerian(interpolationDate, orbits, useDerivatives);
  212.                 return new KeplerianOrbit(interpolated[0][0], interpolated[0][1], interpolated[0][2],
  213.                                           interpolated[0][3], interpolated[0][4], interpolated[0][5],
  214.                                           interpolated[1][0], interpolated[1][1], interpolated[1][2],
  215.                                           interpolated[1][3], interpolated[1][4], interpolated[1][5],
  216.                                           PositionAngleType.MEAN, getOutputInertialFrame(), interpolationDate, mu);
  217.             case EQUINOCTIAL:
  218.                 interpolated = interpolateEquinoctial(interpolationDate, orbits, useDerivatives);
  219.                 return new EquinoctialOrbit(interpolated[0][0], interpolated[0][1], interpolated[0][2],
  220.                                             interpolated[0][3], interpolated[0][4], interpolated[0][5],
  221.                                             interpolated[1][0], interpolated[1][1], interpolated[1][2],
  222.                                             interpolated[1][3], interpolated[1][4], interpolated[1][5],
  223.                                             PositionAngleType.MEAN, getOutputInertialFrame(), interpolationDate, mu);
  224.             default:
  225.                 // Should never happen
  226.                 throw new OrekitInternalError(null);
  227.         }

  228.     }

  229.     /**
  230.      * Build interpolating functions for circular orbit parameters.
  231.      *
  232.      * @param interpolationDate interpolation date
  233.      * @param orbits orbits sample
  234.      * @param useDerivatives flag defining if derivatives are available throughout the sample
  235.      *
  236.      * @return interpolating functions for circular orbit parameters
  237.      */
  238.     private double[][] interpolateCircular(final AbsoluteDate interpolationDate, final List<Orbit> orbits,
  239.                                            final boolean useDerivatives) {

  240.         // Set up an interpolator
  241.         final HermiteInterpolator interpolator = new HermiteInterpolator();

  242.         // Second pass to feed interpolator
  243.         AbsoluteDate previousDate   = null;
  244.         double       previousRAAN   = Double.NaN;
  245.         double       previousAlphaM = Double.NaN;
  246.         for (final Orbit orbit : orbits) {
  247.             final CircularOrbit circ = (CircularOrbit) OrbitType.CIRCULAR.convertType(orbit);
  248.             final double        continuousRAAN;
  249.             final double        continuousAlphaM;
  250.             if (previousDate == null) {
  251.                 continuousRAAN   = circ.getRightAscensionOfAscendingNode();
  252.                 continuousAlphaM = circ.getAlphaM();
  253.             }
  254.             else {
  255.                 final double dt       = circ.getDate().durationFrom(previousDate);
  256.                 final double keplerAM = previousAlphaM + circ.getKeplerianMeanMotion() * dt;
  257.                 continuousRAAN   = MathUtils.normalizeAngle(circ.getRightAscensionOfAscendingNode(), previousRAAN);
  258.                 continuousAlphaM = MathUtils.normalizeAngle(circ.getAlphaM(), keplerAM);
  259.             }
  260.             previousDate   = circ.getDate();
  261.             previousRAAN   = continuousRAAN;
  262.             previousAlphaM = continuousAlphaM;
  263.             if (useDerivatives) {
  264.                 interpolator.addSamplePoint(circ.getDate().durationFrom(interpolationDate),
  265.                                             new double[] { circ.getA(),
  266.                                                            circ.getCircularEx(),
  267.                                                            circ.getCircularEy(),
  268.                                                            circ.getI(),
  269.                                                            continuousRAAN,
  270.                                                            continuousAlphaM },
  271.                                             new double[] { circ.getADot(),
  272.                                                            circ.getCircularExDot(),
  273.                                                            circ.getCircularEyDot(),
  274.                                                            circ.getIDot(),
  275.                                                            circ.getRightAscensionOfAscendingNodeDot(),
  276.                                                            circ.getAlphaMDot() });
  277.             }
  278.             else {
  279.                 interpolator.addSamplePoint(circ.getDate().durationFrom(interpolationDate),
  280.                                             new double[] { circ.getA(),
  281.                                                            circ.getCircularEx(),
  282.                                                            circ.getCircularEy(),
  283.                                                            circ.getI(),
  284.                                                            continuousRAAN,
  285.                                                            continuousAlphaM });
  286.             }
  287.         }

  288.         return interpolator.derivatives(0.0, 1);
  289.     }

  290.     /**
  291.      * Build interpolating functions for keplerian orbit parameters.
  292.      *
  293.      * @param interpolationDate interpolation date
  294.      * @param orbits orbits sample
  295.      * @param useDerivatives flag defining if derivatives are available throughout the sample
  296.      *
  297.      * @return interpolating functions for keplerian orbit parameters
  298.      */
  299.     private double[][] interpolateKeplerian(final AbsoluteDate interpolationDate, final List<Orbit> orbits,
  300.                                             final boolean useDerivatives) {

  301.         // Set up an interpolator
  302.         final HermiteInterpolator interpolator = new HermiteInterpolator();

  303.         // Second pass to feed interpolator
  304.         AbsoluteDate previousDate = null;
  305.         double       previousPA   = Double.NaN;
  306.         double       previousRAAN = Double.NaN;
  307.         double       previousM    = Double.NaN;
  308.         for (final Orbit orbit : orbits) {
  309.             final KeplerianOrbit kep = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(orbit);
  310.             final double         continuousPA;
  311.             final double         continuousRAAN;
  312.             final double         continuousM;
  313.             if (previousDate == null) {
  314.                 continuousPA   = kep.getPerigeeArgument();
  315.                 continuousRAAN = kep.getRightAscensionOfAscendingNode();
  316.                 continuousM    = kep.getMeanAnomaly();
  317.             }
  318.             else {
  319.                 final double dt      = kep.getDate().durationFrom(previousDate);
  320.                 final double keplerM = previousM + kep.getKeplerianMeanMotion() * dt;
  321.                 continuousPA   = MathUtils.normalizeAngle(kep.getPerigeeArgument(), previousPA);
  322.                 continuousRAAN = MathUtils.normalizeAngle(kep.getRightAscensionOfAscendingNode(), previousRAAN);
  323.                 continuousM    = MathUtils.normalizeAngle(kep.getMeanAnomaly(), keplerM);
  324.             }
  325.             previousDate = kep.getDate();
  326.             previousPA   = continuousPA;
  327.             previousRAAN = continuousRAAN;
  328.             previousM    = continuousM;
  329.             if (useDerivatives) {
  330.                 interpolator.addSamplePoint(kep.getDate().durationFrom(interpolationDate),
  331.                                             new double[] { kep.getA(),
  332.                                                            kep.getE(),
  333.                                                            kep.getI(),
  334.                                                            continuousPA,
  335.                                                            continuousRAAN,
  336.                                                            continuousM },
  337.                                             new double[] { kep.getADot(),
  338.                                                            kep.getEDot(),
  339.                                                            kep.getIDot(),
  340.                                                            kep.getPerigeeArgumentDot(),
  341.                                                            kep.getRightAscensionOfAscendingNodeDot(),
  342.                                                            kep.getMeanAnomalyDot() });
  343.             }
  344.             else {
  345.                 interpolator.addSamplePoint(kep.getDate().durationFrom(interpolationDate),
  346.                                             new double[] { kep.getA(),
  347.                                                            kep.getE(),
  348.                                                            kep.getI(),
  349.                                                            continuousPA,
  350.                                                            continuousRAAN,
  351.                                                            continuousM });
  352.             }
  353.         }

  354.         return interpolator.derivatives(0.0, 1);
  355.     }

  356.     /**
  357.      * Build interpolating functions for equinoctial orbit parameters.
  358.      *
  359.      * @param interpolationDate interpolation date
  360.      * @param orbits orbits sample
  361.      * @param useDerivatives flag defining if derivatives are available throughout the sample
  362.      *
  363.      * @return interpolating functions for equinoctial orbit parameters
  364.      */
  365.     private double[][] interpolateEquinoctial(final AbsoluteDate interpolationDate, final List<Orbit> orbits,
  366.                                               final boolean useDerivatives) {

  367.         // Set up an interpolator
  368.         final HermiteInterpolator interpolator = new HermiteInterpolator();

  369.         // Second pass to feed interpolator
  370.         AbsoluteDate previousDate = null;
  371.         double       previousLm   = Double.NaN;
  372.         for (final Orbit orbit : orbits) {
  373.             final EquinoctialOrbit equi = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(orbit);
  374.             final double           continuousLm;
  375.             if (previousDate == null) {
  376.                 continuousLm = equi.getLM();
  377.             }
  378.             else {
  379.                 final double dt       = equi.getDate().durationFrom(previousDate);
  380.                 final double keplerLm = previousLm + equi.getKeplerianMeanMotion() * dt;
  381.                 continuousLm = MathUtils.normalizeAngle(equi.getLM(), keplerLm);
  382.             }
  383.             previousDate = equi.getDate();
  384.             previousLm   = continuousLm;
  385.             if (useDerivatives) {
  386.                 interpolator.addSamplePoint(equi.getDate().durationFrom(interpolationDate),
  387.                                             new double[] { equi.getA(),
  388.                                                            equi.getEquinoctialEx(),
  389.                                                            equi.getEquinoctialEy(),
  390.                                                            equi.getHx(),
  391.                                                            equi.getHy(),
  392.                                                            continuousLm },
  393.                                             new double[] {
  394.                                                     equi.getADot(),
  395.                                                     equi.getEquinoctialExDot(),
  396.                                                     equi.getEquinoctialEyDot(),
  397.                                                     equi.getHxDot(),
  398.                                                     equi.getHyDot(),
  399.                                                     equi.getLMDot() });
  400.             }
  401.             else {
  402.                 interpolator.addSamplePoint(equi.getDate().durationFrom(interpolationDate),
  403.                                             new double[] { equi.getA(),
  404.                                                            equi.getEquinoctialEx(),
  405.                                                            equi.getEquinoctialEy(),
  406.                                                            equi.getHx(),
  407.                                                            equi.getHy(),
  408.                                                            continuousLm });
  409.             }
  410.         }

  411.         return interpolator.derivatives(0.0, 1);
  412.     }
  413. }