Source code for tatc.schemas.instrument

# -*- coding: utf-8 -*-
"""
Object schemas for instruments.

@author: Paul T. Grogan <pgrogan@stevens.edu>
"""

from typing import Optional
from datetime import timedelta

from pydantic import BaseModel, Field
import numpy as np
from skyfield.api import wgs84, EarthSatellite
from skyfield.timelib import Time

from ..constants import de421
from ..utils import compute_min_elevation_angle, field_of_regard_to_swath_width


[docs]class Instrument(BaseModel): """ Remote sensing instrument. """ name: str = Field(..., description="Instrument name.") field_of_regard: float = Field( 180, description="Angular field (degrees) of possible observations (with pointing).", gt=0, le=360, example=50, ) min_access_time: timedelta = Field( timedelta(0), description="Minimum access (integration) time to record an observation.", example=timedelta(seconds=10), ) req_self_sunlit: Optional[bool] = Field( None, description="Required instrument sunlit state for valid observation " + "(`True`: sunlit, `False`: eclipse, `None`: no requirement).", ) req_target_sunlit: Optional[bool] = Field( None, description="Required target sunlit state for valid observation " + "(`True`: sunlit, `False`: eclipse, `None`: no requirement).", )
[docs] def get_swath_width(self, height: float) -> float: """ Gets the instrument swath width projected to the Earth's surface. Args: height (float): Height (meters) above surface of the observation. Returns: float: The observation diameter (meters). """ return field_of_regard_to_swath_width(height, self.field_of_regard)
[docs] def get_min_elevation_angle(self, height: float) -> float: """ Get the minimum elevation angle required to observe a point. Args: height (float): Height (meters) above surface of the observation. Returns: float: The minimum elevation angle (degrees) for observation. """ return compute_min_elevation_angle(height, self.field_of_regard)
[docs] def is_valid_observation(self, sat: EarthSatellite, time: Time) -> bool: """Determines if an instrument can provide a valid observations. Args: sat (EarthSatellite): Satellite hosting this instrument. time (Time): Observation time(s). Returns: bool: `True` if instrument provides a valid observation, otherwise `False`. """ if isinstance(time.tt, float): # scalar is_valid = True if self.req_self_sunlit is not None: # compare requirement to satellite sunlit condition is_valid = is_valid and ( self.req_self_sunlit == sat.at(time).is_sunlit(de421) ) if self.req_target_sunlit is not None: # compute solar altitude angle at sub-satellite point solar_alt = ( (de421["earth"] + wgs84.subpoint_of(sat.at(time))) .at(time) .observe(de421["sun"]) .apparent() .altaz()[0] .degrees ) # compare requirement to sub-satellite point sunlit condition is_valid = is_valid and (self.req_target_sunlit == (solar_alt > 0)) return is_valid # vector is_valid = np.ones(len(time), dtype=bool) if self.req_self_sunlit is not None: # compare requirement to satellite sunlit condition is_valid = np.logical_and( is_valid, self.req_self_sunlit == sat.at(time).is_sunlit(de421) ) if self.req_target_sunlit is not None: # compute solar altitude angle at sub-satellite points solar_alt = np.array( [ (de421["earth"] + wgs84.subpoint_of(sat.at(t))) .at(t) .observe(de421["sun"]) .apparent() .altaz()[0] .degrees for t in time ] ) # compare requirement to sub-satellite point sunlit conditions is_valid = np.logical_and( is_valid, self.req_target_sunlit == (solar_alt > 0) ) return is_valid