This tutorial emphasizes a specific usage of the attitude package described in the attitudes section of the library architecture documentation.

Attitudes Sequence

AttitudesSequence enables easy switching between attitude laws on event occurrences when propagating some SpacecraftState.

Let's set up an initial state as:

  • a date in UTC time scale
  • an orbit defined by the position and the velocity of the spacecraft in the EME2000 inertial frame and an associated central attraction coefficient chosen among many physical constants available in Orekit.

The initial orbit is here defined as a KeplerianOrbit. TimeScale utc = TimeScalesFactory.getUTC(); AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, utc); Vector3D position = new Vector3D(-6142438.668, 3492467.560, -25767.25680); Vector3D velocity = new Vector3D(505.8479685, 942.7809215, 7435.922231); Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity), FramesFactory.getEME2000(), initialDate, Constants.EIGEN5C_EARTH_MU);

More details on the orbit representation can be found in the orbits section of the library architecture documentation.

We will put all switching events in a set.

final SortedSet<String> output = new TreeSet<>();

Let's define a couple of AttitudeProvider, built upon LofOffset laws for instance.

final AttitudeProvider dayObservationLaw = new LofOffset(initialOrbit.getFrame(), LOFType.VVLH,
                                                         RotationOrder.XYZ, FastMath.toRadians(20), FastMath.toRadians(40), 0);
final AttitudeProvider nightRestingLaw   = new LofOffset(initialOrbit.getFrame(), LOFType.VVLH);

Let's also define some EventDetector. For this tutorial's requirements, two EclipseDetector, each one using a customized implementation of EventHandler with a dedicated eventOccurred method: dayNightEvent, to detect the day to night transition, nightDayEvent, to detect the night to day transition:

PVCoordinatesProvider sun   = CelestialBodyFactory.getSun();
PVCoordinatesProvider earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                                                   FramesFactory.getITRF(IERSConventions.IERS_2010, true));
EventDetector dayNightEvent = new EclipseDetector(sun, 696000000., earth).
                              withHandler(new ContinueOnEvent());
EventDetector nightDayEvent = new EclipseDetector(sun, 696000000., earth).
                              withHandler(new ContinueOnEvent());

More details on event detectors and event handlers can be found in the propagation section of the library architecture documentation.

An AttitudesSequence is then defined, for the sake of this tutorial, by adding two switching conditions acting as a simple loop:

  • the first one enables the transition from dayObservationLaw to nightRestingLaw when a decreasing dayNightEvent occurs,
  • the second one enables the transition from nightRestingLaw to dayObservationLaw when an increasing nightDayEvent occurs.

As the two conditions reverse each other effect, the combined AttitudesSequence acts as a loop. We also define a handler to monitor attitude switches:

