AttitudesSequence.java
/* Copyright 2002-2016 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (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.attitudes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.handlers.EventHandler.Action;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedAngularCoordinates;
/** This classes manages a sequence of different attitude providers that are activated
* in turn according to switching events.
* <p>Only one attitude provider in the sequence is in an active state. When one of
* the switch event associated with the active provider occurs, the active provider becomes
* the one specified with the event. A simple example is a provider for the sun lighted part
* of the orbit and another provider for the eclipse time. When the sun lighted provider is active,
* the eclipse entry event is checked and when it occurs the eclipse provider is activated.
* When the eclipse provider is active, the eclipse exit event is checked and when it occurs
* the sun lighted provider is activated again. This sequence is a simple loop.</p>
* <p>An active attitude provider may have several switch events and next provider settings, leading
* to different activation patterns depending on which events are triggered first. An example
* of this feature is handling switches to safe mode if some contingency condition is met, in
* addition to the nominal switches that correspond to proper operations. Another example
* is handling of maneuver mode.<p>
* @author Luc Maisonobe
* @since 5.1
*/
public class AttitudesSequence implements AttitudeProvider {
/** Serializable UID. */
private static final long serialVersionUID = 20150603L;
/** Active provider. */
private AttitudeProvider active;
/** Attitude at preceding transition. */
private Attitude transitionPreceding;
/** Date of final switch to following attitude law. */
private AbsoluteDate transitionFollowing;
/** Transition filter. */
private AngularDerivativesFilter filter;
/** Propagation direction. */
private boolean forward;
/** Switching events list. */
private final List<Switch<?>> switches;
/** Constructor for an initially empty sequence.
*/
public AttitudesSequence() {
active = null;
switches = new ArrayList<Switch<?>>();
transitionPreceding = null;
transitionFollowing = null;
filter = null;
forward = true;
}
/** Reset the active provider.
* @param provider provider to activate
*/
public void resetActiveProvider(final AttitudeProvider provider) {
active = provider;
}
/** Register all wrapped switch events to the propagator.
* <p>
* This method must be called once before propagation, after the
* switching conditions have been set up by calls to {@link
* #addSwitchingCondition(AttitudeProvider, AttitudeProvider, EventDetector,
* boolean, boolean, double, AngularDerivativesFilter, SwitchHandler)
* addSwitchingCondition}.
* </p>
* @param propagator propagator that will handle the events
*/
public void registerSwitchEvents(final Propagator propagator) {
for (final Switch<?> s : switches) {
propagator.addEventDetector(s);
}
}
/** Add a switching condition between two attitude providers.
* <p>
* The {@code past} and {@code future} attitude providers are defined with regard
* to the natural flow of time. This means that if the propagation is forward, the
* propagator will switch from {@code past} provider to {@code future} provider at
* event occurrence, but if the propagation is backward, the propagator will switch
* from {@code future} provider to {@code past} provider at event occurrence. The
* transition between the two attitude laws is not instantaneous, the switch event
* defines the start of the transition (i.e. when leaving the {@code past} attitude
* law and entering the interpolated transition law). The end of the transition
* (i.e. when leaving the interpolating transition law and entering the {@code future}
* attitude law) occurs at switch time plus {@code transitionTime}.
* </p>
* <p>
* An attitude provider may have several different switch events associated to
* it. Depending on which event is triggered, the appropriate provider is
* switched to.
* </p>
* <p>
* The switch events specified here must <em>not</em> be registered to the
* propagator directly. The proper way to register these events is to
* call {@link #registerSwitchEvents(Propagator)} once after all switching
* conditions have been set up. The reason for this is that the events will
* be wrapped before being registered.
* </p>
* <p>
* If the underlying detector has an event handler associated to it, this handler
* will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
* EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
* of the event really triggering an attitude switch or not. As an example, if an
* eclipse detector is used to switch from day to night attitude mode when entering
* eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
* set to {@code true}. Then a handler set directly at eclipse detector level would
* be triggered at both eclipse entry and eclipse exit, but attitude switch would
* occur <em>only</em> at eclipse entry. Note that for the sake of symmetry, the
* transition start and end dates should match for both forward and backward propagation.
* This implies that for backward propagation, we have to compensate for the {@code
* transitionTime} when looking for the event. An unfortunate consequence is that the
* {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState, EventDetector, boolean)
* eventOccurred} method may appear to be called out of sync with respect to the
* propagation (it will be called when propagator reaches transition end, despite it
* refers to transition start, as per {@code transitionTime} compensation), and if the
* method returns {@link Action#STOP}, it will stop at the end of the
* transition instead of at the start. For these reasons, it is not recommended to
* set up an event handler for events that are used to switch attitude. If an event
* handler is needed for other purposes, a second handler should be registered to
* the propagator rather than relying on the side effects of attitude switches.
* </p>
* <p>
* The smoothness of the transition between past and future attitude laws can be tuned
* using the {@code transitionTime} and {@code transitionOrder} parameters. The {@code
* transitionTime} parameter specifies how much time is spent to switch from one law to
* the other law. It should be larger than the event {@link EventDetector#getThreshold()
* convergence threshold} in order to ensure attitude continuity. The {@code
* transitionOrder} parameter specifies the order of the attitude time derivatives that
* should match at the boundaries between past attitude law and transition law on one side,
* and between transition law and future law on the other side. Order 0 means only the
* rotation should be identical, order 1 means both rotation and rotation rate should be
* identical, order 2 means both rotation, rotation rate and rotation acceleration should
* be identical. During the transition, the attitude law is computed by interpolating
* between past attitude law at switch time and future attitude law at current intermediate
* time.
* </p>
* @param past attitude provider applicable for times in the switch event occurrence past
* @param future attitude provider applicable for times in the switch event occurrence future
* @param switchEvent event triggering the attitude providers switch
* @param switchOnIncrease if true, switch is triggered on increasing event
* @param switchOnDecrease if true, switch is triggered on decreasing event
* @param transitionTime duration of the transition between the past and future attitude laws
* @param transitionFilter order at which the transition law time derivatives
* should match past and future attitude laws
* @param handler handler to call for notifying when switch occurs (may be null)
* @param <T> class type for the switch event
* @exception OrekitException if transition time is shorter than event convergence threshold
* @since 7.1
*/
public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
final AttitudeProvider future,
final T switchEvent,
final boolean switchOnIncrease,
final boolean switchOnDecrease,
final double transitionTime,
final AngularDerivativesFilter transitionFilter,
final SwitchHandler handler)
throws OrekitException {
// safety check, for ensuring attitude continuity
if (transitionTime < switchEvent.getThreshold()) {
throw new OrekitException(OrekitMessages.TOO_SHORT_TRANSITION_TIME_FOR_ATTITUDES_SWITCH,
transitionTime, switchEvent.getThreshold());
}
// if it is the first switching condition, assume first active law is the past one
if (active == null) {
active = past;
}
// add the switching condition
switches.add(new Switch<T>(switchEvent, switchOnIncrease, switchOnDecrease,
past, future, transitionTime, transitionFilter, handler));
}
/** {@inheritDoc} */
public Attitude getAttitude(final PVCoordinatesProvider pvProv,
final AbsoluteDate date, final Frame frame)
throws OrekitException {
if (transitionPreceding != null) {
final double dtPreceding = date.durationFrom(transitionPreceding.getDate());
final double dtFollowing = date.durationFrom(transitionFollowing);
if (( forward && dtPreceding > 0 && dtFollowing < 0) ||
(!forward && dtPreceding < 0 && dtFollowing > 0)) {
// the date occurs during the transition
// interpolate between the two boundary attitudes
final TimeStampedAngularCoordinates preceding =
transitionPreceding.withReferenceFrame(frame).getOrientation();
final TimeStampedAngularCoordinates following =
active.getAttitude(pvProv, transitionFollowing, frame).getOrientation();
final TimeStampedAngularCoordinates interpolated =
TimeStampedAngularCoordinates.interpolate(date, filter,
Arrays.asList(preceding, following));
return new Attitude(frame, interpolated);
}
}
// the date is in the stabilized active attitude law
return active.getAttitude(pvProv, date, frame);
}
/** Switch specification.
* @param <T> class type for the generic version
*/
private class Switch<T extends EventDetector> implements EventDetector {
/** Serializable UID. */
private static final long serialVersionUID = 20150604L;
/** Event. */
private final T event;
/** Event direction triggering the switch. */
private final boolean switchOnIncrease;
/** Event direction triggering the switch. */
private final boolean switchOnDecrease;
/** Attitude provider applicable for times in the switch event occurrence past. */
private final AttitudeProvider past;
/** Attitude provider applicable for times in the switch event occurrence future. */
private final AttitudeProvider future;
/** Duration of the transition between the past and future attitude laws. */
private final double transitionTime;
/** Order at which the transition law time derivatives should match past and future attitude laws. */
private final AngularDerivativesFilter transitionFilter;
/** Handler to call for notifying when switch occurs (may be null). */
private final SwitchHandler switchHandler;
/** Simple constructor.
* @param event event
* @param switchOnIncrease if true, switch is triggered on increasing event
* @param switchOnDecrease if true, switch is triggered on decreasing event
* otherwise switch is triggered on decreasing event
* @param past attitude provider applicable for times in the switch event occurrence past
* @param future attitude provider applicable for times in the switch event occurrence future
* @param transitionTime duration of the transition between the past and future attitude laws
* @param transitionFilter order at which the transition law time derivatives
* should match past and future attitude laws
* @param switchHandler handler to call for notifying when switch occurs (may be null)
*/
Switch(final T event,
final boolean switchOnIncrease, final boolean switchOnDecrease,
final AttitudeProvider past, final AttitudeProvider future,
final double transitionTime, final AngularDerivativesFilter transitionFilter,
final SwitchHandler switchHandler) {
this.event = event;
this.switchOnIncrease = switchOnIncrease;
this.switchOnDecrease = switchOnDecrease;
this.past = past;
this.future = future;
this.transitionTime = transitionTime;
this.transitionFilter = transitionFilter;
this.switchHandler = switchHandler;
}
/** {@inheritDoc} */
@Override
public double getThreshold() {
return event.getThreshold();
}
/** {@inheritDoc} */
@Override
public double getMaxCheckInterval() {
return event.getMaxCheckInterval();
}
/** {@inheritDoc} */
@Override
public int getMaxIterationCount() {
return event.getMaxIterationCount();
}
/** {@inheritDoc} */
public void init(final SpacecraftState s0, final AbsoluteDate t) {
// reset the transition parameters (this will be done once for each switch,
// despite doing it only once would have sufficient; its not really a problem)
transitionPreceding = null;
transitionFollowing = null;
forward = t.durationFrom(s0.getDate()) >= 0.0;
// initialize the underlying event
event.init(s0, t);
}
/** {@inheritDoc} */
public double g(final SpacecraftState s)
throws OrekitException {
return event.g(forward ? s : s.shiftedBy(-transitionTime));
}
/** {@inheritDoc} */
public Action eventOccurred(final SpacecraftState s, final boolean increasing)
throws OrekitException {
if (active == (forward ? past : future) &&
((increasing && switchOnIncrease) || (!increasing && switchOnDecrease))) {
if (forward) {
// prepare transition
transitionPreceding = s.getAttitude();
transitionFollowing = transitionPreceding.getDate().shiftedBy(transitionTime);
filter = transitionFilter;
// switch to future attitude provider
active = future;
if (switchHandler != null) {
switchHandler.switchOccurred(past, future, s);
}
return event.eventOccurred(s, increasing);
} else {
// prepare transition
transitionPreceding = s.getAttitude();
transitionFollowing = transitionPreceding.getDate().shiftedBy(-transitionTime);
filter = transitionFilter;
// estimate state at transition start, according to the past attitude law
final Orbit sOrbit = s.getOrbit().shiftedBy(-transitionTime);
final Attitude sAttitude = past.getAttitude(sOrbit, sOrbit.getDate(), sOrbit.getFrame());
SpacecraftState sState = new SpacecraftState(sOrbit, sAttitude, s.getMass());
for (final Map.Entry<String, double[]> entry : s.getAdditionalStates().entrySet()) {
sState = sState.addAdditionalState(entry.getKey(), entry.getValue());
}
// switch to past attitude provider
active = past;
if (switchHandler != null) {
switchHandler.switchOccurred(future, past, sState);
}
return event.eventOccurred(sState, increasing);
}
} else {
// trigger the underlying event despite no attitude switch occurred
return event.eventOccurred(s, increasing);
}
}
/** {@inheritDoc} */
@Override
public SpacecraftState resetState(final SpacecraftState oldState)
throws OrekitException {
// delegate to underlying event
return event.resetState(oldState);
}
}
/** Interface for attitude switch notifications.
* <p>
* This interface is intended to be implemented by users who want to be
* notified when an attitude switch occurs.
* </p>
* @since 7.1
*/
public interface SwitchHandler {
/** Method called when attitude is switched from one law to another law.
* @param preceding attitude law used preceding the switch (i.e. in the past
* of the switch event for a forward propagation, or in the future
* of the switch event for a backward propagation)
* @param following attitude law used following the switch (i.e. in the future
* of the switch event for a forward propagation, or in the past
* of the switch event for a backward propagation)
* @param state state at switch time (with attitude computed using the {@code preceding} law)
* @exception OrekitException if some unexpected condition occurs
*/
void switchOccurred(AttitudeProvider preceding, AttitudeProvider following, SpacecraftState state)
throws OrekitException;
}
}