Ephemeris.java
/* Copyright 2002-2022 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.propagation.analytical;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.linear.RealMatrix;
import org.hipparchus.util.FastMath;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.InertialProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.AbstractMatricesHarvester;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.DoubleArrayDictionary;
import org.orekit.utils.ImmutableTimeStampedCache;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedPVCoordinates;
/** This class is designed to accept and handle tabulated orbital entries.
* Tabulated entries are classified and then extrapolated in way to obtain
* continuous output, with accuracy and computation methods configured by the user.
*
* @author Fabien Maussion
* @author Véronique Pommier-Maurussane
* @author Luc Maisonobe
*/
public class Ephemeris extends AbstractAnalyticalPropagator implements BoundedPropagator {
/** Default extrapolation time threshold: 1ms.
* @since 9.0
**/
public static final double DEFAULT_EXTRAPOLATION_THRESHOLD_SEC = 1e-3;
/** First date in range. */
private final AbsoluteDate minDate;
/** Last date in range. */
private final AbsoluteDate maxDate;
/** The extrapolation threshold beyond which the propagation will fail. **/
private final double extrapolationThreshold;
/** Reference frame. */
private final Frame frame;
/** Names of the additional states. */
private final String[] additional;
/** Local PV Provider used for computing attitude. **/
private LocalPVProvider pvProvider;
/** Thread-safe cache. */
private final transient ImmutableTimeStampedCache<SpacecraftState> cache;
/** Constructor with tabulated states.
* <p>
* This constructor allows extrapolating outside of the states time span
* by up to the 1ms {@link #DEFAULT_EXTRAPOLATION_THRESHOLD_SEC default
* extrapolation threshold}.
* </p>
*
* @param states tabulates states
* @param interpolationPoints number of points to use in interpolation
* @exception MathIllegalArgumentException if the number of states is smaller than
* the number of points to use in interpolation
* @see #Ephemeris(List, int, double)
* @see #Ephemeris(List, int, double, AttitudeProvider)
*/
public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints)
throws MathIllegalArgumentException {
this(states, interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
}
/** Constructor with tabulated states.
*
* @param states tabulates states
* @param interpolationPoints number of points to use in interpolation
* @param extrapolationThreshold the largest time difference in seconds between
* the start or stop boundary of the ephemeris bounds to be doing extrapolation
* @exception MathIllegalArgumentException if the number of states is smaller than
* the number of points to use in interpolation
* @since 9.0
* @see #Ephemeris(List, int, double, AttitudeProvider)
*/
public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints,
final double extrapolationThreshold)
throws MathIllegalArgumentException {
this(states, interpolationPoints, extrapolationThreshold,
// if states is empty an exception will be thrown in the other constructor
states.isEmpty() ? null : InertialProvider.of(states.get(0).getFrame()));
}
/** Constructor with tabulated states.
* @param states tabulates states
* @param interpolationPoints number of points to use in interpolation
* @param extrapolationThreshold the largest time difference in seconds between
* the start or stop boundary of the ephemeris bounds to be doing extrapolation
* @param attitudeProvider attitude law to use.
* @exception MathIllegalArgumentException if the number of states is smaller than
* the number of points to use in interpolation
* @since 10.1
*/
public Ephemeris(final List<SpacecraftState> states,
final int interpolationPoints,
final double extrapolationThreshold,
final AttitudeProvider attitudeProvider)
throws MathIllegalArgumentException {
super(attitudeProvider);
if (states.size() < interpolationPoints) {
throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DIMENSION,
states.size(), interpolationPoints);
}
final SpacecraftState s0 = states.get(0);
minDate = s0.getDate();
maxDate = states.get(states.size() - 1).getDate();
frame = s0.getFrame();
final List<DoubleArrayDictionary.Entry> as = s0.getAdditionalStatesValues().getData();
additional = new String[as.size()];
for (int i = 0; i < additional.length; ++i) {
additional[i] = as.get(i).getKey();
}
// check all states handle the same additional states
for (final SpacecraftState state : states) {
s0.ensureCompatibleAdditionalStates(state);
}
pvProvider = new LocalPVProvider(states, interpolationPoints, extrapolationThreshold);
// user needs to explicitly set attitude provider if they want to use one
setAttitudeProvider(null);
// set up cache
cache = new ImmutableTimeStampedCache<SpacecraftState>(interpolationPoints, states);
this.extrapolationThreshold = extrapolationThreshold;
}
/** Get the first date of the range.
* @return the first date of the range
*/
public AbsoluteDate getMinDate() {
return minDate;
}
/** Get the last date of the range.
* @return the last date of the range
*/
public AbsoluteDate getMaxDate() {
return maxDate;
}
/** Get the maximum timespan outside of the stored ephemeris that is allowed
* for extrapolation.
* @return the extrapolation threshold in seconds
*/
public double getExtrapolationThreshold() {
return extrapolationThreshold;
}
@Override
public Frame getFrame() {
return frame;
}
@Override
/** {@inheritDoc} */
public SpacecraftState basicPropagate(final AbsoluteDate date) {
final SpacecraftState evaluatedState;
final AbsoluteDate central;
if (date.compareTo(minDate) < 0 && FastMath.abs(date.durationFrom(minDate)) <= extrapolationThreshold) {
// avoid TimeStampedCacheException as we are still within the tolerance before minDate
central = minDate;
} else if (date.compareTo(maxDate) > 0 && FastMath.abs(date.durationFrom(maxDate)) <= extrapolationThreshold) {
// avoid TimeStampedCacheException as we are still within the tolerance after maxDate
central = maxDate;
} else {
central = date;
}
final List<SpacecraftState> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
evaluatedState = neighbors.get(0).interpolate(date, neighbors);
final AttitudeProvider attitudeProvider = getAttitudeProvider();
if (attitudeProvider == null) {
return evaluatedState;
} else {
pvProvider.setCurrentState(evaluatedState);
final Attitude calculatedAttitude = attitudeProvider.getAttitude(pvProvider, date,
evaluatedState.getFrame());
// Verify if orbit is defined
if (evaluatedState.isOrbitDefined()) {
return new SpacecraftState(evaluatedState.getOrbit(), calculatedAttitude, evaluatedState.getMass(),
evaluatedState.getAdditionalStatesValues(), evaluatedState.getAdditionalStatesDerivatives());
} else {
return new SpacecraftState(evaluatedState.getAbsPVA(), calculatedAttitude, evaluatedState.getMass(),
evaluatedState.getAdditionalStatesValues(), evaluatedState.getAdditionalStatesDerivatives());
}
}
}
/** {@inheritDoc} */
protected Orbit propagateOrbit(final AbsoluteDate date) {
return basicPropagate(date).getOrbit();
}
/** {@inheritDoc} */
protected double getMass(final AbsoluteDate date) {
return basicPropagate(date).getMass();
}
/** {@inheritDoc} */
public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
return propagate(date).getPVCoordinates(f);
}
/** Try (and fail) to reset the initial state.
* <p>
* This method always throws an exception, as ephemerides cannot be reset.
* </p>
* @param state new initial state to consider
*/
public void resetInitialState(final SpacecraftState state) {
throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
}
/** {@inheritDoc} */
protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
}
/** {@inheritDoc} */
public SpacecraftState getInitialState() {
return basicPropagate(getMinDate());
}
/** {@inheritDoc} */
@Override
public boolean isAdditionalStateManaged(final String name) {
// the additional state may be managed by a specific provider in the base class
if (super.isAdditionalStateManaged(name)) {
return true;
}
// the additional state may be managed in the states sample
for (final String a : additional) {
if (a.equals(name)) {
return true;
}
}
return false;
}
/** {@inheritDoc} */
@Override
public String[] getManagedAdditionalStates() {
final String[] upperManaged = super.getManagedAdditionalStates();
final String[] managed = new String[upperManaged.length + additional.length];
System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
return managed;
}
/** {@inheritDoc} */
@Override
protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
final DoubleArrayDictionary initialJacobianColumns) {
// In order to not throw an Orekit exception during ephemeris based orbit determination
// The default behavior of the method is overrided to return a null parameter
return null;
}
/** Internal PVCoordinatesProvider for attitude computation. */
private static class LocalPVProvider implements PVCoordinatesProvider, Serializable {
/** Serializable UID. */
private static final long serialVersionUID = 20160115L;
/** Current state. */
private SpacecraftState currentState;
/** List of spacecraft states. */
private List<SpacecraftState> states;
/** Interpolation points number. */
private int interpolationPoints;
/** Extrapolation threshold. */
private double extrapolationThreshold;
/** Constructor.
* @param states list of spacecraft states
* @param interpolationPoints interpolation points number
* @param extrapolationThreshold extrapolation threshold value
*/
LocalPVProvider(final List<SpacecraftState> states, final int interpolationPoints,
final double extrapolationThreshold) {
this.states = states;
this.interpolationPoints = interpolationPoints;
this.extrapolationThreshold = extrapolationThreshold;
}
/** Get the current state.
* @return current state
*/
public SpacecraftState getCurrentState() {
return currentState;
}
/** Set the current state.
* @param state state to set
*/
public void setCurrentState(final SpacecraftState state) {
this.currentState = state;
}
/** {@inheritDoc} */
public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame f) {
final double dt = getCurrentState().getDate().durationFrom(date);
final double closeEnoughTimeInSec = 1e-9;
if (FastMath.abs(dt) > closeEnoughTimeInSec) {
// used in case of attitude transition, the attitude computed is not at the current date.
final Ephemeris ephemeris = new Ephemeris(states, interpolationPoints, extrapolationThreshold, null);
return ephemeris.getPVCoordinates(date, f);
}
return currentState.getPVCoordinates(f);
}
}
}