Attitude not applied inside handlers (Orekit 12)

Hi all

It seems the attitude is not correctly applied during propagation.

I set the attitudeprovider as LofOffset(initial_orbit.getFrame(), LOFType.VVLH) and expected the appropriate attitude in the step handler(PythonOrekitFixedStepHandler) of my propagator.

When I tried to get a pointing vector (body +Z) with respect to the inertial frame, (e.g. s.getAttitude().getRotation().applyInverseTo(Vector3D.PLUS_K)
the result was <Vector3D: {0; 0; 1}>.
Obviously the satellite was not above the South Pole.

This also happens in event handlers(PythonEventHandler) for event detectors.

Hi @moonshot,

It should work… Could you please share with us a runnable example of the error you’re facing?

Thanks,
Maxime

Thanks @MaximeJ

Here is an example.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Apr 12 01:59:10 2024

@author: yongjunmoon
Compatible with Orekit 12
"""
from orekit import initVM, JArray_double
from orekit.pyhelpers import setup_orekit_curdir

from org.orekit.attitudes import LofOffset
from org.orekit.bodies import OneAxisEllipsoid
from org.orekit.forces.gravity.potential import GravityFieldFactory
from org.orekit.frames import FramesFactory, LOFType
from org.orekit.orbits import KeplerianOrbit, PositionAngleType
from org.orekit.propagation import SpacecraftState, PropagationType, Propagator
from org.orekit.propagation.sampling import PythonOrekitFixedStepHandler
from org.orekit.propagation.semianalytical.dsst import DSSTPropagator
from org.orekit.propagation.semianalytical.dsst.forces import DSSTTesseral, DSSTZonal
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.utils import Constants, IERSConventions

from org.hipparchus.geometry.euclidean.threed import Vector3D
from org.hipparchus.ode.nonstiff import DormandPrince853Integrator

import os
import sys
from math import radians

here = os.getcwd()
up = os.path.dirname(here)
cond1 = os.path.basename(here) == 'python'
cond2 = os.path.basename(up) == 'python'
if cond1:
    data = os.path.join(here, 'orekit-data-master.zip')
    if not here in sys.path:
        sys.path.append(here)
elif cond2:
    data = os.path.join(up, 'orekit-data-master.zip')
    if not up in sys.path:
        sys.path.append(up)
    
initVM()
setup_orekit_curdir(data)

class SimpleOrbitHandler(PythonOrekitFixedStepHandler):
    def __init__(self, nominal_attitude):
        self.nominal_attitude = nominal_attitude
        super().__init__()
    
    def init(self, s0, t, step):
        pass
        
    def handleStep(self, s):
        
        eci2body = s.getAttitude().getRotation()
        pointing = eci2body.applyInverseTo(Vector3D.PLUS_K)
        
        eci2body2 = self.nominal_attitude.getAttitude(s.getOrbit(), s.getDate(), s.getFrame()).getRotation()
        pointing2 = eci2body2.applyInverseTo(Vector3D.PLUS_K)
        
        print(pointing, pointing2)
        
    def finish(self, s):
        pass


## Earth frame definitions
eci = FramesFactory.getEME2000()
ecef = FramesFactory.getITRF(IERSConventions.IERS_2003, True)
wgs84 = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, ecef)

#%% Initial states
# Orbit
initial_date = AbsoluteDate(2024, 6, 1, 0, 0, 00.000, TimeScalesFactory.getUTC())
sma0 = 6878.1363e3 + 500e3
ecc0 = 0.002
inc0 = radians(45.)
aop0 = radians(0.)
raan0 = radians(0.)
ta0 = radians(0.)

initial_orbit = KeplerianOrbit(sma0, ecc0, inc0, aop0, raan0, ta0, PositionAngleType.TRUE,
                              eci, initial_date, Constants.EIGEN5C_EARTH_MU)

# Attitude
nominal_attitude = LofOffset(initial_orbit.getFrame(), LOFType.VVLH)
initial_attitude = nominal_attitude.getAttitude(initial_orbit, initial_date, eci)

initial_state = SpacecraftState(initial_orbit, initial_attitude)

#%% Perturbations
# Gravity
degree = 4
order  = 4
unnormalized = GravityFieldFactory.getUnnormalizedProvider(degree, order)
zonal_force = DSSTZonal(unnormalized)
tesseral_force = DSSTTesseral(ecef, Constants.WGS84_EARTH_ANGULAR_VELOCITY, unnormalized)

forces = [zonal_force, tesseral_force]

#%% Propagation
# Time
out_step = 600.
duration = 86400.

tolerances = DSSTPropagator.tolerances(1e-10, initial_orbit)
integrator = DormandPrince853Integrator(1e-6, 86400., 
                                        JArray_double.cast_(tolerances[0]), JArray_double.cast_(tolerances[1]))

dsst_prop = DSSTPropagator(integrator)
[dsst_prop.addForceModel(f) for f in forces]

orbit_handler = SimpleOrbitHandler(nominal_attitude)
Propagator.cast_(dsst_prop).setStepHandler(out_step, orbit_handler)

#%%

dsst_prop.setInitialState(initial_state, PropagationType.MEAN)
final_state = dsst_prop.propagate(initial_date, initial_date.shiftedBy(duration))

poinitng1 = initial_state.getAttitude().getRotation().applyInverseTo(Vector3D.PLUS_K) 
# expected <Vector3D: {-1; -0; 0}>, worked

poinitng2 = final_state.getAttitude().getRotation().applyInverseTo(Vector3D.PLUS_K)
# expected <Vector3D: {0.2697586745; 0.6717926636; 0.6898730859}>, but <Vector3D: {0; 0; 1}> is the result

poinitng3 = nominal_attitude.getAttitude(final_state.getOrbit(), final_state.getDate(), eci).getRotation().applyInverseTo(Vector3D.PLUS_K)
# expected <Vector3D: {0.2697586745; 0.6717926636; 0.6898730859}>, worked

Hi @moonshot,

Here initial_attitude is an Attitude object, a one-time [rotation, rotation velocity, rotation acceleration] object representing the attitude at a specific instant.
nominal_attitude on the other hand is an AttitudeProvider, that’s the object you need to compute the attitude at any point in time and space.

To make your propagator understand that it must use the VVLH LOF throughout propagation, you have to register this AttitudeProvider to it.

In short, you’re missing the line…

Propagator.cast_(dsst_prop).setAttitudeProvider(nominal_attitude)

… before propagating.

Hope this helps,
Maxime

1 Like

@MaximeJ Thank you so much!

I was confused that I didn’t set any attitude provider to a propagator before.
It was my mistake, not a bug of Orekit 12 at all.

1 Like