# -*- coding: utf-8 -*-
'''Webgeocalc Calculations.'''
import time
from .api import API, Api, ESA_API, JPL_API
from .errors import (CalculationAlreadySubmitted, CalculationConflictAttr,
CalculationFailed, CalculationIncompatibleAttr,
CalculationInvalidAttr, CalculationInvalidValue,
CalculationNotCompleted, CalculationRequiredAttr,
CalculationTimeOut, CalculationUndefinedAttr)
from .types import KernelSetDetails
from .vars import (ABERRATION_CORRECTION, ANGULAR_UNITS, ANGULAR_VELOCITY_REPRESENTATION,
ANGULAR_VELOCITY_UNITS, AXIS, CALCULATION_FAILED_PHASES,
CALCULATION_TYPE, COORDINATE_REPRESENTATION, DIRECTION_VECTOR_TYPE,
INTERVALS, ORIENTATION_REPRESENTATION, OUTPUT_TIME_FORMAT, SHAPE,
STATE_REPRESENTATION, SUB_POINT_TYPE, TIME_FORMAT, TIME_LOCATION,
TIME_STEP_UNITS, TIME_SYSTEM)
APIs = {
'': API,
'JPL': JPL_API,
'ESA': ESA_API,
}
class SetterProperty:
'''Setter property decorator.'''
def __init__(self, func, doc=None):
self.func = func
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
return self.func(obj, value)
[docs]class Calculation:
'''Webgeocalc calculation object.
Parameters
----------
api: str or webgeocalc.Api, optional
WebGeoCalc API endpoint.
By default, if no :py:attr:`api` option is provided,
the query is sent to the ``WGC_URL`` API
(if set in the global environment variables)
or :py:obj:`JPL_API` (if not).
Keyword are also accepted (``JPL`` and ``ESA``).
Custom 3-rd party endpoints can be used
(with their ``URL`` or as a custom :py:class:`webgeocalc.api.Api`).
time_system: str, optional
See: :py:attr:`time_system`
time_format: str, optional
See: :py:attr:`time_format`
verbose: bool, optional
Verbose calculation phase during :py:func:`submit`, :py:func:`update`
and :py:func:`run`.
Other Parameters
----------------
calculation_type: str
See: :py:attr:`calculation_type`
kernels: str, int, [str or/and int]
See: :py:attr:`kernels`
kernel_paths: str, [str]
See: :py:attr:`kernel_paths`
times: str or [str]
See: :py:attr:`times`
intervals: [str, str] or {'startTime': str, 'endTime': str} or [interval, ...]
See: :py:attr:`intervals`
time_step: int
See: :py:attr:`time_step`
time_step_units: str
See: :py:attr:`time_step_units`
sclk_id: int
See: :py:attr:`sclk_id`
output_time_system: str
See: :py:attr:`output_time_system`
output_time_format: str
See: :py:attr:`output_time_format`
output_time_custom_format: str
See: :py:attr:`output_time_custom_format`
output_sclk_id: int
See: :py:attr:`output_sclk_id`
target: str or int
See: :py:attr:`target`
target_frame: str
See: :py:attr:`target_frame`
target_1: str
See: :py:attr:`target_1`
target_2: str
See: :py:attr:`target_2`
shape_1: str
See: :py:attr:`shape_1`
shape_2: str
See: :py:attr:`shape_2`
observer: str or int
See: :py:attr:`observer`
reference_frame: str or int
See: :py:attr:`reference_frame`
frame_1: str ot int
See: :py:attr:`frame_1`
frame_2: str or int
See: :py:attr:`frame_2`
orbiting_body: str
See: :py:attr:`orbiting_body`
center_body: str
See: :py:attr:`center_body`
aberration_correction: str
See: :py:attr:`aberration_correction`
state_representation: str
See: :py:attr:`state_representation`
time_location: str
See: :py:attr:`time_location`
orientation_representation: str
See: :py:attr:`orientation_representation`
axis_1: str
See: :py:attr:`axis_1`
axis_2: str
See: :py:attr:`axis_2`
axis_3: str
See: :py:attr:`axis_3`
angular_units: str
See: :py:attr:`angular_units`
angular_velocity_representation: str
See: :py:attr:`angular_velocity_representation`
angular_velocity_units: str
See: :py:attr:`angular_velocity_units`
coordinate_representation: str
See: :py:attr:`coordinate_representation`
latitude: float
See: :py:attr:`latitude`
longitude: float
See: :py:attr:`longitude`
sub_point_type: str
See: :py:attr:`sub_point_type`
direction_vector_type: str
See: :py:attr:`direction_vector_type`
direction_instrument: str or int
See: :py:attr:`direction_instrument`
direction_frame: str
See: :py:attr:`direction_frame`
direction_frame_axis: str
See: :py:attr:`direction_frame_axis`
direction_vector_x: float
See: :py:attr:`direction_vector_x`
direction_vector_y: float
See: :py:attr:`direction_vector_y`
direction_vector_z: float
See: :py:attr:`direction_vector_z`
direction_vector_ra: float
See: :py:attr:`direction_vector_ra`
direction_vector_dec: float
See: :py:attr:`direction_vector_dec`
Raises
------
CalculationRequiredAttr
If :py:attr:`calculation_type`, :py:attr:`time_system` and
:py:attr:`time_format` are not provided.
CalculationRequiredAttr
If neither :py:attr:`kernels` nor :py:attr:`kernel_paths` is not provided.
CalculationRequiredAttr
If neither :py:attr:`times` nor :py:attr:`intervals` is not provided.
'''
def __init__(self, api='', time_system='UTC',
time_format='CALENDAR', verbose=True, **kwargs):
# Add default parameters to kwargs
kwargs['time_system'] = time_system
kwargs['time_format'] = time_format
# Init parameters
self.params = kwargs
self.__kernels = []
self.id = None
self.phase = 'NOT SUBMITED'
self.columns = None
self.values = None
self.verbose = verbose
# Select API (with caching)
api_key = str(api).upper()
if api_key not in APIs:
APIs[api_key] = api if isinstance(api, Api) else Api(api)
self.api = APIs[api_key]
# Required parameters
self._required(['calculation_type', 'time_system', 'time_format'], kwargs)
if 'kernels' not in kwargs.keys() and 'kernel_paths' not in kwargs.keys():
raise CalculationRequiredAttr('kernels\' or \'kernel_paths')
if 'times' not in kwargs.keys() and 'intervals' not in kwargs.keys():
raise CalculationRequiredAttr('times\' or \'intervals')
# Check and set parameters
for key, value in kwargs.items():
setattr(self, key, value)
def __repr__(self):
return '\n'.join([
f"<{self.__class__.__name__}> Phase: {self.phase} (id: {self.id})"
] + [
f' - {k}: {v}' for k, v in self.payload.items()
])
@staticmethod
def _required(attrs, kwargs):
# Check required arguments
for required in attrs:
if required not in kwargs.keys():
raise CalculationRequiredAttr(required)
@property
def payload(self):
'''Calculation payload parameters *dict* for JSON input in WebGeoCalc format.
Return
------
dict
Payload keys and values.
Example
-------
>>> Calculation(
... kernels = 'Cassini Huygens',
... times = '2012-10-19T08:24:00.000',
... calculation_type = 'STATE_VECTOR',
... target = 'CASSINI',
... observer = 'SATURN',
... reference_frame = 'IAU_SATURN',
... aberration_correction = 'NONE',
... state_representation = 'PLANETOGRAPHIC',
... ).payload # noqa: E501
{'kernels': [{'type': 'KERNEL_SET', 'id': 5}], 'times': ['2012-10-19T08:24:00.000'], ...}
'''
return {k.split('__')[-1]: v for k, v in vars(self).items() if k.startswith('_')}
[docs] def submit(self):
'''Submit calculation parameters and get calculation ``id`` and ``phase``.
Raises
------
CalculationAlreadySubmitted
If the calculation was already submitted (to avoid duplicated submissions).
Example
-------
>>> calc.submit() # noqa: E501 # doctest: +SKIP
[Calculation submit] Phase: QUEUED | POSITION: 6 (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.id # doctest: +SKIP
'8750344d-645d-4e43-b159-c8d88d28aac6'
>>> calc.phase # doctest: +SKIP
'QUEUED | POSITION: 6'
'''
if self.id is not None:
raise CalculationAlreadySubmitted(self.id)
self.id, self.phase = self.api.new_calculation(self.payload)
if self.verbose:
print(f'[Calculation submit] Phase: {self.phase} (id: {self.id})')
[docs] def resubmit(self):
'''Reset calculation ``id`` and re-submit the calculation.
See: :py:func:`submit`.
'''
self.id = None
self.submit()
[docs] def cancel(self):
'''Cancels calculation if already submitted.'''
if self.id is not None:
_, self.phase = self.api.cancel_calculation(self.id)
if self.verbose:
print(f'[Calculation cancellation] Phase: {self.phase} (id: {self.id})')
[docs] def update(self):
'''Update calculation phase ``phase``.
Example
-------
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: QUEUED | POSITION: 3 (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: STARTING (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: LOADING_KERNELS (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # noqa: E501 # doctest: +SKIP
[Calculation update] Phase: CALCULATING (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
>>> calc.update() # doctest: +SKIP
[Calculation update] Phase: COMPLETE (id: 8750344d-645d-4e43-b159-c8d88d28aac6)
'''
if self.id is None:
self.submit()
else:
_, self.phase = self.api.phase_calculation(self.id)
if self.verbose:
print(f'[Calculation update] Phase: {self.phase} (id: {self.id})')
@property
def results(self):
'''Gets the results of a calculation, if its phase is `COMPLETE`.
Return
------
dict
Calculation results as *dict* based on output columns. If multiple ``times``
or ``intervals`` are used, the value of the *dict* will be an array.
See examples.
Raises
------
CalculationNotCompleted
If calculation phase is not `COMPLETE`.
Examples
--------
>>> calc.results # doctest: +SKIP
{'DATE': '2012-10-19 09:00:00.000000 UTC',
'DISTANCE': 764142.63776247,
'SPEED': 111.54765899,
'X': 298292.85744169,
'Y': -651606.58468976,
'Z': 265224.81187627,
'D_X_DT': -98.8032491,
'D_Y_DT': -51.73211296,
'D_Z_DT': -2.1416539,
'TIME_AT_TARGET': '2012-10-19 08:59:57.451094 UTC',
'LIGHT_TIME': 2.54890548}
>>> ang_sep = AngularSeparation(
... kernel_paths = ['pds/wgc/kernels/lsk/naif0012.tls', 'pds/wgc/kernels/spk/de430.bsp'],
... times = ['2012-10-19T08:24:00.000', '2012-10-19T09:00:00.000'],
... target_1 = 'VENUS',
... target_2 = 'MERCURY',
... observer = 'SUN',
... verbose = False,
... ) # noqa: E501
>>> ang_sep.submit() # doctest: +SKIP
>>> ang_sep.results # doctest: +SKIP
{'DATE': ['2012-10-19 08:24:00.000000 UTC', '2012-10-19 09:00:00.000000 UTC'],
'ANGULAR_SEPARATION': [175.17072258, 175.18555938]}
'''
if self.phase != 'COMPLETE':
raise CalculationNotCompleted(self.phase)
if self.columns is None or self.values is None:
self.columns, self.values = self.api.results_calculation(self.id)
if len(self.values) == 1:
data = self.values[0]
else:
# Transpose values array
data = [[row[i] for row in self.values] for i in range(len(self.columns))]
return {column.outputID: value for column, value in zip(self.columns, data)}
[docs] def run(self, timeout=30, sleep=1):
'''Submit, update and retrive calculation results at once.
See: :py:func:`submit`, :py:func:`update` and :py:attr:`results`.
Parameters
----------
timeout: int, optional
Auto-update time out (in seconds).
sleep: int, optional
Sleep duration (in seconds) between each update.
Raises
------
CalculationTimeOut
If calculation reach the timeout duration.
'''
if self.columns is not None and self.values is not None:
return self.results
for _ in range(int(timeout / sleep)):
self.update()
if self.phase == 'COMPLETE':
return self.results
if self.phase in CALCULATION_FAILED_PHASES:
raise CalculationFailed(self.phase)
time.sleep(sleep)
raise CalculationTimeOut(timeout, sleep)
[docs] @SetterProperty
def calculation_type(self, val):
'''The type of calculation to perform.
Parameters
----------
calculation_type: str
One of the following:
- STATE_VECTOR
- ANGULAR_SEPARATION
- ANGULAR_SIZE
- SUB_OBSERVER_POINT
- SUB_SOLAR_POINT
- ILLUMINATION_ANGLES
- SURFACE_INTERCEPT_POINT
- OSCULATING_ELEMENTS
- FRAME_TRANSFORMATION
- TIME_CONVERSION
- GF_COORDINATE_SEARCH
- GF_ANGULAR_SEPARATION_SEARCH
- GF_DISTANCE_SEARCH
- GF_SUB_POINT_SEARCH
- GF_OCCULTATION_SEARCH
- GF_TARGET_IN_INSTRUMENT_FOV_SEARCH
- GF_SURFACE_INTERCEPT_POINT_SEARCH
- GF_RAY_IN_FOV_SEARCH
Note
----
This parameters will be auto-filled for specific calculation sub-classes.
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in CALCULATION_TYPE:
self.__calculationType = val
else:
raise CalculationInvalidAttr('calculation_type', val, CALCULATION_TYPE)
[docs] @SetterProperty
def kernels(self, kernel_sets):
'''Add kernel sets.
Parameters
----------
kernels: str, int, [str or/and int]
Kernel set(s) to be used for the calculation::
[{'type': 'KERNEL_SET', 'id': 5}, ...]
'''
self.__kernels += [
self._kernel_id_obj(kernel_sets)
] if isinstance(
kernel_sets,
(int, str, KernelSetDetails)
) else list(map(self._kernel_id_obj, kernel_sets))
def _kernel_id_obj(self, kernel_set):
# Payload kernel set object
return {"type": "KERNEL_SET", "id": self.api.kernel_set_id(kernel_set)}
[docs] @SetterProperty
def kernel_paths(self, paths):
'''Add path for individual kernel paths.
Parameters
----------
kernel_paths: str, [str]
Kernel path(s) to be used for the calculation::
[{'type': 'KERNEL', 'path': 'pds/wgc/kernels/lsk/naif0012.tls'}, ...]
'''
self.__kernels += [
self._kernel_path_obj(paths)
] if isinstance(paths, str) else list(map(self._kernel_path_obj, paths))
@staticmethod
def _kernel_path_obj(server_path):
# Payloaf individual kernel path object
return {"type": "KERNEL", "path": server_path}
[docs] @SetterProperty
def times(self, times):
'''Calculation input times.
Parameters
----------
times: str or [str]
String or array of strings representing the time points
that should be used in the calculation.
Raises
------
CalculationConflictAttr
Either this parameter or the py:attr:`intervals` parameter must be supplied.
'''
self.__times = [times] if isinstance(times, str) else times
if 'intervals' in self.params.keys():
raise CalculationConflictAttr('times', 'intervals')
[docs] @SetterProperty
def intervals(self, intervals):
'''Calculation input intervals.
Parameters
----------
intervals: [str, str] or {'startTime': str, 'endTime': str} or [interval, ...]
An array of objects with startTime and endTime parameters,
representing the time intervals used for the calculation.
Warning
-------
Either this parameter or the :py:attr:`times` parameter must be supplied.
Raises
------
CalculationInvalidAttr
If :py:attr:intervals` input format is invalid.
For example, if :py:attr:intervals` is provided an dict,
``startTime`` and ``endTime`` must be present.
CalculationUndefinedAttr
If this parameter is used, :py:attr:`time_step` must also be supplied.
'''
if isinstance(intervals, dict):
self.__intervals = [self._interval(intervals)]
elif isinstance(intervals, list) and len(intervals) > 2:
self.__intervals = list(map(self._interval, intervals))
elif isinstance(intervals, list) and len(intervals) == 2:
if isinstance(intervals[0], str) and isinstance(intervals[1], str):
self.__intervals = [self._interval(intervals)]
elif isinstance(intervals[0], (dict, list)) and \
isinstance(intervals[1], (dict, list)):
self.__intervals = [
self._interval(intervals[0]), self._interval(intervals[1])]
else:
raise CalculationInvalidAttr('intervals', intervals, INTERVALS)
elif isinstance(intervals, list) and len(intervals) == 1:
if isinstance(intervals[0], (dict, list)):
self.__intervals = [self._interval(intervals[0])]
else:
raise CalculationInvalidAttr('intervals', intervals, INTERVALS)
else:
raise CalculationInvalidAttr('intervals', intervals, INTERVALS)
if 'time_step' not in self.params.keys():
raise CalculationUndefinedAttr('intervals', intervals, 'time_step')
@staticmethod
def _interval(interval):
# Parse interval object
if not len(interval) == 2:
raise CalculationInvalidAttr('interval', interval, INTERVALS[:2])
if isinstance(interval, dict):
if 'startTime' in interval.keys() and 'endTime' in interval.keys():
return interval
raise CalculationInvalidAttr('interval', interval, INTERVALS[:2])
return {'startTime': str(interval[0]), 'endTime': str(interval[1])}
[docs] @SetterProperty
def time_step(self, val):
'''Time step for intervals.
Parameters
----------
time_step: int
Number of steps parameter used for time series or
geometry finder calculations.
Raises
------
CalculationConflictAttr
If :py:attr:`times` attribute is supplied.
CalculationUndefinedAttr
If :py:attr:`time_step_units` is not supplied.
'''
self.__timeStep = int(val)
if 'times' in self.params.keys():
raise CalculationConflictAttr('time_step', 'times')
if 'time_step_units' not in self.params.keys():
raise CalculationUndefinedAttr('time_step', val, 'time_step_units')
[docs] @SetterProperty
def time_step_units(self, val):
'''Time step units.
Parameters
----------
time_step_units: str
One of the following:
- SECONDS
- MINUTES
- HOURS
- DAYS
- EQUAL_INTERVALS
Raises
-------
CalculationInvalidAttr
If the value provided is invalid.
CalculationConflictAttr
If :py:attr:`times` attribute is supplied.
CalculationUndefinedAttr
If :py:attr:`time_step` is not supplied.
'''
if val in TIME_STEP_UNITS:
self.__timeStepUnits = val
else:
raise CalculationInvalidAttr('time_step_units', val, TIME_STEP_UNITS)
if 'times' in self.params.keys():
raise CalculationConflictAttr('time_step', 'times')
if 'time_step' not in self.params.keys():
raise CalculationUndefinedAttr('time_step_units', val, 'time_step')
[docs] @SetterProperty
def time_system(self, val):
'''Time System.
Parameters
----------
time_system: str
One of the following:
- UTC
- TDB
- TDT
- SPACECRAFT_CLOCK
Raises
-------
CalculationInvalidAttr
If the value provided is invalid.
CalculationUndefinedAttr
If ``SPACECRAFT_CLOCK`` is selected, but
:py:attr:`sclk_id` attribute is not provided.
'''
if val in TIME_SYSTEM:
self.__timeSystem = val
else:
raise CalculationInvalidAttr('time_system', val, TIME_SYSTEM)
if val == 'SPACECRAFT_CLOCK' and 'sclk_id' not in self.params.keys():
raise CalculationUndefinedAttr('time_system', 'SPACECRAFT_CLOCK', 'sclk_id')
[docs] @SetterProperty
def sclk_id(self, val):
'''Spacecraft clock kernel id.
Parameters
----------
sclk_id: int
Spacecraft clock kernel id.
Raises
------
CalculationRequiredAttr
If :py:attr:`time_system` is not provided.
CalculationIncompatibleAttr
If :py:attr:`time_system` is not ``SPACECRAFT_CLOCK``.
'''
self.__sclkId = int(val)
self._required(['time_system'], self.params)
if self.params['time_system'] != 'SPACECRAFT_CLOCK':
raise CalculationIncompatibleAttr(
'sclk_id', val, 'time_system',
self.params['time_system'], ['SPACECRAFT_CLOCK'])
[docs] @SetterProperty
def output_time_system(self, val):
'''The time system for results output times.
Parameters
----------
output_time_system: str
One of the following:
- UTC
- TDB
- TDT
- SPACECRAFT_CLOCK
Raises
-------
CalculationInvalidAttr
If the value provided is invalid.
CalculationUndefinedAttr
If ``SPACECRAFT_CLOCK`` is selected, but
:py:attr:`output_sclk_id` attribute is not provided.
'''
if val in TIME_SYSTEM:
self.__outputTimeSystem = val
else:
raise CalculationInvalidAttr('output_time_system', val, TIME_SYSTEM)
if val == 'SPACECRAFT_CLOCK' and 'output_sclk_id' not in self.params.keys():
raise CalculationUndefinedAttr(
'output_time_system', 'SPACECRAFT_CLOCK', 'output_sclk_id')
[docs] @SetterProperty
def output_sclk_id(self, val):
'''The output spacecraft clock kernel id.
Parameters
----------
output_sclk_id: int
Spacecraft clock kernel id.
Raises
------
CalculationRequiredAttr
If :py:attr:`output_time_system` is not provided.
CalculationIncompatibleAttr
If :py:attr:`output_time_system` is not ``SPACECRAFT_CLOCK``.
'''
self.__outputSclkId = int(val)
self._required(['output_time_system'], self.params)
if self.params['output_time_system'] != 'SPACECRAFT_CLOCK':
raise CalculationIncompatibleAttr(
'output_sclk_id', val, 'output_time_system',
self.params['output_time_system'], ['SPACECRAFT_CLOCK'])
[docs] @SetterProperty
def target(self, val):
'''Target body.
Parameters
----------
target: str or int
The target body ``name`` or ``id`` from :py:func:`API.bodies`.
'''
self.__target = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def target_frame(self, val):
'''The target body-fixed reference frame name.
Parameters
----------
target_frame: str
Reference frame ``name``.
'''
self.__targetFrame = val
[docs] @SetterProperty
def target_1(self, val):
'''The target body the first body.
Parameters
----------
target_1: str
Target body ``name`` or ``id``.
'''
self.__target1 = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def target_2(self, val):
'''The target body the second body.
Parameters
----------
target_2: str
Target body ``name`` or ``id``.
'''
self.__target2 = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def shape_1(self, val):
'''The shape to use for the first body.
Parameters
----------
shape_1: str
One of:
- POINT
- SPHERE
Raises
-------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in SHAPE:
self.__shape1 = val
else:
raise CalculationInvalidAttr('shape_1', val, SHAPE)
[docs] @SetterProperty
def shape_2(self, val):
'''The shape to use for the second body.
Parameters
----------
shape_2: str
One of:
- POINT
- SPHERE
Raises
-------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in SHAPE:
self.__shape2 = val
else:
raise CalculationInvalidAttr('shape_2', val, SHAPE)
[docs] @SetterProperty
def observer(self, val):
'''The observing body.
Parameters
----------
observer: str or int
The oberving body ``name`` or ``id`` from :py:func:`API.bodies`.
'''
self.__observer = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def reference_frame(self, val):
'''The reference frame.
Parameters
----------
reference_frame: str or int
The reference frame ``name`` or ``id`` from :py:func:`API.frames`.
'''
self.__referenceFrame = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def frame_1(self, val):
'''The first reference frame.
Parameters
----------
frame_1: str or int
The reference frame ``name`` or ``id`` from :py:func:`API.frames`.
'''
self.__frame1 = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def frame_2(self, val):
'''The second reference frame.
Parameters
----------
frame_2: str or int
The reference frame ``name`` or ``id`` from :py:func:`API.frames`.
'''
self.__frame2 = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def orbiting_body(self, val):
'''The SPICE orbiting body.
Parameters
----------
orbiting_body: str or int
SPICE body ``name`` or ``id`` for the orbiting body.
'''
self.__orbitingBody = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def center_body(self, val):
'''
The SPICE body center of motion.
Parameters
----------
center_body: str or int
SPICE body ``name`` or ``id`` for the body that is the center of motion.
'''
self.__centerBody = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def aberration_correction(self, val):
'''SPICE aberration correction.
Parameters
----------
aberration_correction: str
The SPICE aberration correction string. One of:
- NONE
- LT
- LT+S
- CN
- CN+S
- XLT
- XLT+S
- XCN
- XCN+S
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in ABERRATION_CORRECTION:
self.__aberrationCorrection = val
else:
raise CalculationInvalidAttr(
'aberration_correction', val, ABERRATION_CORRECTION)
[docs] @SetterProperty
def state_representation(self, val):
'''State representation.
Parameters
----------
state_representation: str
One of:
- RECTANGULAR
- RA_DEC
- LATITUDINAL (planetocentric)
- PLANETODETIC
- PLANETOGRAPHIC
- CYLINDRICAL
- SPHERICAL
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in STATE_REPRESENTATION:
self.__stateRepresentation = val
else:
raise CalculationInvalidAttr(
'state_representation', val, STATE_REPRESENTATION)
[docs] @SetterProperty
def time_location(self, val):
'''The frame for the input times.
Parameters
----------
time_location: str
One of:
- FRAME1
- FRAME2
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
Warning
-------
NAIF API docs::
`Only needed if aberrationCorrection is not NONE.`
Required even when :py:attr:`aberration_correction` is ``NONE``.
'''
if val in TIME_LOCATION:
self.__timeLocation = val
else:
raise CalculationInvalidAttr('time_location', val, TIME_LOCATION)
[docs] @SetterProperty
def orientation_representation(self, val):
'''The representation of the result transformation.
Parameters
----------
orientation_representation: str
Orientation result transformation. One of:
- EULER_ANGLES
- ANGLE_AND_AXIS
- SPICE_QUATERNION
- OTHER_QUATERNION
- MATRIX_ROW_BY_ROW
- MATRIX_FLAGGED
- MATRIX_ALL_ONE_ROW
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in ORIENTATION_REPRESENTATION:
self.__orientationRepresentation = val
else:
raise CalculationInvalidAttr(
'orientation_representation', val, ORIENTATION_REPRESENTATION)
[docs] @SetterProperty
def axis_1(self, val):
'''The first axis for Euler angle rotation.
Parameters
----------
axis_1: str
Axis name. See: :py:func:`axis`.
'''
self.__axis1 = self.axis('axis_1', val)
[docs] @SetterProperty
def axis_2(self, val):
'''The second axis for Euler angle rotation.
Parameters
----------
axis_3: str
Axis name. See: :py:func:`axis`.
'''
self.__axis2 = self.axis('axis_2', val)
[docs] @SetterProperty
def axis_3(self, val):
'''The third axis for Euler angle rotation.
Parameters
----------
axis_3: str
Axis name. See: :py:func:`axis`.
'''
self.__axis3 = self.axis('axis_3', val)
[docs] def axis(self, name, val):
'''Axis for Euler angle rotation.
Parameters
----------
name: str
Axis name. One of:
- X
- Y
- Z
val: float
Value on the axis.
Return
------
float
Value on the axis.
Raises
------
CalculationInvalidAttr
If the ``name`` provided is invalid.
CalculationUndefinedAttr
If :py:attr:`orientation_representation` is not supplied.
CalculationIncompatibleAttr
If :py:attr:`orientation_representation` is not ``EULER_ANGLES``.
'''
if 'orientation_representation' not in self.params.keys():
raise CalculationUndefinedAttr(name, val, 'orientation_representation')
if self.params['orientation_representation'] != 'EULER_ANGLES':
raise CalculationIncompatibleAttr(
name, val, 'orientation_representation',
self.params['orientation_representation'], ['EULER_ANGLES'])
if val in AXIS:
return val
raise CalculationInvalidAttr(name, val, AXIS)
[docs] @SetterProperty
def angular_units(self, val):
'''The angular units.
Parameters
----------
angular_units: str
The angular units used for the angle of rotation. One of:
- deg
- rad
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
CalculationUndefinedAttr
If :py:attr:`orientation_representation` is not supplied.
CalculationIncompatibleAttr
If :py:attr:`orientation_representation` is
not `EULER_ANGLES` or `ANGLE_AND_AXIS`.
'''
if val in ANGULAR_UNITS:
self.__angularUnits = val
else:
raise CalculationInvalidAttr('angular_units', val, ANGULAR_UNITS)
if 'orientation_representation' not in self.params.keys():
raise CalculationUndefinedAttr(
'angular_units', val, 'orientation_representation')
if not self.params['orientation_representation'] in ['EULER_ANGLES',
'ANGLE_AND_AXIS']:
raise CalculationIncompatibleAttr(
'angular_units', val, 'orientation_representation',
self.params['orientation_representation'],
['EULER_ANGLES', 'ANGLE_AND_AXIS'])
[docs] @SetterProperty
def angular_velocity_representation(self, val):
'''Angular velocity representation.
Parameters
----------
angular_velocity_representation: str
The representation of angular velocity in the output. One of:
- NOT_INCLUDED
- VECTOR_IN_FRAME1
- VECTOR_IN_FRAME2
- EULER_ANGLE_DERIVATIVES
- MATRIX
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in ANGULAR_VELOCITY_REPRESENTATION:
self.__angularVelocityRepresentation = val
else:
raise CalculationInvalidAttr(
'angular_velocity_representation', val, ANGULAR_VELOCITY_REPRESENTATION)
[docs] @SetterProperty
def angular_velocity_units(self, val):
'''The units for the angular velocity.
Parameters
----------
angular_velocity_units: str
One of:
- deg/s
- rad/s
- RPM
- Unitary *(ie, Unit vector)*
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
CalculationUndefinedAttr
If :py:attr:`angular_velocity_representation` is not supplied.
CalculationIncompatibleAttr
If :py:attr:`angular_velocity_representation` is not ``VECTOR_IN_FRAME1``,
``VECTOR_IN_FRAME2`` or ``EULER_ANGLE_DERIVATIVES``
CalculationIncompatibleAttr
If ``Unitary`` selected but :py:attr:`angular_velocity_representation` is not
``VECTOR_IN_FRAME1`` or ``VECTOR_IN_FRAME2``.
'''
if val in ANGULAR_VELOCITY_UNITS:
self.__angularVelocityUnits = val
else:
raise CalculationInvalidAttr(
'angular_velocity_units', val, ANGULAR_VELOCITY_UNITS)
if 'angular_velocity_representation' not in self.params.keys():
raise CalculationUndefinedAttr(
'angular_velocity_units', val, 'angular_velocity_representation')
choices = ['VECTOR_IN_FRAME1', 'VECTOR_IN_FRAME2', 'EULER_ANGLE_DERIVATIVES']
if not self.params['angular_velocity_representation'] in choices:
raise CalculationIncompatibleAttr(
'angular_velocity_units', val, 'angular_velocity_representation',
self.params['angular_velocity_representation'], choices)
if val == 'Unitary' and not self.params['angular_velocity_representation'] in \
['VECTOR_IN_FRAME1', 'VECTOR_IN_FRAME2']:
raise CalculationIncompatibleAttr(
'angular_velocity_units', val, 'angular_velocity_representation',
self.params['angular_velocity_representation'],
['VECTOR_IN_FRAME1', 'VECTOR_IN_FRAME2'])
[docs] @SetterProperty
def coordinate_representation(self, val):
'''Coordinate Representation.
Parameters
----------
coordinate_representation: str
One of:
- LATITUDINAL *(planetocentric)*
- PLANETODETIC
- PLANETOGRAPHIC
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in COORDINATE_REPRESENTATION:
self.__coordinateRepresentation = val
else:
raise CalculationInvalidAttr(
'coordinate_representation', val, COORDINATE_REPRESENTATION)
[docs] @SetterProperty
def latitude(self, val):
'''Latitude of the surface point.
Parameters
----------
latitude: float
Latitude angle (in degrees).
Raises
------
CalculationInvalidValue
If ``latitude`` not in [-90, +90] range.
'''
if -90 <= val <= 90:
self.__latitude = val
else:
raise CalculationInvalidValue('latitude', val, -90, 90)
[docs] @SetterProperty
def longitude(self, val):
'''Longitude of the surface point.
Parameters
----------
longitude: float
Longitude angle (in degrees).
Raises
------
CalculationInvalidValue
If ``longitude`` not in [-180, +180] range.
'''
if -180 <= val <= 180:
self.__longitude = val
else:
raise CalculationInvalidValue('longitude', val, -180, 180)
[docs] @SetterProperty
def sub_point_type(self, val):
'''Sub-observer point.
Parameters
----------
sub_point_type: str
The method of finding the sub-observer point, as in
the SPICE ``subpnt()`` API call. One of:
- Near point: ellipsoid
- Intercept: ellipsoid
- NADIR/DSK/UNPRIORITIZED
- INTERCEPT/DSK/UNPRIORITIZED
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
'''
if val in SUB_POINT_TYPE:
self.__subPointType = val
else:
raise CalculationInvalidAttr('sub_point_type', val, SUB_POINT_TYPE)
[docs] @SetterProperty
def direction_vector_type(self, val):
'''Type of ray's direction vector.
Parameters
----------
direction_vector_type: str
Type of vector to be used as the ray direction. One of:
- INSTRUMENT_BORESIGHT *(the instrument boresight vector)*
- INSTRUMENT_FOV_BOUNDARY_VECTORS *(the instrument field-of-view
boundary vectors)*
- REFERENCE_FRAME_AXIS *(an axis of the specified reference frame)*
- VECTOR_IN_INSTRUMENT_FOV *(a vector in the reference frame of the
specified instrument)*
- VECTOR_IN_REFERENCE_FRAME *(a vector in the specified reference frame)*
Raises
------
CalculationInvalidAttr
If the value provided is invalid.
CalculationRequiredAttr
If this parameter is ``INSTRUMENT_BORESIGHT``,
``INSTRUMENT_FOV_BOUNDARY_VECTORS`` or ``VECTOR_IN_INSTRUMENT_FOV``
but :py:attr:`direction_instrument` is not provided.
CalculationRequiredAttr
If this parameter is ``REFERENCE_FRAME_AXIS`` or ``VECTOR_IN_REFERENCE_FRAME``
but :py:attr:`direction_frame` is not provided.
CalculationRequiredAttr
If this parameter is ``REFERENCE_FRAME_AXIS`` but
:py:attr:`direction_frame_axis` is not provided.
CalculationUndefinedAttr
If this parameter is ``VECTOR_IN_INSTRUMENT_FOV``
or ``VECTOR_IN_REFERENCE_FRAME`` but neither :py:attr:`direction_vector_x`,
:py:attr:`direction_vector_y` and :py:attr:`direction_vector_z`
nor :py:attr:`direction_vector_ra` and :py:attr:`direction_vector_dec`
are provided.
'''
if val in DIRECTION_VECTOR_TYPE:
self.__directionVectorType = val
else:
raise CalculationInvalidAttr(
'direction_vector_type', val, DIRECTION_VECTOR_TYPE)
if val in ['INSTRUMENT_BORESIGHT', 'INSTRUMENT_FOV_BOUNDARY_VECTORS',
'VECTOR_IN_INSTRUMENT_FOV']:
self._required(['direction_instrument'], self.params)
elif val in ['REFERENCE_FRAME_AXIS', 'VECTOR_IN_REFERENCE_FRAME']:
self._required(['direction_frame'], self.params)
if val == 'REFERENCE_FRAME_AXIS':
self._required(['direction_frame_axis'], self.params)
keys = self.params.keys()
if val in ['VECTOR_IN_INSTRUMENT_FOV', 'VECTOR_IN_REFERENCE_FRAME']:
if not(
'direction_vector_x' in keys and # noqa: W504
'direction_vector_y' in keys and # noqa: W504
'direction_vector_z' in keys
) and not(
'direction_vector_ra' in keys and # noqa: W504
'direction_vector_dec' in keys
):
raise CalculationUndefinedAttr(
'direction_vector_type', val,
"direction_vector_x/y/z' or 'direction_vector_ra/dec")
[docs] @SetterProperty
def direction_instrument(self, val):
'''Direction imnstrument.
Parameters
----------
direction_instrument: str or int
The instrument ``name`` or ``id``.
Raises
------
CalculationUndefinedAttr
If :py:attr:`direction_vector_type` is not provided.
CalculationIncompatibleAttr
If :py:attr:`direction_vector_type` not in ``INSTRUMENT_BORESIGHT``,
``INSTRUMENT_FOV_BOUNDARY_VECTORS`` or ``VECTOR_IN_INSTRUMENT_FOV``.
'''
if 'direction_vector_type' not in self.params.keys():
raise CalculationUndefinedAttr(
'direction_instrument', val, 'direction_vector_type')
choices = ['INSTRUMENT_BORESIGHT', 'INSTRUMENT_FOV_BOUNDARY_VECTORS',
'VECTOR_IN_INSTRUMENT_FOV']
if not self.params['direction_vector_type'] in choices:
raise CalculationIncompatibleAttr(
'direction_instrument', val, 'direction_vector_type',
self.params['direction_vector_type'], choices)
self.__directionInstrument = val if isinstance(val, int) else val.upper()
[docs] @SetterProperty
def direction_frame(self, val):
'''Direction vector reference frame.
Parameters
----------
direction_frame: str
The vector's reference frame ``name``.
Raises
------
CalculationUndefinedAttr
If :py:attr:`direction_vector_type` is not provided.
CalculationIncompatibleAttr
If :py:attr:`direction_vector_type` is not in ``REFERENCE_FRAME_AXIS``
or ``VECTOR_IN_REFERENCE_FRAME``.
'''
if 'direction_vector_type' not in self.params.keys():
raise CalculationUndefinedAttr(
'direction_frame', val, 'direction_vector_type')
choices = ['REFERENCE_FRAME_AXIS', 'VECTOR_IN_REFERENCE_FRAME']
if not self.params['direction_vector_type'] in choices:
raise CalculationIncompatibleAttr(
'direction_frame', val, 'direction_vector_type',
self.params['direction_vector_type'], choices)
self.__directionFrame = val
[docs] @SetterProperty
def direction_frame_axis(self, val):
'''The vector's reference frame axis name.
Parameters
----------
direction_frame: str
The vector's reference frame axis. One of:
- X
- Y
- Z
Raises
------
CalculationUndefinedAttr
If :py:attr:`direction_vector_type` is not provided.
CalculationIncompatibleAttr
If :py:attr:`direction_vector_type` is not ``REFERENCE_FRAME_AXIS``.
CalculationInvalidAttr
If the value provided is invalid.
'''
if 'direction_vector_type' not in self.params.keys():
raise CalculationUndefinedAttr(
'direction_frame_axis', val, 'direction_vector_type')
if self.params['direction_vector_type'] != 'REFERENCE_FRAME_AXIS':
raise CalculationIncompatibleAttr(
'direction_frame_axis', val, 'direction_vector_type',
self.params['direction_vector_type'], ['REFERENCE_FRAME_AXIS'])
if val in AXIS:
self.__directionFrameAxis = val
else:
raise CalculationInvalidAttr('direction_frame_axis', val, AXIS)
[docs] def direction_vector(self, axis, val):
'''Direction vector coordinate.
Parameters
----------
axis: str
Axis name.
val: float
Value on the axis.
Raises
------
CalculationUndefinedAttr
If :py:attr:`direction_vector_type` is not provided.
CalculationIncompatibleAttr
If :py:attr:`direction_vector_type` is not in ``VECTOR_IN_INSTRUMENT_FOV``
or ``VECTOR_IN_REFERENCE_FRAME``.
'''
if 'direction_vector_type' not in self.params.keys():
raise CalculationUndefinedAttr(
'direction_vector_' + axis, val, 'direction_vector_type')
choices = ['VECTOR_IN_INSTRUMENT_FOV', 'VECTOR_IN_REFERENCE_FRAME']
if not self.params['direction_vector_type'] in choices:
raise CalculationIncompatibleAttr(
'direction_vector_' + axis, val, 'direction_vector_type',
self.params['direction_vector_type'], choices)
return val
[docs] @SetterProperty
def direction_vector_x(self, val):
'''The X ray's direction vector coordinate.
Parameters
----------
direction_vector_x: float
Direction x-coordinate. See :py:func:`direction_vector`.
'''
self.__directionVectorX = self.direction_vector('x', val)
[docs] @SetterProperty
def direction_vector_y(self, val):
'''The Y ray's direction vector coordinate.
Parameters
----------
direction_vector_y: float
Direction y-coordinate. See :py:func:`direction_vector`.
'''
self.__directionVectorY = self.direction_vector('y', val)
[docs] @SetterProperty
def direction_vector_z(self, val):
'''The Z ray's direction vector coordinate.
Parameters
----------
direction_vector_z: float
Direction z-coordinate. See :py:func:`direction_vector`.
'''
self.__directionVectorZ = self.direction_vector('z', val)
[docs] @SetterProperty
def direction_vector_ra(self, val):
'''The right-ascenssion ray's direction vector coordinate.
Parameters
----------
direction_vector_ra: float
Direction RA-coordinate. See :py:func:`direction_vector`.
'''
self.__directionVectorRA = self.direction_vector('ra', val)
[docs] @SetterProperty
def direction_vector_dec(self, val):
'''The declination ray's direction vector coordinate.
Parameters
----------
direction_vector_dec: float
Direction DEC-coordinate. See :py:func:`direction_vector`.
'''
self.__directionVectorDec = self.direction_vector('dec', val)