1   /* Copyright 2002-2022 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  
19  import java.io.Serializable;
20  import java.util.List;
21  import java.util.stream.Collectors;
22  
23  import org.hipparchus.exception.LocalizedCoreFormats;
24  import org.hipparchus.exception.MathIllegalArgumentException;
25  import org.hipparchus.util.FastMath;
26  import org.orekit.attitudes.Attitude;
27  import org.orekit.attitudes.AttitudeProvider;
28  import org.orekit.attitudes.InertialProvider;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.frames.Frame;
32  import org.orekit.orbits.Orbit;
33  import org.orekit.propagation.BoundedPropagator;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.utils.DoubleArrayDictionary;
37  import org.orekit.utils.ImmutableTimeStampedCache;
38  import org.orekit.utils.PVCoordinatesProvider;
39  import org.orekit.utils.TimeStampedPVCoordinates;
40  
41  /** This class is designed to accept and handle tabulated orbital entries.
42   * Tabulated entries are classified and then extrapolated in way to obtain
43   * continuous output, with accuracy and computation methods configured by the user.
44   *
45   * @author Fabien Maussion
46   * @author Véronique Pommier-Maurussane
47   * @author Luc Maisonobe
48   */
49  public class Ephemeris extends AbstractAnalyticalPropagator implements BoundedPropagator {
50  
51      /** Default extrapolation time threshold: 1ms.
52       * @since 9.0
53       **/
54      public static final double DEFAULT_EXTRAPOLATION_THRESHOLD_SEC = 1e-3;
55  
56       /** First date in range. */
57      private final AbsoluteDate minDate;
58  
59      /** Last date in range. */
60      private final AbsoluteDate maxDate;
61  
62      /** The extrapolation threshold beyond which the propagation will fail. **/
63      private final double extrapolationThreshold;
64  
65      /** Reference frame. */
66      private final Frame frame;
67  
68      /** Names of the additional states. */
69      private final String[] additional;
70  
71      /** Local PV Provider used for computing attitude. **/
72      private LocalPVProvider pvProvider;
73  
74      /** Thread-safe cache. */
75      private final transient ImmutableTimeStampedCache<SpacecraftState> cache;
76  
77      /** Constructor with tabulated states.
78       * <p>
79       * This constructor allows extrapolating outside of the states time span
80       * by up to the 1ms {@link #DEFAULT_EXTRAPOLATION_THRESHOLD_SEC default
81       * extrapolation threshold}.
82       * </p>
83       *
84       * @param states tabulates states
85       * @param interpolationPoints number of points to use in interpolation
86            * @exception MathIllegalArgumentException if the number of states is smaller than
87       * the number of points to use in interpolation
88       * @see #Ephemeris(List, int, double)
89       * @see #Ephemeris(List, int, double, AttitudeProvider)
90       */
91      public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints)
92          throws MathIllegalArgumentException {
93          this(states, interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
94      }
95  
96      /** Constructor with tabulated states.
97       *
98       * @param states tabulates states
99       * @param interpolationPoints number of points to use in interpolation
100      * @param extrapolationThreshold the largest time difference in seconds between
101      * the start or stop boundary of the ephemeris bounds to be doing extrapolation
102      * @exception MathIllegalArgumentException if the number of states is smaller than
103      * the number of points to use in interpolation
104      * @since 9.0
105      * @see #Ephemeris(List, int, double, AttitudeProvider)
106      */
107     public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints,
108                      final double extrapolationThreshold)
109         throws MathIllegalArgumentException {
110         this(states, interpolationPoints, extrapolationThreshold,
111                 // if states is empty an exception will be thrown in the other constructor
112                 states.isEmpty() ? null : InertialProvider.of(states.get(0).getFrame()));
113     }
114 
115     /** Constructor with tabulated states.
116      * @param states tabulates states
117      * @param interpolationPoints number of points to use in interpolation
118      * @param extrapolationThreshold the largest time difference in seconds between
119      * the start or stop boundary of the ephemeris bounds to be doing extrapolation
120      * @param attitudeProvider attitude law to use.
121      * @exception MathIllegalArgumentException if the number of states is smaller than
122      * the number of points to use in interpolation
123      * @since 10.1
124      */
125     public Ephemeris(final List<SpacecraftState> states,
126                      final int interpolationPoints,
127                      final double extrapolationThreshold,
128                      final AttitudeProvider attitudeProvider)
129         throws MathIllegalArgumentException {
130 
131         super(attitudeProvider);
132 
133         if (states.size() < interpolationPoints) {
134             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DIMENSION,
135                                                    states.size(), interpolationPoints);
136         }
137 
138         final SpacecraftState s0 = states.get(0);
139         minDate = s0.getDate();
140         maxDate = states.get(states.size() - 1).getDate();
141         frame = s0.getFrame();
142 
143         final List<DoubleArrayDictionary.Entry> as = s0.getAdditionalStatesValues().getData();
144         additional = new String[as.size()];
145         for (int i = 0; i < additional.length; ++i) {
146             additional[i] = as.get(i).getKey();
147         }
148 
149         // check all states handle the same additional states
150         for (final SpacecraftState state : states) {
151             s0.ensureCompatibleAdditionalStates(state);
152         }
153 
154         pvProvider = new LocalPVProvider(states, interpolationPoints, extrapolationThreshold);
155 
156         // user needs to explicitly set attitude provider if they want to use one
157         setAttitudeProvider(null);
158 
159         // set up cache
160         cache = new ImmutableTimeStampedCache<SpacecraftState>(interpolationPoints, states);
161 
162         this.extrapolationThreshold = extrapolationThreshold;
163     }
164 
165     /** Get the first date of the range.
166      * @return the first date of the range
167      */
168     public AbsoluteDate getMinDate() {
169         return minDate;
170     }
171 
172     /** Get the last date of the range.
173      * @return the last date of the range
174      */
175     public AbsoluteDate getMaxDate() {
176         return maxDate;
177     }
178 
179     /** Get the maximum timespan outside of the stored ephemeris that is allowed
180      * for extrapolation.
181      * @return the extrapolation threshold in seconds
182      */
183     public double getExtrapolationThreshold() {
184         return extrapolationThreshold;
185     }
186 
187     @Override
188     public Frame getFrame() {
189         return frame;
190     }
191 
192     @Override
193     /** {@inheritDoc} */
194     public SpacecraftState basicPropagate(final AbsoluteDate date) {
195         final SpacecraftState evaluatedState;
196 
197         final AbsoluteDate central;
198         if (date.compareTo(minDate) < 0 && FastMath.abs(date.durationFrom(minDate)) <= extrapolationThreshold) {
199             // avoid TimeStampedCacheException as we are still within the tolerance before minDate
200             central = minDate;
201         } else if (date.compareTo(maxDate) > 0 && FastMath.abs(date.durationFrom(maxDate)) <= extrapolationThreshold) {
202             // avoid TimeStampedCacheException as we are still within the tolerance after maxDate
203             central = maxDate;
204         } else {
205             central = date;
206         }
207         final List<SpacecraftState> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
208         evaluatedState = neighbors.get(0).interpolate(date, neighbors);
209 
210         final AttitudeProvider attitudeProvider = getAttitudeProvider();
211 
212         if (attitudeProvider == null) {
213             return evaluatedState;
214         } else {
215             pvProvider.setCurrentState(evaluatedState);
216             final Attitude calculatedAttitude = attitudeProvider.getAttitude(pvProvider, date,
217                                                                              evaluatedState.getFrame());
218 
219             // Verify if orbit is defined
220             if (evaluatedState.isOrbitDefined()) {
221                 return new SpacecraftState(evaluatedState.getOrbit(), calculatedAttitude, evaluatedState.getMass(),
222                                            evaluatedState.getAdditionalStatesValues(), evaluatedState.getAdditionalStatesDerivatives());
223             } else {
224                 return new SpacecraftState(evaluatedState.getAbsPVA(), calculatedAttitude, evaluatedState.getMass(),
225                                            evaluatedState.getAdditionalStatesValues(), evaluatedState.getAdditionalStatesDerivatives());
226             }
227 
228         }
229     }
230 
231     /** {@inheritDoc} */
232     protected Orbit propagateOrbit(final AbsoluteDate date) {
233         return basicPropagate(date).getOrbit();
234     }
235 
236     /** {@inheritDoc} */
237     protected double getMass(final AbsoluteDate date) {
238         return basicPropagate(date).getMass();
239     }
240 
241     /** {@inheritDoc} */
242     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
243         return propagate(date).getPVCoordinates(f);
244     }
245 
246     /** Try (and fail) to reset the initial state.
247      * <p>
248      * This method always throws an exception, as ephemerides cannot be reset.
249      * </p>
250      * @param state new initial state to consider
251      */
252     public void resetInitialState(final SpacecraftState state) {
253         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
254     }
255 
256     /** {@inheritDoc} */
257     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
258         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
259     }
260 
261     /** {@inheritDoc} */
262     public SpacecraftState getInitialState() {
263         return basicPropagate(getMinDate());
264     }
265 
266     /** {@inheritDoc} */
267     @Override
268     public boolean isAdditionalStateManaged(final String name) {
269 
270         // the additional state may be managed by a specific provider in the base class
271         if (super.isAdditionalStateManaged(name)) {
272             return true;
273         }
274 
275         // the additional state may be managed in the states sample
276         for (final String a : additional) {
277             if (a.equals(name)) {
278                 return true;
279             }
280         }
281 
282         return false;
283 
284     }
285 
286     /** {@inheritDoc} */
287     @Override
288     public String[] getManagedAdditionalStates() {
289         final String[] upperManaged = super.getManagedAdditionalStates();
290         final String[] managed = new String[upperManaged.length + additional.length];
291         System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
292         System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
293         return managed;
294     }
295 
296     /** Internal PVCoordinatesProvider for attitude computation. */
297     private static class LocalPVProvider implements PVCoordinatesProvider, Serializable {
298 
299         /** Serializable UID. */
300         private static final long serialVersionUID = 20160115L;
301 
302         /** Current state. */
303         private SpacecraftState currentState;
304 
305         /** List of spacecraft states. */
306         private List<SpacecraftState> states;
307 
308         /** Interpolation points number. */
309         private int interpolationPoints;
310 
311         /** Extrapolation threshold. */
312         private double extrapolationThreshold;
313 
314         /** Constructor.
315          * @param states list of spacecraft states
316          * @param interpolationPoints interpolation points number
317          * @param extrapolationThreshold extrapolation threshold value
318          */
319         LocalPVProvider(final List<SpacecraftState> states, final int interpolationPoints,
320                      final double extrapolationThreshold) {
321 
322             this.states = states;
323             this.interpolationPoints = interpolationPoints;
324             this.extrapolationThreshold = extrapolationThreshold;
325         }
326 
327         /** Get the current state.
328          * @return current state
329          */
330         public SpacecraftState getCurrentState() {
331             return currentState;
332         }
333 
334         /** Set the current state.
335          * @param state state to set
336          */
337         public void setCurrentState(final SpacecraftState state) {
338             this.currentState = state;
339         }
340 
341         /** {@inheritDoc} */
342         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
343             final double dt = getCurrentState().getDate().durationFrom(date);
344             final double closeEnoughTimeInSec = 1e-9;
345 
346             if (FastMath.abs(dt) > closeEnoughTimeInSec) {
347 
348                 // used in case of attitude transition, the attitude computed is not at the current date.
349                 final Ephemeris ephemeris = new Ephemeris(states, interpolationPoints, extrapolationThreshold, null);
350                 return ephemeris.getPVCoordinates(date, f);
351             }
352 
353             return currentState.getPVCoordinates(f);
354 
355         }
356 
357     }
358 
359 }