"""To manage extended body in field of view, like: Earth, moon, sun
"""
import logging
import numpy as np
from astropy.constants import R_earth
from astropy.constants import R_sun
import transforms3d.quaternions as tq
from scipy.linalg import norm
from ecpi.common.mission.attitude import xyz_to_direlev, direlev_to_xyz
from ecpi.common.instru.model_geom import InstruECLAIRs
from ecpi.common.mission.attitude import AttitudeECLAIRs
import ecpi.common.num.array_square_cell as asc
#pylint:disable=C0326
s_logger = logging.getLogger(__name__)
[docs]class ExtentedBodyInFov(object):
"""Informs if an arbitrary astronomical body is in the
filed of view of the ECLAIRs instrument.
"""
def __init__(self):
"""**Constructor**
"""
self._name = "TBD"
self._body_radius_km = None # km
self._pos_body = None
self._instru_ecl = InstruECLAIRs()
self._ecl_att = AttitudeECLAIRs()
self._ecl_att.set_attitude_svom_quater([1.0, 0, 0, 0])
self._pos_sat = np.array([1.0, 0.0, 0.0])
self.inst_half_fov = self._instru_ecl.get_half_fov()
self.fov_ptg = None
self.fov_ptg_side = 0
[docs] def set_quater_svom(self, quater):
"""Set SVOM quaternion
..warning: set position satellite before
:param quater: unit quaternion
:type quater: array(float) of length 4
"""
self._ecl_att = AttitudeECLAIRs()
self._ecl_att.set_attitude_svom_quater(quater)
self._update_geom()
# TODO: COVERAGE
[docs] def set_eclairs_attitude(self, ecl_att):
"""Set ECLAIRs attitude object
:param ecl_att: ECLAIRs instrument attitude object
:type ecl_att: array(float) of length 3
"""
assert isinstance(ecl_att, AttitudeECLAIRs)
self._ecl_att = ecl_att
self._update_geom()
[docs] def set_pos_sat_time(self, pos_sat, cur_time=None):
"""Set satellite position at considered time.
:param pos_sat: satellite position [X, Y, Z] in km
:type pos_sat: array(float) of length 3
:param cur_time: considered time in s. Default is None.
:type cur_time: float
"""
self._time = cur_time
self._pos_sat = pos_sat
self._update_geom()
def _update_geom(self):
"""update body sat dist, limb angle, body pos in fov
"""
self._compute_dist_body_sat()
# don't change order, limb need dist body sat
self._compute_limb_body()
self._compute_pos_fov()
def _compute_dist_body_sat(self):
"""Compute satellite altitude in km.
.. note:: must be redefined in herit classself.flag_update_openfov_map = True
with def of self._pos_body
"""
self._dist_sat_body = norm(self._pos_body)
def _compute_limb_body(self):
"""Compute limb angle in deg.
"""
self._limb = np.arcsin(self._body_radius_km / self._dist_sat_body)
self._cos_limb = np.cos(self._limb)
self._limb = np.rad2deg(self._limb)
# TODO: COVERAGE
[docs] def body_direlev(self):
"""Spherical position in FOV of body in direction and elevation convention
"""
return self._body_direlev
[docs] def body_xyz(self):
"""Cartesian position in FOV of body
"""
return self._body_fov_xyz
def _compute_pos_fov(self):
"""Compute Earth position in term of direction and elevation
(both in deg) in the field of view.
"""
quater = self._ecl_att._q_j2000_instf
sat_body_unit = self._pos_body / self._dist_sat_body
# sat_body is in ref J2000~GCRF cf CNES doc see attitude.py
self._body_fov_xyz = tq.rotate_vector(sat_body_unit, tq.qconjugate(quater))
self._body_direlev = xyz_to_direlev(self._body_fov_xyz[np.newaxis, :], deg=True)[0]
direlev = self._body_direlev
s_logger.debug(f'dir Earth={direlev[0]} deg | elev Earth={direlev[1]} deg')
[docs] def limb_angle(self):
"""
:return: limb angle in degrees
:rtype: float
"""
return self._limb
[docs] def is_in_fov(self):
"""Return whether Earth appears in ECALIRs field of view
:return: if Earth is in FOV
:rtype: bool
"""
limb_angle = self._limb
colat = 90 - self._body_direlev[1]
cond = not (colat > limb_angle + self.inst_half_fov)
s_logger.debug(f'{self._name} in FOV: {cond} \tInstru half fov:'\
' {self.inst_half_fov:.1f} deg')
return cond
def _create_fov_ptg(self, side_fov):
"""Pointing of fov array
.. note::
by Colley Jean-Marc
Pointing of fov array is a constant of instrument for a given size_fov side array.
So I can pre-compute it and store it in fov_ptg attribut:
- float array with unit pointing vector of each position in fov
- type : numpy array (side_fov, side_fov, 3)
:param side_fov: number cell to represent fov (always square array)
"""
self.fov_ptg_side = side_fov
# by defintion
size_sky_pix = self._instru_ecl.pitch_pix_size
# TODO: adapt 199 for another geometry ?
size_fov_cm = 199 * size_sky_pix
size_cell = size_fov_cm / side_fov
array_fov = asc.ArraySquareCell(side_fov, side_fov, size_cell)
array_fov.set_origin_center()
a_fov_pos = array_fov.array_pos_center_cells()
# convert in sky pixel unit
a_fov_pos /= size_sky_pix
# elev in deg
elev, dir_src = self._instru_ecl.skypix_to_elevdir(a_fov_pos[:,:,0].ravel(),
a_fov_pos[:,:,1].ravel())
a_dir_elev = np.array([dir_src, elev]).T
ptg = direlev_to_xyz(a_dir_elev, deg=True)
# unit vector pointing in FOV
self.fov_ptg = ptg.reshape((side_fov, side_fov, 3))
[docs] def compute_openfov_map(self, side_fov=199):
"""Create the open field of view map
.. note:: Definition
OpenFOV map is square boolean array (side_fovxside_fov), True is open (no extended body)
:param side_fov: integer, number of side pixels
:return: True if extended body is NOT present
:rtype: numpy array (side_fov, side_fov) of bool
"""
if self.fov_ptg_side != side_fov:
self._create_fov_ptg(side_fov)
# Optimize note by JM Colley :
# Instead of directly comparing the angle, use the comparison
# of their cosine
# scalar product between unit vector ptg fov and center earth position
cos_ptg_earth = np.dot(self.fov_ptg, self._body_fov_xyz)
# fov map is array test between cos(earth, ptg fov) and cos(limb)
self.open_fov = cos_ptg_earth < self._cos_limb
return self.open_fov
[docs] def compute_occultation_frac(self):
"""Compute occultation fraction in FOV, call open_fov_map() method before
:param side_fov: integer, number of side pixels
:return: earth fraction [0..1]
:rtype: float
"""
earth_frac = 1.0 - np.sum(self.open_fov) / self.open_fov.size
s_logger.debug(f'fraction of fov covered by Earth: {(100*earth_frac):.2f} %')
return earth_frac
[docs]class EarthInFov(ExtentedBodyInFov):
"""Informs if Earth is in the
filed of view of the ECLAIRs instrument.
Inherits from ExtendedBodyinFov class.
"""
def __init__(self):
"""**Constructor**
"""
super().__init__()
self._name = "earth"
self._body_radius_km = 1e-3 * R_earth.value # km
self._pos_sat = np.array([self._body_radius_km, 0, 0])
self._limb = None
self._body_fov_xyz = None
def _compute_dist_body_sat(self):
"""
Must be redefined in herit class with def of self._pos_body
"""
self._pos_body = -self._pos_sat
super()._compute_dist_body_sat()
# Remove pragma: no cover when class is used.
[docs]class MoonInFov(ExtentedBodyInFov): # pragma: no cover
"""Informs if Moon is in the
filed of view of the ECLAIRs instrument.
Inherits from ExtendedBodyinFov class.
"""
def __init__(self):
"""
**Constructor**
.. note :: Moon radius note implemented in astropy.constants
"""
super().__init__()
self._name = "moon"
self._body_radius_km = 1737.0 # km
self._pos_sat = np.array([self._body_radius_km, 0, 0])
def _compute_dist_body_sat(self):
"""
Must be redefined in herit class with def of self._pos_body
"""
# TODO: position de la lune a t dans GCRS
self._pos_body = "TBD"
super()._compute_dist_body_sat()
# Remove pragma: no cover when class is used.
[docs]class SunInFov(ExtentedBodyInFov): # pragma: no cover
"""Informs if Sun is in the
filed of view of the ECLAIRs instrument.
Inherits from ExtendedBodyinFov class.
"""
def __init__(self):
"""
**Constructor**
"""
super().__init__()
self._name = "sun"
self._body_radius_km = 1e-3 * R_sun.value # km
self._pos_sat = np.array([self._body_radius_km, 0, 0])
def _compute_dist_body_sat(self):
"""
Must be redefined in herit class with def of self._pos_body
"""
# TODO: position du soleil a t dans GCRS
self._pos_body = "TBD"
super()._compute_dist_body_sat()