AttitudesSequence.java

  1. /* Copyright 2002-2024 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.attitudes;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.List;

  21. import org.hipparchus.CalculusFieldElement;
  22. import org.hipparchus.Field;
  23. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  24. import org.hipparchus.geometry.euclidean.threed.Rotation;
  25. import org.hipparchus.ode.events.Action;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.frames.Frame;
  29. import org.orekit.orbits.Orbit;
  30. import org.orekit.propagation.FieldPropagator;
  31. import org.orekit.propagation.FieldSpacecraftState;
  32. import org.orekit.propagation.Propagator;
  33. import org.orekit.propagation.SpacecraftState;
  34. import org.orekit.propagation.events.AdaptableInterval;
  35. import org.orekit.propagation.events.EventDetector;
  36. import org.orekit.propagation.events.FieldAdaptableInterval;
  37. import org.orekit.propagation.events.FieldEventDetector;
  38. import org.orekit.propagation.events.handlers.EventHandler;
  39. import org.orekit.propagation.events.handlers.FieldEventHandler;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.time.FieldAbsoluteDate;
  42. import org.orekit.time.FieldTimeInterpolator;
  43. import org.orekit.time.TimeInterpolator;
  44. import org.orekit.utils.AngularDerivativesFilter;
  45. import org.orekit.utils.DoubleArrayDictionary;
  46. import org.orekit.utils.FieldPVCoordinatesProvider;
  47. import org.orekit.utils.PVCoordinatesProvider;
  48. import org.orekit.utils.TimeSpanMap;
  49. import org.orekit.utils.TimeStampedAngularCoordinates;
  50. import org.orekit.utils.TimeStampedAngularCoordinatesHermiteInterpolator;
  51. import org.orekit.utils.TimeStampedFieldAngularCoordinates;
  52. import org.orekit.utils.TimeStampedFieldAngularCoordinatesHermiteInterpolator;

  53. /** This classes manages a sequence of different attitude providers that are activated
  54.  * in turn according to switching events.
  55.  * <p>Only one attitude provider in the sequence is in an active state. When one of
  56.  * the switch event associated with the active provider occurs, the active provider becomes
  57.  * the one specified with the event. A simple example is a provider for the sun lighted part
  58.  * of the orbit and another provider for the eclipse time. When the sun lighted provider is active,
  59.  * the eclipse entry event is checked and when it occurs the eclipse provider is activated.
  60.  * When the eclipse provider is active, the eclipse exit event is checked and when it occurs
  61.  * the sun lighted provider is activated again. This sequence is a simple loop.</p>
  62.  * <p>An active attitude provider may have several switch events and next provider settings, leading
  63.  * to different activation patterns depending on which events are triggered first. An example
  64.  * of this feature is handling switches to safe mode if some contingency condition is met, in
  65.  * addition to the nominal switches that correspond to proper operations. Another example
  66.  * is handling of maneuver mode.
  67.  * <p>
  68.  * Note that this attitude provider is stateful, it keeps in memory the sequence of active
  69.  * underlying providers with their switch dates and the transitions from one provider to
  70.  * the other. This implies that this provider should <em>not</em> be shared among different
  71.  * propagators at the same time, each propagator should use its own instance of this provider.
  72.  * <p>
  73.  * The sequence kept in memory is reset when {@link #resetActiveProvider(AttitudeProvider)}
  74.  * is called, and only the specify provider is kept. The sequence is also partially
  75.  * reset each time a propagation starts. If a new propagation is started after a first
  76.  * propagation has been run, all the already computed switches that occur after propagation
  77.  * start for forward propagation or before propagation start for backward propagation will
  78.  * be erased. New switches will be computed and applied properly according to the new
  79.  * propagation settings. The already computed switches that are not in covered are kept
  80.  * in memory. This implies that if a propagation is interrupted and restarted in the
  81.  * same direction, then attitude switches will remain in place, ensuring that even if the
  82.  * interruption occurred in the middle of an attitude transition the second propagation will
  83.  * properly complete the transition that was started by the first propagator.
  84.  * </p>
  85.  * @author Luc Maisonobe
  86.  * @since 5.1
  87.  */
  88. public class AttitudesSequence implements AttitudeProvider {

  89.     /** Providers that have been activated. */
  90.     private transient TimeSpanMap<AttitudeProvider> activated;

  91.     /** Switching events list. */
  92.     private final List<Switch> switches;

  93.     /** Constructor for an initially empty sequence.
  94.      */
  95.     public AttitudesSequence() {
  96.         activated = null;
  97.         switches  = new ArrayList<>();
  98.     }

  99.     /** Reset the active provider.
  100.      * <p>
  101.      * Calling this method clears all already seen switch history,
  102.      * so it should <em>not</em> be used during the propagation itself,
  103.      * it is intended to be used only at start
  104.      * </p>
  105.      * @param provider provider to activate
  106.      */
  107.     public void resetActiveProvider(final AttitudeProvider provider) {
  108.         activated = new TimeSpanMap<>(provider);
  109.     }

  110.     /** Register all wrapped switch events to the propagator.
  111.      * <p>
  112.      * This method must be called once before propagation, after the
  113.      * switching conditions have been set up by calls to {@link
  114.      * #addSwitchingCondition(AttitudeProvider, AttitudeProvider, EventDetector,
  115.      * boolean, boolean, double, AngularDerivativesFilter, SwitchHandler)
  116.      * addSwitchingCondition}.
  117.      * </p>
  118.      * @param propagator propagator that will handle the events
  119.      */
  120.     public void registerSwitchEvents(final Propagator propagator) {
  121.         for (final Switch s : switches) {
  122.             propagator.addEventDetector(s);
  123.         }
  124.     }

  125.     /** Register all wrapped switch events to the propagator.
  126.      * <p>
  127.      * This method must be called once before propagation, after the
  128.      * switching conditions have been set up by calls to {@link
  129.      * #addSwitchingCondition(AttitudeProvider, AttitudeProvider, EventDetector,
  130.      * boolean, boolean, double, AngularDerivativesFilter, SwitchHandler)
  131.      * addSwitchingCondition}.
  132.      * </p>
  133.      * @param field field to which the elements belong
  134.      * @param propagator propagator that will handle the events
  135.      * @param <T> type of the field elements
  136.      */
  137.     public <T extends CalculusFieldElement<T>> void registerSwitchEvents(final Field<T> field, final FieldPropagator<T> propagator) {
  138.         for (final Switch sw : switches) {
  139.             propagator.addEventDetector(new FieldEventDetector<T>() {

  140.                 /** {@inheritDoc} */
  141.                 @Override
  142.                 public void init(final FieldSpacecraftState<T> s0,
  143.                                  final FieldAbsoluteDate<T> t) {
  144.                     sw.init(s0.toSpacecraftState(), t.toAbsoluteDate());
  145.                 }

  146.                 /** {@inheritDoc} */
  147.                 @Override
  148.                 public T g(final FieldSpacecraftState<T> s) {
  149.                     return field.getZero().newInstance(sw.g(s.toSpacecraftState()));
  150.                 }

  151.                 /** {@inheritDoc} */
  152.                 @Override
  153.                 public T getThreshold() {
  154.                     return field.getZero().newInstance(sw.getThreshold());
  155.                 }

  156.                 /** {@inheritDoc} */
  157.                 @Override
  158.                 public FieldAdaptableInterval<T> getMaxCheckInterval() {
  159.                     return s -> sw.getMaxCheckInterval().currentInterval(s.toSpacecraftState());
  160.                 }

  161.                 /** {@inheritDoc} */
  162.                 @Override
  163.                 public int getMaxIterationCount() {
  164.                     return sw.getMaxIterationCount();
  165.                 }

  166.                 /** {@inheritDoc} */
  167.                 @Override
  168.                 public FieldEventHandler<T> getHandler() {
  169.                     return new FieldEventHandler<T>() {
  170.                         /** {@inheritDoc} */
  171.                         @Override
  172.                         public Action eventOccurred(final FieldSpacecraftState<T> s,
  173.                                                     final FieldEventDetector<T> detector,
  174.                                                     final boolean increasing) {
  175.                             return sw.eventOccurred(s.toSpacecraftState(), sw, increasing);
  176.                         }

  177.                         /** {@inheritDoc} */
  178.                         @Override
  179.                         public FieldSpacecraftState<T> resetState(final FieldEventDetector<T> detector,
  180.                                                                   final FieldSpacecraftState<T> oldState) {
  181.                             return new FieldSpacecraftState<>(field, sw.resetState(sw, oldState.toSpacecraftState()));
  182.                         }
  183.                     };
  184.                 }

  185.             });
  186.         }
  187.     }

  188.     /** Add a switching condition between two attitude providers.
  189.      * <p>
  190.      * The {@code past} and {@code future} attitude providers are defined with regard
  191.      * to the natural flow of time. This means that if the propagation is forward, the
  192.      * propagator will switch from {@code past} provider to {@code future} provider at
  193.      * event occurrence, but if the propagation is backward, the propagator will switch
  194.      * from {@code future} provider to {@code past} provider at event occurrence. The
  195.      * transition between the two attitude laws is not instantaneous, the switch event
  196.      * defines the start of the transition (i.e. when leaving the {@code past} attitude
  197.      * law and entering the interpolated transition law). The end of the transition
  198.      * (i.e. when leaving the interpolating transition law and entering the {@code future}
  199.      * attitude law) occurs at switch time plus {@code transitionTime}.
  200.      * </p>
  201.      * <p>
  202.      * An attitude provider may have several different switch events associated to
  203.      * it. Depending on which event is triggered, the appropriate provider is
  204.      * switched to.
  205.      * </p>
  206.      * <p>
  207.      * The switch events specified here must <em>not</em> be registered to the
  208.      * propagator directly. The proper way to register these events is to
  209.      * call {@link #registerSwitchEvents(Propagator)} once after all switching
  210.      * conditions have been set up. The reason for this is that the events will
  211.      * be wrapped before being registered.
  212.      * </p>
  213.      * <p>
  214.      * If the underlying detector has an event handler associated to it, this handler
  215.      * will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
  216.      * EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
  217.      * of the event really triggering an attitude switch or not. As an example, if an
  218.      * eclipse detector is used to switch from day to night attitude mode when entering
  219.      * eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
  220.      * set to {@code true}. Then a handler set directly at eclipse detector level would
  221.      * be triggered at both eclipse entry and eclipse exit, but attitude switch would
  222.      * occur <em>only</em> at eclipse entry. Note that for the sake of symmetry, the
  223.      * transition start and end dates should match for both forward and backward propagation.
  224.      * This implies that for backward propagation, we have to compensate for the {@code
  225.      * transitionTime} when looking for the event. An unfortunate consequence is that the
  226.      * {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState, EventDetector, boolean)
  227.      * eventOccurred} method may appear to be called out of sync with respect to the
  228.      * propagation (it will be called when propagator reaches transition end, despite it
  229.      * refers to transition start, as per {@code transitionTime} compensation), and if the
  230.      * method returns {@link Action#STOP}, it will stop at the end of the
  231.      * transition instead of at the start. For these reasons, it is not recommended to
  232.      * set up an event handler for events that are used to switch attitude. If an event
  233.      * handler is needed for other purposes, a second handler should be registered to
  234.      * the propagator rather than relying on the side effects of attitude switches.
  235.      * </p>
  236.      * <p>
  237.      * The smoothness of the transition between past and future attitude laws can be tuned
  238.      * using the {@code transitionTime} and {@code transitionFilter} parameters. The {@code
  239.      * transitionTime} parameter specifies how much time is spent to switch from one law to
  240.      * the other law. It should be larger than the event {@link EventDetector#getThreshold()
  241.      * convergence threshold} in order to ensure attitude continuity. The {@code
  242.      * transitionFilter} parameter specifies the attitude time derivatives that should match
  243.      * at the boundaries between past attitude law and transition law on one side, and
  244.      * between transition law and future law on the other side.
  245.      * {@link AngularDerivativesFilter#USE_R} means only the rotation should be identical,
  246.      * {@link AngularDerivativesFilter#USE_RR} means both rotation and rotation rate
  247.      * should be identical, {@link AngularDerivativesFilter#USE_RRA} means both rotation,
  248.      * rotation rate and rotation acceleration should be identical. During the transition,
  249.      * the attitude law is computed by interpolating between past attitude law at switch time
  250.      * and future attitude law at current intermediate time.
  251.      * </p>
  252.      * @param past attitude provider applicable for times in the switch event occurrence past
  253.      * @param future attitude provider applicable for times in the switch event occurrence future
  254.      * @param switchEvent event triggering the attitude providers switch
  255.      * @param switchOnIncrease if true, switch is triggered on increasing event
  256.      * @param switchOnDecrease if true, switch is triggered on decreasing event
  257.      * @param transitionTime duration of the transition between the past and future attitude laws
  258.      * @param transitionFilter specification of transition law time derivatives that
  259.      * should match past and future attitude laws
  260.      * @param handler handler to call for notifying when switch occurs (may be null)
  261.      * @param <T> class type for the switch event
  262.      * @since 7.1
  263.      */
  264.     public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
  265.                                                                 final AttitudeProvider future,
  266.                                                                 final T switchEvent,
  267.                                                                 final boolean switchOnIncrease,
  268.                                                                 final boolean switchOnDecrease,
  269.                                                                 final double transitionTime,
  270.                                                                 final AngularDerivativesFilter transitionFilter,
  271.                                                                 final SwitchHandler handler) {

  272.         // safety check, for ensuring attitude continuity
  273.         if (transitionTime < switchEvent.getThreshold()) {
  274.             throw new OrekitException(OrekitMessages.TOO_SHORT_TRANSITION_TIME_FOR_ATTITUDES_SWITCH,
  275.                                       transitionTime, switchEvent.getThreshold());
  276.         }

  277.         // if it is the first switching condition, assume first active law is the past one
  278.         if (activated == null) {
  279.             resetActiveProvider(past);
  280.         }

  281.         // add the switching condition
  282.         switches.add(new Switch(switchEvent, switchOnIncrease, switchOnDecrease,
  283.                                 past, future, transitionTime, transitionFilter, handler));

  284.     }

  285.     /** {@inheritDoc} */
  286.     @Override
  287.     public Attitude getAttitude(final PVCoordinatesProvider pvProv,
  288.                                 final AbsoluteDate date, final Frame frame) {
  289.         return activated.get(date).getAttitude(pvProv, date, frame);
  290.     }

  291.     /** {@inheritDoc} */
  292.     @Override
  293.     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
  294.                                                                             final FieldAbsoluteDate<T> date,
  295.                                                                             final Frame frame) {
  296.         return activated.get(date.toAbsoluteDate()).getAttitude(pvProv, date, frame);
  297.     }

  298.     /** {@inheritDoc} */
  299.     @Override
  300.     public Rotation getAttitudeRotation(final PVCoordinatesProvider pvProv, final AbsoluteDate date, final Frame frame) {
  301.         return activated.get(date).getAttitudeRotation(pvProv, date, frame);
  302.     }

  303.     @Override
  304.     public <T extends CalculusFieldElement<T>> FieldRotation<T> getAttitudeRotation(final FieldPVCoordinatesProvider<T> pvProv,
  305.                                                                                     final FieldAbsoluteDate<T> date,
  306.                                                                                     final Frame frame) {
  307.         return activated.get(date.toAbsoluteDate()).getAttitudeRotation(pvProv, date, frame);
  308.     }

  309.     /**
  310.      * Gets a deep copy of the switches stored in this instance.
  311.      *
  312.      * @return deep copy of the switches stored in this instance
  313.      */
  314.     public List<Switch> getSwitches() {
  315.         return new ArrayList<>(switches);
  316.     }

  317.     /** Switch specification. */
  318.     public class Switch implements EventDetector, EventHandler {

  319.         /** Event. */
  320.         private final EventDetector event;

  321.         /** Event direction triggering the switch. */
  322.         private final boolean switchOnIncrease;

  323.         /** Event direction triggering the switch. */
  324.         private final boolean switchOnDecrease;

  325.         /** Attitude provider applicable for times in the switch event occurrence past. */
  326.         private final AttitudeProvider past;

  327.         /** Attitude provider applicable for times in the switch event occurrence future. */
  328.         private final AttitudeProvider future;

  329.         /** Duration of the transition between the past and future attitude laws. */
  330.         private final double transitionTime;

  331.         /** Order at which the transition law time derivatives should match past and future attitude laws. */
  332.         private final AngularDerivativesFilter transitionFilter;

  333.         /** Handler to call for notifying when switch occurs (may be null). */
  334.         private final SwitchHandler switchHandler;

  335.         /** Propagation direction. */
  336.         private boolean forward;

  337.         /**
  338.          * Simple constructor.
  339.          *
  340.          * @param event event
  341.          * @param switchOnIncrease if true, switch is triggered on increasing event
  342.          * @param switchOnDecrease if true, switch is triggered on decreasing event otherwise switch is triggered on
  343.          * decreasing event
  344.          * @param past attitude provider applicable for times in the switch event occurrence past
  345.          * @param future attitude provider applicable for times in the switch event occurrence future
  346.          * @param transitionTime duration of the transition between the past and future attitude laws
  347.          * @param transitionFilter order at which the transition law time derivatives should match past and future attitude
  348.          * laws
  349.          * @param switchHandler handler to call for notifying when switch occurs (may be null)
  350.          */
  351.         private Switch(final EventDetector event, final boolean switchOnIncrease, final boolean switchOnDecrease,
  352.                        final AttitudeProvider past, final AttitudeProvider future, final double transitionTime,
  353.                        final AngularDerivativesFilter transitionFilter, final SwitchHandler switchHandler) {
  354.             this.event            = event;
  355.             this.switchOnIncrease = switchOnIncrease;
  356.             this.switchOnDecrease = switchOnDecrease;
  357.             this.past             = past;
  358.             this.future           = future;
  359.             this.transitionTime   = transitionTime;
  360.             this.transitionFilter = transitionFilter;
  361.             this.switchHandler    = switchHandler;
  362.         }

  363.         /** {@inheritDoc} */
  364.         @Override
  365.         public double getThreshold() {
  366.             return event.getThreshold();
  367.         }

  368.         /** {@inheritDoc} */
  369.         @Override
  370.         public AdaptableInterval getMaxCheckInterval() {
  371.             return event.getMaxCheckInterval();
  372.         }

  373.         /** {@inheritDoc} */
  374.         @Override
  375.         public int getMaxIterationCount() {
  376.             return event.getMaxIterationCount();
  377.         }

  378.         /** {@inheritDoc} */
  379.         public void init(final SpacecraftState s0, final AbsoluteDate t) {

  380.             // reset the transition parameters (this will be done once for each switch,
  381.             //  despite doing it only once would have sufficient; it's not really a problem)
  382.             forward = t.durationFrom(s0.getDate()) >= 0.0;
  383.             if (activated.getSpansNumber() > 1) {
  384.                 // remove transitions that will be overridden during upcoming propagation
  385.                 if (forward) {
  386.                     activated = activated.extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(transitionTime));
  387.                 } else {
  388.                     activated = activated.extractRange(s0.getDate().shiftedBy(-transitionTime), AbsoluteDate.FUTURE_INFINITY);
  389.                 }
  390.             }

  391.             // initialize the underlying event
  392.             event.init(s0, t);

  393.         }

  394.         /** {@inheritDoc} */
  395.         public double g(final SpacecraftState s) {
  396.             return event.g(forward ? s : s.shiftedBy(-transitionTime));
  397.         }

  398.         /** {@inheritDoc} */
  399.         public EventHandler getHandler() {
  400.             return this;
  401.         }

  402.         /** {@inheritDoc} */
  403.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {

  404.             final AbsoluteDate date = s.getDate();
  405.             if (activated.get(date) == (forward ? past : future) &&
  406.                 (increasing && switchOnIncrease || !increasing && switchOnDecrease)) {

  407.                 if (forward) {

  408.                     // prepare transition
  409.                     final AbsoluteDate transitionEnd = date.shiftedBy(transitionTime);
  410.                     activated.addValidAfter(new TransitionProvider(s.getAttitude(), transitionEnd), date, false);

  411.                     // prepare future law after transition
  412.                     activated.addValidAfter(future, transitionEnd, false);

  413.                     // notify about the switch
  414.                     if (switchHandler != null) {
  415.                         switchHandler.switchOccurred(past, future, s);
  416.                     }

  417.                     return event.getHandler().eventOccurred(s, event, increasing);

  418.                 } else {

  419.                     // estimate state at transition start, according to the past attitude law
  420.                     final Orbit     sOrbit    = s.getOrbit().shiftedBy(-transitionTime);
  421.                     final Attitude  sAttitude = past.getAttitude(sOrbit, sOrbit.getDate(), sOrbit.getFrame());
  422.                     SpacecraftState sState    = new SpacecraftState(sOrbit, sAttitude, s.getMass());
  423.                     for (final DoubleArrayDictionary.Entry entry : s.getAdditionalStatesValues().getData()) {
  424.                         sState = sState.addAdditionalState(entry.getKey(), entry.getValue());
  425.                     }

  426.                     // prepare transition
  427.                     activated.addValidBefore(new TransitionProvider(sAttitude, date), date, false);

  428.                     // prepare past law before transition
  429.                     activated.addValidBefore(past, sOrbit.getDate(), false);

  430.                     // notify about the switch
  431.                     if (switchHandler != null) {
  432.                         switchHandler.switchOccurred(future, past, sState);
  433.                     }

  434.                     return event.getHandler().eventOccurred(sState, event, increasing);

  435.                 }

  436.             } else {
  437.                 // trigger the underlying event despite no attitude switch occurred
  438.                 return event.getHandler().eventOccurred(s, event, increasing);
  439.             }

  440.         }

  441.         /** {@inheritDoc} */
  442.         @Override
  443.         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
  444.             // delegate to underlying event
  445.             return event.getHandler().resetState(event, oldState);
  446.         }

  447.         /** Provider for transition phases.
  448.          * @since 9.2
  449.          */
  450.         private class TransitionProvider implements AttitudeProvider {

  451.             /** Attitude at preceding transition. */
  452.             private final Attitude transitionPreceding;

  453.             /** Date of final switch to following attitude law. */
  454.             private final AbsoluteDate transitionEnd;

  455.             /** Simple constructor.
  456.              * @param transitionPreceding attitude at preceding transition
  457.              * @param transitionEnd date of final switch to following attitude law
  458.              */
  459.             TransitionProvider(final Attitude transitionPreceding, final AbsoluteDate transitionEnd) {
  460.                 this.transitionPreceding = transitionPreceding;
  461.                 this.transitionEnd       = transitionEnd;
  462.             }

  463.             /** {@inheritDoc} */
  464.             public Attitude getAttitude(final PVCoordinatesProvider pvProv,
  465.                                         final AbsoluteDate date, final Frame frame) {

  466.                 // Create sample
  467.                 final TimeStampedAngularCoordinates start =
  468.                         transitionPreceding.withReferenceFrame(frame).getOrientation();
  469.                 final TimeStampedAngularCoordinates end =
  470.                         future.getAttitude(pvProv, transitionEnd, frame).getOrientation();
  471.                 final List<TimeStampedAngularCoordinates> sample =  Arrays.asList(start, end);

  472.                 // Create interpolator
  473.                 final TimeInterpolator<TimeStampedAngularCoordinates> interpolator =
  474.                         new TimeStampedAngularCoordinatesHermiteInterpolator(sample.size(), transitionFilter);

  475.                 // interpolate between the two boundary attitudes
  476.                 final TimeStampedAngularCoordinates interpolated = interpolator.interpolate(date, sample);

  477.                 return new Attitude(frame, interpolated);

  478.             }

  479.             /** {@inheritDoc} */
  480.             public <S extends CalculusFieldElement<S>> FieldAttitude<S> getAttitude(final FieldPVCoordinatesProvider<S> pvProv,
  481.                                                                                 final FieldAbsoluteDate<S> date,
  482.                                                                                 final Frame frame) {

  483.                 // create sample
  484.                 final TimeStampedFieldAngularCoordinates<S> start =
  485.                         new TimeStampedFieldAngularCoordinates<>(date.getField(),
  486.                                                                  transitionPreceding.withReferenceFrame(frame).getOrientation());
  487.                 final TimeStampedFieldAngularCoordinates<S> end =
  488.                         future.getAttitude(pvProv,
  489.                                            new FieldAbsoluteDate<>(date.getField(), transitionEnd),
  490.                                            frame).getOrientation();
  491.                 final List<TimeStampedFieldAngularCoordinates<S>> sample = Arrays.asList(start, end);

  492.                 // create interpolator
  493.                 final FieldTimeInterpolator<TimeStampedFieldAngularCoordinates<S>, S> interpolator =
  494.                         new TimeStampedFieldAngularCoordinatesHermiteInterpolator<>(sample.size(), transitionFilter);

  495.                 // interpolate between the two boundary attitudes
  496.                 final TimeStampedFieldAngularCoordinates<S> interpolated = interpolator.interpolate(date, sample);

  497.                 return new FieldAttitude<>(frame, interpolated);
  498.             }

  499.         }

  500.     }

  501.     /** Interface for attitude switch notifications.
  502.      * <p>
  503.      * This interface is intended to be implemented by users who want to be
  504.      * notified when an attitude switch occurs.
  505.      * </p>
  506.      * @since 7.1
  507.      */
  508.     public interface SwitchHandler {

  509.         /** Method called when attitude is switched from one law to another law.
  510.          * @param preceding attitude law used preceding the switch (i.e. in the past
  511.          * of the switch event for a forward propagation, or in the future
  512.          * of the switch event for a backward propagation)
  513.          * @param following attitude law used following the switch (i.e. in the future
  514.          * of the switch event for a forward propagation, or in the past
  515.          * of the switch event for a backward propagation)
  516.          * @param state state at switch time (with attitude computed using the {@code preceding} law)
  517.          */
  518.         void switchOccurred(AttitudeProvider preceding, AttitudeProvider following, SpacecraftState state);

  519.     }

  520. }