1   /* Copyright 2002-2021 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.Set;
22  import java.util.stream.Collectors;
23  
24  import org.hipparchus.exception.LocalizedCoreFormats;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.util.FastMath;
27  import org.orekit.attitudes.Attitude;
28  import org.orekit.attitudes.AttitudeProvider;
29  import org.orekit.attitudes.InertialProvider;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.frames.Frame;
33  import org.orekit.orbits.Orbit;
34  import org.orekit.propagation.BoundedPropagator;
35  import org.orekit.propagation.SpacecraftState;
36  import org.orekit.time.AbsoluteDate;
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 Set<String> names0 = s0.getAdditionalStates().keySet();
144         additional = names0.toArray(new String[names0.size()]);
145 
146         // check all states handle the same additional states
147         for (final SpacecraftState state : states) {
148             s0.ensureCompatibleAdditionalStates(state);
149         }
150 
151         pvProvider = new LocalPVProvider(states, interpolationPoints, extrapolationThreshold);
152 
153         // user needs to explicitly set attitude provider if they want to use one
154         setAttitudeProvider(null);
155 
156         // set up cache
157         cache = new ImmutableTimeStampedCache<SpacecraftState>(interpolationPoints, states);
158 
159         this.extrapolationThreshold = extrapolationThreshold;
160     }
161 
162     /** Get the first date of the range.
163      * @return the first date of the range
164      */
165     public AbsoluteDate getMinDate() {
166         return minDate;
167     }
168 
169     /** Get the last date of the range.
170      * @return the last date of the range
171      */
172     public AbsoluteDate getMaxDate() {
173         return maxDate;
174     }
175 
176     /** Get the maximum timespan outside of the stored ephemeris that is allowed
177      * for extrapolation.
178      * @return the extrapolation threshold in seconds
179      */
180     public double getExtrapolationThreshold() {
181         return extrapolationThreshold;
182     }
183 
184     @Override
185     public Frame getFrame() {
186         return frame;
187     }
188 
189     @Override
190     /** {@inheritDoc} */
191     public SpacecraftState basicPropagate(final AbsoluteDate date) {
192         final SpacecraftState evaluatedState;
193 
194         final AbsoluteDate central;
195         if (date.compareTo(minDate) < 0 && FastMath.abs(date.durationFrom(minDate)) <= extrapolationThreshold) {
196             // avoid TimeStampedCacheException as we are still within the tolerance before minDate
197             central = minDate;
198         } else if (date.compareTo(maxDate) > 0 && FastMath.abs(date.durationFrom(maxDate)) <= extrapolationThreshold) {
199             // avoid TimeStampedCacheException as we are still within the tolerance after maxDate
200             central = maxDate;
201         } else {
202             central = date;
203         }
204         final List<SpacecraftState> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
205         evaluatedState = neighbors.get(0).interpolate(date, neighbors);
206 
207         final AttitudeProvider attitudeProvider = getAttitudeProvider();
208 
209         if (attitudeProvider == null) {
210             return evaluatedState;
211         } else {
212             pvProvider.setCurrentState(evaluatedState);
213             final Attitude calculatedAttitude = attitudeProvider.getAttitude(pvProvider, date,
214                                                                              evaluatedState.getFrame());
215 
216             // Verify if orbit is defined
217             if (evaluatedState.isOrbitDefined()) {
218                 return new SpacecraftState(evaluatedState.getOrbit(), calculatedAttitude,
219                                            evaluatedState.getMass(), evaluatedState.getAdditionalStates());
220             } else {
221                 return new SpacecraftState(evaluatedState.getAbsPVA(), calculatedAttitude,
222                                            evaluatedState.getMass(),  evaluatedState.getAdditionalStates());
223             }
224 
225         }
226     }
227 
228     /** {@inheritDoc} */
229     protected Orbit propagateOrbit(final AbsoluteDate date) {
230         return basicPropagate(date).getOrbit();
231     }
232 
233     /** {@inheritDoc} */
234     protected double getMass(final AbsoluteDate date) {
235         return basicPropagate(date).getMass();
236     }
237 
238     /** {@inheritDoc} */
239     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
240         return propagate(date).getPVCoordinates(f);
241     }
242 
243     /** Try (and fail) to reset the initial state.
244      * <p>
245      * This method always throws an exception, as ephemerides cannot be reset.
246      * </p>
247      * @param state new initial state to consider
248      */
249     public void resetInitialState(final SpacecraftState state) {
250         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
251     }
252 
253     /** {@inheritDoc} */
254     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
255         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
256     }
257 
258     /** {@inheritDoc} */
259     public SpacecraftState getInitialState() {
260         return basicPropagate(getMinDate());
261     }
262 
263     /** {@inheritDoc} */
264     @Override
265     public boolean isAdditionalStateManaged(final String name) {
266 
267         // the additional state may be managed by a specific provider in the base class
268         if (super.isAdditionalStateManaged(name)) {
269             return true;
270         }
271 
272         // the additional state may be managed in the states sample
273         for (final String a : additional) {
274             if (a.equals(name)) {
275                 return true;
276             }
277         }
278 
279         return false;
280 
281     }
282 
283     /** {@inheritDoc} */
284     @Override
285     public String[] getManagedAdditionalStates() {
286         final String[] upperManaged = super.getManagedAdditionalStates();
287         final String[] managed = new String[upperManaged.length + additional.length];
288         System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
289         System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
290         return managed;
291     }
292 
293     /** Internal PVCoordinatesProvider for attitude computation. */
294     private static class LocalPVProvider implements PVCoordinatesProvider, Serializable {
295 
296         /** Serializable UID. */
297         private static final long serialVersionUID = 20160115L;
298 
299         /** Current state. */
300         private SpacecraftState currentState;
301 
302         /** List of spacecraft states. */
303         private List<SpacecraftState> states;
304 
305         /** Interpolation points number. */
306         private int interpolationPoints;
307 
308         /** Extrapolation threshold. */
309         private double extrapolationThreshold;
310 
311         /** Constructor.
312          * @param states list of spacecraft states
313          * @param interpolationPoints interpolation points number
314          * @param extrapolationThreshold extrapolation threshold value
315          */
316         LocalPVProvider(final List<SpacecraftState> states, final int interpolationPoints,
317                      final double extrapolationThreshold) {
318 
319             this.states = states;
320             this.interpolationPoints = interpolationPoints;
321             this.extrapolationThreshold = extrapolationThreshold;
322         }
323 
324         /** Get the current state.
325          * @return current state
326          */
327         public SpacecraftState getCurrentState() {
328             return currentState;
329         }
330 
331         /** Set the current state.
332          * @param state state to set
333          */
334         public void setCurrentState(final SpacecraftState state) {
335             this.currentState = state;
336         }
337 
338         /** {@inheritDoc} */
339         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
340             final double dt = getCurrentState().getDate().durationFrom(date);
341             final double closeEnoughTimeInSec = 1e-9;
342 
343             if (FastMath.abs(dt) > closeEnoughTimeInSec) {
344 
345                 // used in case of attitude transition, the attitude computed is not at the current date.
346                 final Ephemeris ephemeris = new Ephemeris(states, interpolationPoints, extrapolationThreshold, null);
347                 return ephemeris.getPVCoordinates(date, f);
348             }
349 
350             return currentState.getPVCoordinates(f);
351 
352         }
353 
354     }
355 
356 }