Hello Orekit community,
I am quite new to the Orekit python wrapper. I am working on a satellite attitude control simulation using the Orekit Python Wrapper, usingAttitudesSequence to switch between different pointing modes.
I have encountered a very strange behavior when mixing different attitude providers in the same sequence.
I have a list of targets on the ground. For each target, I want the satellite to maneuver from NadirPointing, perform the observation, and return to NadirPointing. I have two types of observation modes:
-
SPOTLIGHT: Uses
TargetPointingto track a specific ground intersection. -
STRIPMAP: Uses
LofOffset(w.r.t. VVLH) to maintain a constant roll/yaw angle offset.
The Problem:
-
If I generate a sequence of only
TargetPointingmaneuvers, everything works perfectly (up-left plot). -
If I generate a sequence of only
LofOffsetmaneuvers, everything works perfectly (up-right plot). -
The bug: If I alternate them (e.g., Target 1 is
SPOTLIGHT, Target 2 isSTRIPMAP), the STRIPMAP maneuver fails (bottom plot).
You can clearly observe this behavior in the attached plots showing the attitude evolution. Here are the relevant snippets of how I’ve built the sequence:
#targets = target_generation_spotlight(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)
#targets = target_generation_stripmap(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)
targets = target_generation_spotlight_stripmap(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)
print("\nComputing maneuvering time for each target...")
for tgt in targets:
if tgt["mode"] == "SPOTLIGHT":
target_pointing = TargetPointing(inertial_frame, tgt["geodetic point"], earth)
t_start = 0.0
while True:
state = sgp4_propagator.propagate(search_date)
att = target_pointing.getAttitude(state.getOrbit(), search_date, inertial_frame)
rot_ECI_BRF = att.getRotation()
rot_ECI_VVLH = vvlh_frame.rotationFromInertial(search_date, state.getPVCoordinates(inertial_frame))
rot_VVLH_BRF = rot_ECI_BRF.compose(rot_ECI_VVLH.revert(), RotationConvention.VECTOR_OPERATOR)
angles_lof_offset = rot_VVLH_BRF.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)
rot_VVLH_BRF_ft = rot_ECI_VVLH.revert().compose(rot_ECI_BRF, RotationConvention.FRAME_TRANSFORM)
pitch_start_deg = math.degrees(rot_VVLH_BRF_ft.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)[1])
if pitch_start_deg >= 30.0:
tgt["start_yaw"] = angles_lof_offset[0]
tgt["start_pitch"] = angles_lof_offset[1]
tgt["start_roll"] = angles_lof_offset[2]
break
t_start -= 1.0
J_pitch = diag_J[1]
alpha_max_pitch = max_torque / J_pitch
delta_theta_rad = math.radians(30.0)
t_slew = math.sqrt((2.0 * delta_theta_rad) / alpha_max_pitch)
t_end = 0.0
while True:
search_date = tgt["date"].shiftedBy(t_end)
state = sgp4_propagator.propagate(search_date)
att = target_pointing.getAttitude(state.getOrbit(), search_date, inertial_frame)
rot_ECI_BRF = att.getRotation()
rot_ECI_VVLH = vvlh_frame.rotationFromInertial(search_date, state.getPVCoordinates(inertial_frame))
#rot_VVLH_BRF_ft = rot_ECI_BRF.compose(rot_ECI_VVLH.revert(), RotationConvention.FRAME_TRANSFORM)
rot_VVLH_BRF_ft = rot_ECI_VVLH.revert().compose(rot_ECI_BRF, RotationConvention.FRAME_TRANSFORM)
pitch_end_deg = math.degrees(rot_VVLH_BRF_ft.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)[1])
if pitch_end_deg <= -30.0:
break
t_end += 1.0
tgt["t_start"] = t_start
tgt["t_maneuver"] = t_slew + 2*t_r
tgt["t_pre_pointing"] = t_start - tgt["t_maneuver"]
tgt["t_end"] = t_end
print(f"Target at {tgt['date']} | Mode: SPOTLIGHT | Roll Angle: {tgt['roll_deg']} deg | Window: {tgt['date'].shiftedBy(tgt['t_pre_pointing'])} UTC to {tgt['date'].shiftedBy(tgt['t_end'] + tgt['t_maneuver'])} UTC")
print(f"Total maneuvering time: {tgt['t_end'] + tgt['t_maneuver'] - tgt['t_pre_pointing']} s")
#print(f"Starting Pitch: {pitch_start_deg}")
elif tgt["mode"] == "STRIPMAP":
J_roll = diag_J[0]
alpha_max_roll = max_torque / J_roll
delta_theta_rad = math.radians(25.0)
t_slew = math.sqrt((2.0*delta_theta_rad) / alpha_max_roll)
tgt["t_maneuver"] = t_slew + 2.0*t_r
tgt["t_start"] = -tgt["t_maneuver"]
tgt["t_pre_pointing"] = -tgt["t_maneuver"]
tgt["t_end"] = tgt["duration"]
print(f"Target at {tgt['date']} | Mode: STRIPMAP | Azimuth Angle: {tgt['azimuth_deg']} deg | Roll Angle: {tgt['roll_deg']} deg | Window: {tgt['date'].shiftedBy(tgt['t_pre_pointing'])} UTC to {tgt['date'].shiftedBy(tgt['t_end'] + tgt['t_maneuver'])} UTC")
print(f"Total maneuvering time: {tgt['t_end'] + tgt['t_maneuver'] - tgt['t_pre_pointing']} s")
attitude_sequence = AttitudesSequence()
attitude_sequence.resetActiveProvider(nadir_pointing)
for tgt in targets:
if tgt["mode"] == "SPOTLIGHT":
target_provider = TargetPointing(inertial_frame, tgt["geodetic point"], earth)
t_switch_to_target = tgt["t_pre_pointing"]
t_slerp = tgt["t_maneuver"]
t_switch_to_nadir = tgt["t_end"]
elif tgt["mode"] == "STRIPMAP":
target_provider = LofOffset(inertial_frame, LOFType.VVLH, RotationOrder.ZYX, math.radians(tgt["azimuth_deg"]), 0.0, math.radians(tgt["roll_deg"]))
t_switch_to_target = tgt["t_pre_pointing"]
t_slerp = tgt["t_maneuver"]
t_switch_to_nadir = tgt["t_end"]
switch_to_target_date = tgt["date"].shiftedBy(t_switch_to_target)
switch_to_nadir_date = tgt["date"].shiftedBy(t_switch_to_nadir)
target_detector = DateDetector(switch_to_target_date).withMaxCheck(10.0).withThreshold(1e-3).withHandler(ContinueOnEvent())
nadir_detector = DateDetector(switch_to_nadir_date).withMaxCheck(10.0).withThreshold(1e-3).withHandler(ContinueOnEvent())
attitude_sequence.addSwitchingCondition(nadir_pointing, target_provider, target_detector, True, False, t_slerp, AngularDerivativesFilter.USE_RR, None)
attitude_sequence.addSwitchingCondition(target_provider, nadir_pointing, nadir_detector, True, False, t_slerp, AngularDerivativesFilter.USE_RR, None)
To avoid state machine ambiguity, I also tried instantiating a new NadirPointing object for every return phase, but the result remains exactly the same
Any help would be greatly appreciated. Thank you!
