1   /* Copyright 2002-2026 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.utils;
18  
19  import org.hipparchus.analysis.interpolation.HermiteInterpolator;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.orekit.errors.OrekitInternalError;
22  import org.orekit.frames.Frame;
23  import org.orekit.time.AbsoluteDate;
24  import org.orekit.time.AbstractTimeInterpolator;
25  
26  import java.util.List;
27  
28  /**
29   * Class using a Hermite interpolator to interpolate absolute position-velocity-acceleration coordinates. It is assumed
30   * that provided samples have the same frame.
31   * <p>
32   * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
33   * points (about 10-20 points) in order to avoid <a href="https://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
34   * phenomenon</a> and numerical problems (including NaN appearing).
35   *
36   * @author Luc Maisonobe
37   * @author Vincent Cucchietti
38   * @see HermiteInterpolator
39   * @see AbsolutePVCoordinates
40   */
41  public class AbsolutePVCoordinatesHermiteInterpolator extends AbstractTimeInterpolator<AbsolutePVCoordinates> {
42  
43      /** Filter for derivatives from the sample to use in interpolation. */
44      private final CartesianDerivativesFilter filter;
45  
46      /** Output frame for the interpolated instance. */
47      private final Frame outputFrame;
48  
49      /**
50       * Constructor with :
51       * <ul>
52       *     <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li>
53       *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
54       *     <li>Use of position and two time derivatives during interpolation</li>
55       * </ul>
56       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
57       * points (about 10-20 points) in order to avoid <a href="https://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
58       * phenomenon</a> and numerical problems (including NaN appearing).
59       *
60       * @param outputFrame frame for the interpolated instance
61       */
62      public AbsolutePVCoordinatesHermiteInterpolator(final Frame outputFrame) {
63          this(DEFAULT_INTERPOLATION_POINTS, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputFrame,
64               CartesianDerivativesFilter.USE_PVA);
65      }
66  
67      /**
68       * Constructor with :
69       * <ul>
70       *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
71       *     <li>Use of position and two time derivatives during interpolation</li>
72       * </ul>
73       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
74       * points (about 10-20 points) in order to avoid <a href="https://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
75       * phenomenon</a> and numerical problems (including NaN appearing).
76       *
77       * @param interpolationPoints number of interpolation points
78       * @param outputFrame frame for the interpolated instance
79       */
80      public AbsolutePVCoordinatesHermiteInterpolator(final int interpolationPoints, final Frame outputFrame) {
81          this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputFrame, CartesianDerivativesFilter.USE_PVA);
82      }
83  
84      /**
85       * Constructor with default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s).
86       * <p>
87       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
88       * points (about 10-20 points) in order to avoid <a href="https://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
89       * phenomenon</a> and numerical problems (including NaN appearing).
90       *
91       * @param interpolationPoints number of interpolation points
92       * @param outputFrame frame for the interpolated instance
93       * @param filter filter for derivatives from the sample to use in interpolation
94       */
95      public AbsolutePVCoordinatesHermiteInterpolator(final int interpolationPoints, final Frame outputFrame,
96                                                      final CartesianDerivativesFilter filter) {
97          this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, outputFrame, filter);
98      }
99  
100     /**
101      * Constructor.
102      * <p>
103      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
104      * points (about 10-20 points) in order to avoid <a href="https://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
105      * phenomenon</a> and numerical problems (including NaN appearing).
106      *
107      * @param interpolationPoints number of interpolation points
108      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
109      * @param outputFrame frame for the interpolated instance
110      * @param filter filter for derivatives from the sample to use in interpolation
111      */
112     public AbsolutePVCoordinatesHermiteInterpolator(final int interpolationPoints, final double extrapolationThreshold,
113                                                     final Frame outputFrame, final CartesianDerivativesFilter filter) {
114         super(interpolationPoints, extrapolationThreshold);
115         this.outputFrame = outputFrame;
116         this.filter      = filter;
117     }
118 
119     /** Get the filter for derivatives from the sample to use in interpolation.
120      * @return filter for derivatives from the sample to use in interpolation.
121      */
122     public CartesianDerivativesFilter getFilter() {
123         return filter;
124     }
125 
126     /** Get output frame for the interpolated instance.
127      * @return output frame for the interpolated instance
128      */
129     public Frame getOutputFrame() {
130         return outputFrame;
131     }
132 
133     /**
134      * {@inheritDoc}
135      * <p>
136      * The interpolated instance is created by polynomial Hermite interpolation ensuring velocity remains the exact
137      * derivative of position.
138      * <p>
139      * Note that even if first time derivatives (velocities) from sample can be ignored, the interpolated instance always
140      * includes interpolated derivatives. This feature can be used explicitly to compute these derivatives when it would be
141      * too complex to compute them from an analytical formula: just compute a few sample points from the explicit formula and
142      * set the derivatives to zero in these sample points, then use interpolation to add derivatives consistent with the
143      * positions.
144      */
145     @Override
146     protected AbsolutePVCoordinates interpolate(final InterpolationData interpolationData) {
147 
148         // Get date
149         final AbsoluteDate date = interpolationData.getInterpolationDate();
150 
151         // Get sample
152         final List<AbsolutePVCoordinates> sample = interpolationData.getNeighborList();
153 
154         // Extract input frame from sample
155         final Frame inputFrame = sample.getFirst().getFrame();
156 
157         // Set up an interpolator taking derivatives into account
158         final HermiteInterpolator interpolator = new HermiteInterpolator();
159 
160         // Add sample points
161         switch (filter) {
162             case USE_P:
163                 // Populate sample with position data, ignoring velocity
164                 sample.forEach(pv -> {
165                     final Vector3D position = pv.getPosition();
166                     interpolator.addSamplePoint(pv.getDate().durationFrom(date),
167                                                 position.toArray());
168                 });
169                 break;
170             case USE_PV:
171                 // Populate sample with position and velocity data
172                 sample.forEach(pv -> {
173                     final Vector3D position = pv.getPosition();
174                     final Vector3D velocity = pv.getVelocity();
175                     interpolator.addSamplePoint(pv.getDate().durationFrom(date),
176                                                 position.toArray(), velocity.toArray());
177                 });
178                 break;
179             case USE_PVA:
180                 // Populate sample with position, velocity and acceleration data
181                 sample.forEach(pv -> {
182                     final Vector3D position     = pv.getPosition();
183                     final Vector3D velocity     = pv.getVelocity();
184                     final Vector3D acceleration = pv.getAcceleration();
185                     interpolator.addSamplePoint(pv.getDate().durationFrom(date),
186                                                 position.toArray(), velocity.toArray(), acceleration.toArray());
187                 });
188                 break;
189             default:
190                 // this should never happen
191                 throw new OrekitInternalError(null);
192         }
193 
194         // interpolate
195         final double[][] pva = interpolator.derivatives(0.0, 2);
196 
197         // build a new interpolated instance
198         final AbsolutePVCoordinates interpolated = new AbsolutePVCoordinates(inputFrame,
199                                                                              date,
200                                                                              new Vector3D(pva[0]),
201                                                                              new Vector3D(pva[1]),
202                                                                              new Vector3D(pva[2]));
203 
204         // return interpolated as is
205         if (inputFrame == outputFrame) {
206             return interpolated;
207         }
208 
209         // convert to output frame
210         return new AbsolutePVCoordinates(outputFrame, interpolated.getPVCoordinates(outputFrame));
211     }
212 }