AttitudesSequence attitudesSequence = new AttitudesSequence();
AttitudesSequence.SwitchHandler switchHandler =
            (preceding, following, s) -> {
                if (preceding == dayObservationLaw) {
                    output.add(s.getDate().toStringWithoutUtcOffset(utc, 3) + ": switching to night law");
                } else {
                    output.add(s.getDate().toStringWithoutUtcOffset(utc, 3) + ": switching to day law");
attitudesSequence.addSwitchingCondition(dayObservationLaw, nightRestingLaw, dayNightEvent,
                                        false, true, 10.0,
                                        AngularDerivativesFilter.USE_R, switchHandler);
attitudesSequence.addSwitchingCondition(nightRestingLaw, dayObservationLaw, nightDayEvent,
                                        true, false, 10.0,
                                        AngularDerivativesFilter.USE_R, switchHandler);

An AttitudesSequence needs at least one switching condition to be meaningful, but there is no upper limit.

An active AttitudeProvider may have several switch events and next law settings, leading to different activation patterns depending on which event is triggered first.

Don't forget to set the initial active law according to the initial state:

if (dayNightEvent.g(new SpacecraftState(initialOrbit)) >= 0) {
    // initial position is in daytime
} else {
    // initial position is in nighttime

Now, let's choose some propagator to compute the spacecraft motion. We will use an EcksteinHechlerPropagator based on the analytical Eckstein-Hechler model. The propagator is built upon the initialOrbit, the attitudeSequence and physical constants for the potential.

Propagator propagator = new EcksteinHechlerPropagator(initialOrbit, attitudesSequence,
                                                      Constants.EIGEN5C_EARTH_MU, Constants.EIGEN5C_EARTH_C20,
                                                      Constants.EIGEN5C_EARTH_C30, Constants.EIGEN5C_EARTH_C40,
                                                      Constants.EIGEN5C_EARTH_C50, Constants.EIGEN5C_EARTH_C60);

The attitudeSequence must register all the switching events before propagation.


The propagator operating mode is completed with a fixed step handler. The implementation of the interface OrekitFixedStepHandler aims to define the handleStep method called within the loop. For the purpose of this tutorial, the handleStep method will print at the current date two angles, the first one indicates if the spacecraft is eclipsed while the second informs about the current attitude law.

propagator.getMultiplexer().add(180.0, (currentState, isLast) -> {
        DecimalFormatSymbols angleDegree = new DecimalFormatSymbols(Locale.US);
        DecimalFormat ad = new DecimalFormat(" 00.000;-00.000", angleDegree);

        // the Earth position in spacecraft frame should be along spacecraft Z axis
        // during nigthtime and away from it during daytime due to roll and pitch offsets
        final Vector3D earthDir = currentState.toTransform().transformPosition(Vector3D.ZERO);
        final double pointingOffset = Vector3D.angle(earthDir, Vector3D.PLUS_K);

        // the g function is the eclipse indicator, it is an angle between Sun and Earth limb,
        // positive when Sun is outside of Earth limb, negative when Sun is hidden by Earth limb
        final double eclipseAngle = dayNightEvent.g(currentState);

        output.add(currentState.getDate().toStringWithoutUtcOffset(utc, 3) +
                   " " + ad.format(FastMath.toDegrees(eclipseAngle) +
                   " " + ad.format(FastMath.toDegrees(pointingOffset)));

More details on steps management can be found in the propagation section of the library architecture documentation.

Finally, the propagator is just asked to propagate for a given duration, and we print the results:

SpacecraftState finalState = propagator.propagate(initialDate.shiftedBy(12600.));
for (final String line : output) {

Note that we use an intermediate SortedSet to first gather both the switching events and the step outputs instead of just letting the event handler and step handler directly print their results. The rationale is that as events handlers could truncate a step (if their eventOccurred method returned Action.STOP), the library design is to always call eventOccurred on the event handler first, and then to call handleStep on the step handler afterwards, with the isLast boolean set up correctly if the event handler decided to stop propagation. A side effect is that if both methods print something, then the switch from the end of the step would be printed first and the step itself printed afterwards, which would lead to out of order output. The SortedSet ensures the various lines will be sorted in lexicographic order, which is chronological order here, despite they will be generated slightly out of order near events occurrences.

As the propagation goes along, events occur switching from one attitude law to another.

The printed results are shown below:

2004-01-01T23:30:00.000 -11°630  00°000
2004-01-01T23:33:00.000 -17°804  00°000
2004-01-01T23:36:00.000 -22°432  00°000
2004-01-01T23:39:00.000 -24°945  00°000
2004-01-01T23:42:00.000 -24°937  00°000
2004-01-01T23:45:00.000 -22°425  00°000
2004-01-01T23:48:00.000 -17°843  00°000
2004-01-01T23:51:00.000 -11°764  00°000
2004-01-01T23:54:00.000 -04°681  00°000
2004-01-01T23:55:50.363: switching to day law
2004-01-01T23:57:00.000  03°050  43°958
2004-01-02T00:00:00.000  11°186  43°958
2004-01-02T00:03:00.000  19°556  43°958
2004-01-02T00:06:00.000  28°028  43°958
2004-01-02T00:09:00.000  36°484  43°958
2004-01-02T00:12:00.000  44°797  43°958
2004-01-02T00:15:00.000  52°808  43°958
2004-01-02T00:18:00.000  60°296  43°958
2004-01-02T00:21:00.000  66°944  43°958
2004-01-02T00:24:00.000  72°308  43°958
2004-01-02T00:27:00.000  75°830  43°958
2004-01-02T00:30:00.000  76°995  43°958
2004-01-02T00:33:00.000  75°605  43°958
2004-01-02T00:36:00.000  71°918  43°958
2004-01-02T00:39:00.000  66°468  43°958
2004-01-02T00:42:00.000  59°792  43°958
2004-01-02T00:45:00.000  52°301  43°958
2004-01-02T00:48:00.000  44°283  43°958
2004-01-02T00:51:00.000  35°938  43°958
2004-01-02T00:54:00.000  27°420  43°958
2004-01-02T00:57:00.000  18°858  43°958
2004-01-02T01:00:00.000  10°383  43°958
2004-01-02T01:03:00.000  02°148  43°958
2004-01-02T01:03:48.386: switching to night law
2004-01-02T01:06:00.000 -05°646  00°000
2004-01-02T01:09:00.000 -12°720  00°000
2004-01-02T01:12:00.000 -18°680  00°000
2004-01-02T01:15:00.000 -23°002  00°000
2004-01-02T01:18:00.000 -25°124  00°000
2004-01-02T01:21:00.000 -24°693  00°000
2004-01-02T01:24:00.000 -21°807  00°000
2004-01-02T01:27:00.000 -16°943  00°000
2004-01-02T01:30:00.000 -10°670  00°000
2004-01-02T01:33:00.000 -03°460  00°000
2004-01-02T01:34:21.084: switching to day law
2004-01-02T01:36:00.000  04°351  43°958
2004-01-02T01:39:00.000  12°536  43°958
2004-01-02T01:42:00.000  20°931  43°958
2004-01-02T01:45:00.000  29°408  43°958
2004-01-02T01:48:00.000  37°851  43°958
2004-01-02T01:51:00.000  46°127  43°958
2004-01-02T01:54:00.000  54°070  43°958
2004-01-02T01:57:00.000  61°447  43°958
2004-01-02T02:00:00.000  67°920  43°958
2004-01-02T02:03:00.000  73°024  43°958
2004-01-02T02:06:00.000  76°194  43°958
2004-01-02T02:09:00.000  76°944  43°958
2004-01-02T02:12:00.000  75°151  43°958
2004-01-02T02:15:00.000  71°137  43°958
2004-01-02T02:18:00.000  65°454  43°958
2004-01-02T02:21:00.000  58°620  43°958
2004-01-02T02:24:00.000  51°026  43°958
2004-01-02T02:27:00.000  42°942  43°958
2004-01-02T02:30:00.000  34°560  43°958
2004-01-02T02:33:00.000  26°025  43°958
2004-01-02T02:36:00.000  17°468  43°958
2004-01-02T02:39:00.000  09°020  43°958
2004-01-02T02:42:00.000  00°842  43°958
2004-01-02T02:42:19.059: switching to night law
2004-01-02T02:45:00.000 -06°856  00°000
2004-01-02T02:48:00.000 -13°780  00°000
2004-01-02T02:51:00.000 -19°512  00°000
2004-01-02T02:54:00.000 -23°513  00°000
2004-01-02T02:57:00.000 -25°234  00°000
2004-01-02T03:00:00.000 -24°384  00°000
Propagation ended at 2004-01-02T03:00:00.000Z

The complete code for this example can be found in the source tree of the tutorials, in file src/main/java/org/orekit/tutorials/attitude/