"""PTR pointing module.
- extract PTR parameters from a given opporttunity definition.
- create a PTR block from these parameters.
Usage:
- ptr_pointing = JUICE_Jupiter_Ring(fixed_offset_x_angle=12.4)
- ptr_pointing.get_prm(start_time, stop_time, metadata='')
or
- ptr_pointing = PTR_Pointing(pointing_type='juice_jupiter_ring', fixed_offset_x_angle=12.4)
- ptr_pointing.get_prm(start_time, stop_time, metadata='')
or
- ptr_pointing = PTR_Pointing(opportunition_definition=opportunition_definition)
- ptr_pointing.get_prm(start_time, stop_time, metadata='')
"""
# from gfinder.opportunity import OpportunityDefinition
from ptr import Element, ObsBlock, PointingRequestMessage
from ptr.datetime.parser import dt, td
SLEW_START_DELAY = '3m'
"""Delay before spacecraft slew starts."""
SLEW_STOP_DELAY = '3m'
"""Delay after spacecraft slew stops."""
AGM_ORB_POLE_DIRS = {
'JUPITER': 'JP2SC_orbPole',
'CALLISTO': 'CA2SC_orbPole',
'EUROPA': 'EU2SC_orbPole',
'GANYMEDE': 'GA2SC_orbPole'
}
"""Mapping between target body name and AGM SC to orbital pole direction definition."""
AGM_NORTH_POLE_DIRS = {
'JUPITER': 'JPNorthPole',
}
"""Mapping between target body name and AGM North Pole direction definition."""
[docs]def list_pointing_types():
print('Available PTR pointing types, and associated required pointing parameters:')
for ptr_pointing_type in PTR_POINTINGS:
# print(ptr_pointing_type)
ptr_pointing = PTRPointing(ptr_pointing_type=ptr_pointing_type, mandatory=False)
# print(f' - {ptr_pointing}')
s = f' - {ptr_pointing_type:<25}: '
for parameter in ptr_pointing.parameters.keys():
s += f'{parameter} = {ptr_pointing.parameters[parameter]}, '
parameters_str = s[:-2]
print(f'{parameters_str}')
print()
[docs]def PTRPointing(ptr_pointing_type='', opportunity_definition=None, **kwargs):
"""Function serving as AbstractPTRPointing object factory.
"""
if opportunity_definition:
ptr_pointing_type = opportunity_definition.ptr_pointing_type
# check that format is valid and return corresponding AbstractPTRPointing object.
if ptr_pointing_type in PTR_POINTINGS.keys():
PTRPointingClass = PTR_POINTINGS[ptr_pointing_type]
ptr_pointing = PTRPointingClass(opportunity_definition=opportunity_definition, **kwargs)
return ptr_pointing
else:
raise ValueError(f'Invalid PTR pointing type: {ptr_pointing_type}. Allowed format are: {list(PTR_POINTINGS.keys())}')
[docs]class AbstractPTRPointing:
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
if opportunity_definition:
params = self.derive_parameters(opportunity_definition)
self.set_parameters(params, mandatory=mandatory)
else:
self.set_parameters(kwargs, mandatory=mandatory)
# self.pointing_type = self.__class__.__name__
def __repr__(self):
s = f"{self.__class__.__name__}: "
for parameter in self.parameters.keys():
s += f'{parameter} = {self.parameters[parameter]}, '
return s[:-2]
[docs] def set_parameters(self, parameters, mandatory=True):
for required_parameter in self.parameters:
if required_parameter in parameters.keys():
setattr(self, required_parameter, parameters[required_parameter])
else:
if mandatory:
raise ValueError(f'Could not set {required_parameter}.')
@property
def parameters(self):
"""PTR pointing required parameters. """
return self.__dict__
[docs] def derive_parameters(self, opportunity_definition):
return {}
@property
def attitude(self):
return Element('attitude')
[docs] def get_prm(self, start_time, stop_time, metadata=''):
return PointingRequestMessage(
ObsBlock(
start_time, stop_time,
self.attitude,
metadata=metadata
)
)
[docs]class JUICE_Jupiter_Ring(AbstractPTRPointing):
"""JUICE_Jupiter_Ring.
DEPRECATED, use JUICE_Target_NPL instead.
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="Jupiter"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="fixed">
<xAngle units="deg">{{ fixed_offset_x_angle }}</xAngle>
<yAngle units="deg">0.0</yAngle>
</offsetAngles>
<phaseAngle ref="align">
<SCAxis frame="SC">
<x>0</x>
<y>1</y>
<z>0</z>
</SCAxis>
<inertialAxis ref="JPNorthPole"/>
</phaseAngle>
</attitude>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# set required pointing parameters
self.fixed_offset_x_angle = 0.0
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# if opportunity_definition:
# params = self.derive_parameters(opportunity_definition)
# self.set_parameters(params, mandatory=mandatory)
# else:
# self.set_parameters(kwargs, mandatory=mandatory)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
y_start = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
fixed_offset_x_angle = -y_start
return {
'fixed_offset_x_angle': fixed_offset_x_angle
}
@property
def attitude(self):
return Element(
'attitude',
Element('boresight', ref='SC_Zaxis'),
Element('target', ref='Jupiter'),
Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('xAngle', self.fixed_offset_x_angle, units='deg'),
Element('yAngle', 0.0, units='deg'),
ref='fixed'
),
Element(
'phaseAngle',
Element('SCAxis', {'x': 0, 'y': 1, 'z': 0}, frame='SC'),
Element('inertialAxis', ref='JPNorthPole'),
ref='align'
),
ref='track'
)
[docs]class JUICE_Target_NOA(AbstractPTRPointing):
"""JUICE_Target_NOA.
Template:
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="{{ target }}"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="fixed">
<xAngle units="deg">{{ x_offset_angle }} </xAngle>
<yAngle units="deg">{{ y_offset_angle }}</yAngle>
</offsetAngles>
<phaseAngle ref="align">
<SCAxis frame="SC">
<x>1</x>
<y>0</y>
<z>0</z>
</SCAxis>
<inertialAxis ref="{{ inertial_axis }}"/>
</phaseAngle>
</attitude>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# set required pointing parameters
self.target = ''
self.fixed_offset_x_angle = 0.0
self.fixed_offset_y_angle = 0.0
# self.boresight = '' # "MAJIS_Zaxis"
# self.secondary_axis = '' # "MAJIS_Yaxis"
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
target = opportunity_definition.target.upper()
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
# print(sc_frame_def)
try:
x_start = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
except Exception as e:
x_start = 0.0
print(f'MOS `x_start` parameter not found, set to {x_start}.')
try:
y_start = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
except Exception as e:
y_start = 0.0
print(f'MOS `y_start` parameter not found, set to {y_start}.')
fixed_offset_x_angle = y_start
fixed_offset_y_angle = -x_start
return {
'target': target,
'fixed_offset_x_angle': fixed_offset_x_angle,
'fixed_offset_y_angle': fixed_offset_y_angle
}
@property
def attitude(self):
sc_axis = [1, 0, 0]
offset_ref_axis = [1, 0, 0]
inertial_axis = ''
if self.target in AGM_ORB_POLE_DIRS.keys():
inertial_axis = AGM_ORB_POLE_DIRS[self.target]
else:
raise Exception(f'No orbital pole direction vector defined for {self.target}.')
# Temporary patch to be removed when "MAJIS_Yaxis" definition has been added by JUICE SOC.
# if self.secondary_axis == 'MAJIS_Yaxis':
# sc_axis = [-0.01425291, 0.99988351, 0.00546049] # majis_axis as defined in JUICE FK on March 2023
# offset_ref_axis = [ 0.99988928, 0.01422921, 0.00435425]
return Element(
'attitude',
Element('boresight', ref='SC_Zaxis'),
Element('target', ref=self.target),
Element('offsetRefAxis', {'x': offset_ref_axis[0], 'y': offset_ref_axis[1], 'z': offset_ref_axis[2]}, frame='SC'),
# Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('xAngle', self.fixed_offset_x_angle, units='deg'),
Element('yAngle', self.fixed_offset_y_angle, units='deg'),
ref='fixed'
),
Element(
'phaseAngle',
Element('SCAxis', {'x': sc_axis[0], 'y': sc_axis[1], 'z': sc_axis[2]}, frame='SC'),
Element('inertialAxis', ref=inertial_axis),
ref='align'
),
ref='track'
)
[docs]class JUICE_Target_NPL(AbstractPTRPointing):
"""JUICE_Target_NPL.
Template:
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="{{ target }}"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="fixed">
<xAngle units="deg">{{ fixed_offset_x_angle }}</xAngle>
<yAngle units="deg">0.0</yAngle>
</offsetAngles>
<phaseAngle ref="align">
<SCAxis frame="SC">
<x>0</x>
<y>1</y>
<z>0</z>
</SCAxis>
<inertialAxis ref="JPNorthPole"/>
</phaseAngle>
</attitude>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# set required pointing parameters
self.target = ''
self.fixed_offset_x_angle = 0.0
self.fixed_offset_y_angle = 0.0
self.boresight = '' # "MAJIS_Zaxis"
self.secondary_axis = '' # "MAJIS_Yaxis"
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
target = opportunity_definition.target
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
# print(sc_frame_def)
try:
x_start = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
except Exception as e:
x_start = 0.0
print(f'MOS `x_start` parameter not found, set to {x_start}.')
try:
y_start = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
except Exception as e:
y_start = 0.0
print(f'MOS `y_start` parameter not found, set to {y_start}.')
fixed_offset_x_angle = y_start
fixed_offset_y_angle = -x_start
boresight = opportunity_definition.boresight
if boresight == 'Z':
boresight = 'SC_Zaxis'
# TODO: temporary patch to be removed when JUICE SOC has changed naming from "JUICE_MAJIS_BASE" to "MAJIS_Zaxis"
if boresight == 'MAJIS_Zaxis':
boresight = 'JUICE_MAJIS_BASE'
secondary_axis = opportunity_definition.secondary_axis
return {
'target': target,
'fixed_offset_x_angle': fixed_offset_x_angle,
'fixed_offset_y_angle': fixed_offset_y_angle,
'boresight': boresight,
'secondary_axis': secondary_axis
}
@property
def attitude(self):
# TODO: Temporary patch to be removed when "MAJIS_Yaxis" definition has been added by SC
sc_axis = [0, 1, 0]
offset_ref_axis = [1, 0, 0]
if self.secondary_axis == 'MAJIS_Yaxis':
sc_axis = [-0.01425291, 0.99988351, 0.00546049] # majis_axis as defined in JUICE FK on March 2023
offset_ref_axis = [ 0.99988928, 0.01422921, 0.00435425]
inertial_axis = ''
if self.target in AGM_NORTH_POLE_DIRS.keys():
inertial_axis = AGM_NORTH_POLE_DIRS[self.target]
else:
raise Exception(f'No SC to North Pole direction defined for {self.target}.')
return Element(
'attitude',
Element('boresight', ref=self.boresight), # eg: 'SC_Zaxis' or 'JUICE_MAJIS_BASE'
Element('target', ref=self.target),
Element('offsetRefAxis', {'x': offset_ref_axis[0], 'y': offset_ref_axis[1], 'z': offset_ref_axis[2]}, frame='SC'),
# Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('xAngle', self.fixed_offset_x_angle, units='deg'),
Element('yAngle', self.fixed_offset_y_angle, units='deg'),
ref='fixed'
),
Element(
'phaseAngle',
Element('SCAxis', {'x': sc_axis[0], 'y': sc_axis[1], 'z': sc_axis[2]}, frame='SC'),
# Element('SCAxis', {'x': 0, 'y': 1, 'z': 0}, frame='SC'),
Element('inertialAxis', ref=inertial_axis),
ref='align'
),
ref='track'
)
[docs]class JUICE_Target_NPL_Slew(AbstractPTRPointing):
"""JUICE_Target_NPL_Slew.
Note:
- "scan" refers to spacecraft slew.
Hypothesis:
- The "scan" startTime element indicates the actual requested MAJIS observation start time.
- A minimum time delay of 120/200s [TBD] is required between "OBS" block and the "scan" start time, as well as
between "OBS" block stop time .
Therefore, the "OBS" block startTime element
Template:
<block ref="OBS">
<startTime> {{ start_time }} </startTime>
<endTime> {{ stop_time }} </endTime>
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="{{ target }}"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="scan">
<startTime>{{ scan_start_time }}</startTime>
<numberOfLines>1</numberOfLines>
<numberOfScansPerLine>1</numberOfScansPerLine>
<xStart units="deg">{{ x_start }}</xStart>
<yStart units="deg">{{ y_start }}</yStart>
<scanSpeed units="deg/sec">{{ scan_speed }}</scanSpeed>
<borderSlewTime units="sec">0.0</borderSlewTime>
<lineAxis>y</lineAxis>
</offsetAngles>
<phaseAngle ref="align">
<SCAxis frame="SC">
<x>0</x>
<y>1</y>
<z>0</z>
</SCAxis>
<inertialAxis ref="JPNorthPole"/>
</phaseAngle>
</attitude>
</block>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# set required pointing parameters
self.scan_start_time = ''
self.target = ''
self.x_start = 0.0 # deg
self.y_start = 0.0 # deg
self.scan_delta = 0.0 # deg
self.scan_speed = 0.0 # deg/s
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
"""
Example of input opportunity definition "offset_rotations":
"offset_rotations": {
"name": "SC_Slew_Angles",
"parameters": {
"y_start": 0.0,
"x_start": -4.0,
"x_stop": 4.0,
"x_rate": 0.034482758620689655
}
}
"""
# retrieve target
target = opportunity_definition.target
# retrieve Simulated_SC_Frame parameters
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
try:
x_start_rot = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
except Exception as e:
x_start_rot = 0.0
print(f'MOS `x_start` parameter not found, set to {x_start_rot}.')
try:
x_stop_rot = sc_frame_def['parameters']['offset_rotations']['parameters']['x_stop']
except Exception as e:
x_stop_rot = 0.0
print(f'MOS `x_stop` parameter not found, set to {x_stop_rot}.')
try:
x_rate = abs(sc_frame_def['parameters']['offset_rotations']['parameters']['x_rate'])
except Exception as e:
x_rate = 0.0
print(f'MOS `x_rate` parameter not found, set to {x_rate}.')
try:
y_start_rot = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
except Exception as e:
y_start_rot = 0.0
print(f'MOS `y_start` parameter not found, set to {y_start_rot}.')
# derive PTR pointing parameters
x_start = y_start_rot
y_start = -x_start_rot
y_stop = -x_stop_rot
scan_delta = y_stop - y_start
return {
'scan_start_time': '',
'target': target,
'x_start': x_start,
'y_start': y_start,
'scan_delta': scan_delta,
'scan_speed': x_rate
}
@property
def attitude(self):
inertial_axis = ''
if self.target in AGM_NORTH_POLE_DIRS.keys():
inertial_axis = AGM_NORTH_POLE_DIRS[self.target]
else:
raise Exception(f'No SC to North Pole direction defined for {self.target}.')
return Element(
'attitude',
Element('boresight', ref='SC_Zaxis'),
Element('target', ref=self.target),
Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('startTime', self.scan_start_time),
Element('numberOfLines', 1),
Element('numberOfScansPerLine', 1),
Element('xStart', self.x_start, units='deg'),
Element('yStart', self.y_start, units='deg'),
Element('scanDelta', self.scan_delta, units='deg'),
Element('scanSpeed', self.scan_speed, units='deg/sec'),
# Element('borderSlewTime', 0.0, units='sec'),
ref='scan'
),
Element(
'phaseAngle',
Element('SCAxis', {'x': 0, 'y': 1, 'z': 0}, frame='SC'),
Element('inertialAxis', ref=inertial_axis),
ref='align'
),
ref='track'
)
[docs] def get_prm(self, start_time, stop_time, metadata=''):
"""Returns PRM XML for JUICE_Target_NPL_Slew pointing type.
Note: This child method is required to update pointing parameters that can not be retrieved from an opportunity
definition object. In this case: the `scan_start_time` parameter. It also to adjust input start and stop time
to accommodate for AGM validation rules, eg: "tranquilisation" time.
"""
# update scan start time to the actually requested observation start time.
self.scan_start_time = dt(start_time).isoformat()
# update start and stop to be used in "OBS" block so as to include a time delay of 2 minutes before and after
# the scan
start_time = dt(start_time) - td(SLEW_START_DELAY)
stop_time = dt(stop_time) + td(SLEW_STOP_DELAY)
# return PRM
return super().get_prm(start_time, stop_time, metadata=metadata)
[docs]class JUICE_Target_NPO(AbstractPTRPointing):
"""JUICE_Target_NPO.
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="{{ target }}"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="fixed">
<xAngle units="deg">{{ fixed_offset_x_angle }}</xAngle>
<yAngle units="deg">{{ fixed_offset_y_angle }}</yAngle>
</offsetAngles>
<phaseAngle ref="powerOptimised">
<yDir>false</yDir>
</phaseAngle>
</attitude>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
# set required pointing parameters
self.target = ''
self.fixed_offset_x_angle = 0.0
self.fixed_offset_y_angle = 0.0
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
target = opportunity_definition.target
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
x_start = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
y_start = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
fixed_offset_x_angle = y_start
fixed_offset_y_angle = -x_start
return {
'target': target,
'fixed_offset_x_angle': fixed_offset_x_angle,
'fixed_offset_y_angle': fixed_offset_y_angle
}
@property
def attitude(self):
# return Element('attitude')
return Element(
'attitude',
Element('boresight', ref='SC_Zaxis'),
Element('target', ref=self.target),
Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('xAngle', self.fixed_offset_x_angle, units='deg'),
Element('yAngle', self.fixed_offset_y_angle, units='deg'),
ref='fixed'
),
Element('phaseAngle', {'yDir': False}, ref='powerOptimised'),
ref='track'
)
[docs]class JUICE_Target_NPO_Slew(AbstractPTRPointing):
"""JUICE_Target_NPO_Slew.
Note:
- "scan" refers to spacecraft slew.
Hypothesis:
- The "scan" startTime element indicates the actual requested MAJIS observation start time.
- A minimum time delay of 120/200s [TBD] is required between "OBS" block and the "scan" start time, as well as
between "OBS" block stop time .
Therefore, the "OBS" block startTime element
PTR block template:
<block ref="OBS">
<startTime> {{ start_time }} </startTime>
<endTime> {{ stop_time }} </endTime>
<attitude ref="track">
<boresight ref="SC_Zaxis"/>
<target ref="{{ target }}"/>
<offsetRefAxis ref="SC_Xaxis"/>
<offsetAngles ref="scan">
<startTime>{{ scan_start_time }}</startTime>
<numberOfLines>1</numberOfLines>
<numberOfScansPerLine>1</numberOfScansPerLine>
<xStart units="deg">{{ x_start }}</xStart>
<yStart units="deg">{{ y_start }}</yStart>
<scanSpeed units="deg/sec">{{ scan_speed }}</scanSpeed>
<borderSlewTime units="sec">0.0</borderSlewTime>
<lineAxis>y</lineAxis>
</offsetAngles>
<phaseAngle ref="powerOptimised">
<yDir>false</yDir>
</phaseAngle>
</attitude>
</block>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# set required pointing parameters
self.scan_start_time = ''
self.target = ''
self.x_start = 0.0 # deg
self.y_start = 0.0 # deg
self.scan_delta = 0.0 # deg
self.scan_speed = 0.0 # deg/s
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
[docs] def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
"""
Example of input opportunity definition "offset_rotations":
"offset_rotations": {
"name": "SC_Slew_Angles",
"parameters": {
"x_start": -4.0,
"x_stop": 4.0,
"x_rate": 0.034482758620689655
}
}
"""
target = opportunity_definition.target
sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
x_start_rot = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
x_stop_rot = sc_frame_def['parameters']['offset_rotations']['parameters']['x_stop']
x_rate = abs(sc_frame_def['parameters']['offset_rotations']['parameters']['x_rate'])
x_start = 0.0 # TODO: update based on available SC_Slew_Angles parameters
y_start = -x_start_rot
y_stop = -x_stop_rot
scan_delta = y_stop - y_start
return {
'scan_start_time': '',
'target': target,
'x_start': x_start,
'y_start': y_start,
'scan_delta': scan_delta,
'scan_speed': x_rate
}
@property
def attitude(self):
return Element(
'attitude',
Element('boresight', ref='SC_Zaxis'),
Element('target', ref=self.target),
Element('offsetRefAxis', ref='SC_Xaxis'),
Element(
'offsetAngles',
Element('startTime', self.scan_start_time),
Element('numberOfLines', 1),
Element('numberOfScansPerLine', 1),
Element('xStart', self.x_start, units='deg'),
Element('yStart', self.y_start, units='deg'),
Element('scanDelta', self.scan_delta, units='deg'),
Element('scanSpeed', self.scan_speed, units='deg/sec'),
ref='scan'
),
Element('phaseAngle', {'yDir': False}, ref='powerOptimised'),
ref='track'
)
[docs] def get_prm(self, start_time, stop_time, metadata=''):
"""Returns PRM XML for JUICE_Target_NPL_Slew pointing type.
Note: This child method is required to update pointing parameters that can not be retrieved from an opportunity
definition object. In this case: the `scan_start_time` parameter. It also to adjust input start and stop time
to accommodate for AGM validation rules, eg: "tranquilisation" time.
"""
# update scan start time to the actually requested observation start time.
self.scan_start_time = dt(start_time).isoformat()
# update start and stop to be used in "OBS" block so as to include a time delay of 2 minutes before and after
# the scan
start_time = dt(start_time) - td(SLEW_START_DELAY)
stop_time = dt(stop_time) + td(SLEW_STOP_DELAY)
# return PRM
return super().get_prm(start_time, stop_time, metadata='')
[docs]class JUICE_Target_Limb(AbstractPTRPointing):
"""JUICE_Target_Limb.
NOT IMPLEMENTED.
Example:
<block ref="OBS">
<startTime> 2032-01-11T12:31:40 </startTime>
<endTime> 2032-01-11T12:56:00 </endTime>
<attitude ref="limb">
<boresight ref="SC_Zaxis" />
<targetDir ref="rotate">
<axis ref="CA2Sun" />
<rotationAxis ref="SC2CA" />
<rotationAngle units="deg"> 40 </rotationAngle>
</targetDir>
<height units="km"> 0 </height>
<surface ref="Callisto" />
<phaseAngle ref="align">
<SCAxis frame="SC">
<x> 0 </x>
<y> -1 </y>
<z> 0 </z>
</SCAxis>
<inertialAxis ref="rotate">
<axis ref="CA2Sun" />
<rotationAxis ref="SC2CA" />
<rotationAngle units="deg"> 40 </rotationAngle>
</inertialAxis>
</phaseAngle>
</attitude>
</block>
"""
def __init__(self, opportunity_definition=None, mandatory=True, **kwargs):
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
raise NotImplementedError('JUICE_Target_Limb PTR pointing not implemented yet.')
# set required pointing parameters
self.target = ''
self.fixed_offset_x_angle = 0.0
self.fixed_offset_y_angle = 0.0
self.boresight = '' # "MAJIS_Zaxis"
self.secondary_axis = '' # "MAJIS_Yaxis"
super().__init__(opportunity_definition=opportunity_definition, mandatory=mandatory, **kwargs)
# def derive_parameters(self, opportunity_definition): # opportunity definition -> PTR pointing parameters
# target = opportunity_definition.target
# sc_frame_def = opportunity_definition.get_geometry_definition('Simulated_SC_Frame')
# # print(sc_frame_def)
# try:
# x_start = sc_frame_def['parameters']['offset_rotations']['parameters']['x_start']
# except Exception as e:
# x_start = 0.0
# print(f'MOS `x_start` parameter not found, set to {x_start}.')
# try:
# y_start = sc_frame_def['parameters']['offset_rotations']['parameters']['y_start']
# except Exception as e:
# y_start = 0.0
# print(f'MOS `y_start` parameter not found, set to {y_start}.')
#
# fixed_offset_x_angle = y_start
# fixed_offset_y_angle = -x_start
# boresight = opportunity_definition.boresight
# if boresight == 'Z':
# boresight = 'SC_Zaxis'
# # TODO: temporary patch to be removed when JUICE SOC has changed naming from "JUICE_MAJIS_BASE" to "MAJIS_Zaxis"
# if boresight == 'MAJIS_Zaxis':
# boresight = 'JUICE_MAJIS_BASE'
#
# secondary_axis = opportunity_definition.secondary_axis
# return {
# 'target': target,
# 'fixed_offset_x_angle': fixed_offset_x_angle,
# 'fixed_offset_y_angle': fixed_offset_y_angle,
# 'boresight': boresight,
# 'secondary_axis': secondary_axis
# }
#
# @property
# def attitude(self):
# # TODO: Temporary patch to be removed when "MAJIS_Yaxis" definition has been added by SC
# sc_axis = [0, 1, 0]
# offset_ref_axis = [1, 0, 0]
# if self.secondary_axis == 'MAJIS_Yaxis':
# sc_axis = [-0.01425291, 0.99988351, 0.00546049] # majis_axis as defined in JUICE FK on March 2023
# offset_ref_axis = [ 0.99988928, 0.01422921, 0.00435425]
#
# inertial_axis = ''
# if self.target in AGM_NORTH_POLE_DIRS.keys():
# inertial_axis = AGM_NORTH_POLE_DIRS[self.target]
# else:
# raise Exception(f'No SC to North Pole direction defined for {self.target}.')
#
# return Element(
# 'attitude',
# Element('boresight', ref=self.boresight), # eg: 'SC_Zaxis' or 'JUICE_MAJIS_BASE'
# Element('target', ref=self.target),
# Element('offsetRefAxis', {'x': offset_ref_axis[0], 'y': offset_ref_axis[1], 'z': offset_ref_axis[2]}, frame='SC'),
# # Element('offsetRefAxis', ref='SC_Xaxis'),
# Element(
# 'offsetAngles',
# Element('xAngle', self.fixed_offset_x_angle, units='deg'),
# Element('yAngle', self.fixed_offset_y_angle, units='deg'),
# ref='fixed'
# ),
# Element(
# 'phaseAngle',
# Element('SCAxis', {'x': sc_axis[0], 'y': sc_axis[1], 'z': sc_axis[2]}, frame='SC'),
# # Element('SCAxis', {'x': 0, 'y': 1, 'z': 0}, frame='SC'),
# Element('inertialAxis', ref=inertial_axis),
# ref='align'
# ),
# ref='track'
# )
PTR_POINTINGS = {
'juice_target_noa': JUICE_Target_NOA,
'juice_target_npo': JUICE_Target_NPO,
'juice_target_npo_slew': JUICE_Target_NPO_Slew,
'juice_target_npl': JUICE_Target_NPL,
'juice_target_npl_slew': JUICE_Target_NPL_Slew,
'juice_jupiter_ring': JUICE_Jupiter_Ring,
'juice_target_limb': JUICE_Target_Limb
}