Source code for across.tools.visibility.constraints.daytime

from typing import Literal

import numpy as np
import numpy.typing as npt
from astropy.coordinates import AltAz, SkyCoord  # type: ignore[import-untyped]
from astropy.time import Time  # type: ignore[import-untyped]
from pydantic import Field

from ...core.enums import ConstraintType, TwilightType
from ...ephemeris import Ephemeris, GroundEphemeris
from .base import ConstraintABC, get_slice


[docs] class DaytimeConstraint(ConstraintABC): """ Constraint that avoids daytime observations based on twilight definitions. For ground-based telescopes, daytime is defined by the Sun's position relative to the horizon. Different twilight types provide different levels of darkness. For space-based telescopes, daytime refers to periods when the spacecraft is in direct sunlight (requires appropriate ephemeris data). The telescope type (ground vs space) is automatically detected based on the ephemeris type: GroundEphemeris for ground-based, all others for space-based. Parameters ---------- twilight_type : TwilightType The type of twilight to use for defining daytime boundaries. - 'astronomical': Sun 12-18° below horizon (default) - 'nautical': Sun 6-12° below horizon - 'civil': Sun 0-6° below horizon - 'day': Sun above horizon Methods ------- __call__(time, ephemeris, coordinate) Checks if the observation time qualifies as daytime for the given constraints. """
[docs] name: Literal[ConstraintType.DAYTIME] = ConstraintType.DAYTIME
[docs] short_name: Literal["Daytime"] = "Daytime"
[docs] twilight_type: TwilightType = Field( default=TwilightType.ASTRONOMICAL, description="Type of twilight defining daytime boundaries" )
[docs] def __call__(self, time: Time, ephemeris: Ephemeris, coordinate: SkyCoord) -> npt.NDArray[np.bool_]: """ Check if the observation time is during daytime. Parameters ---------- time : Time The time of observation. ephemeris : Ephemeris The ephemeris (used for Earth location and potentially sunlight data). coordinate : SkyCoord The coordinate (not used for daytime calculations). Returns ------- npt.NDArray[np.bool_] Boolean array where True indicates daytime (constraint violated). """ # Auto-detect telescope type based on ephemeris type if isinstance(ephemeris, GroundEphemeris): return self._check_ground_daytime(time, ephemeris) else: # Assume space-based for all other ephemeris types return self._check_space_daytime(time, ephemeris)
[docs] def _check_ground_daytime(self, time: Time, ephemeris: Ephemeris) -> npt.NDArray[np.bool_]: """ Check daytime for ground-based telescopes using Sun altitude. """ # Find the slice of ephemeris data we need i = get_slice(time, ephemeris) # Get Sun position sun_altaz = ephemeris.sun[i].transform_to(AltAz(obstime=time[i], location=ephemeris.earth_location)) self.computed_values.sun_altitude = sun_altaz.alt # Define daytime based on twilight type if self.twilight_type == TwilightType.SUNSET: # Twilight starts after sunset in_constraint = self.computed_values.sun_altitude.deg > -0.866 elif self.twilight_type == TwilightType.CIVIL: # Sun within 6° of horizon (civil twilight) in_constraint = self.computed_values.sun_altitude.deg > -6 elif self.twilight_type == TwilightType.NAUTICAL: # Sun within 12° of horizon (nautical twilight) in_constraint = self.computed_values.sun_altitude.deg > -12 elif self.twilight_type == TwilightType.ASTRONOMICAL: # Sun within 18° of horizon (astronomical twilight) in_constraint = self.computed_values.sun_altitude.deg > -18 return np.asarray(in_constraint, dtype=bool)
[docs] def _check_space_daytime(self, time: Time, ephemeris: Ephemeris) -> npt.NDArray[np.bool_]: """ Check daytime for space-based telescopes using proper eclipse calculations. Determines if the spacecraft is in direct sunlight by calculating whether the Earth is blocking the line of sight to the Sun, accounting for the finite sizes of both Earth and Sun. """ # Find the slice of ephemeris data we need i = get_slice(time, ephemeris) # Calculate angular separation between Sun and Earth centers as seen from spacecraft sun_earth_separation = ephemeris.sun[i].separation(ephemeris.earth[i]) # Spacecraft is in eclipse (not in sunlight) if the angular separation # between Sun and Earth centers is less than the sum of their angular radii # This means the Earth is blocking the Sun from the spacecraft's perspective in_eclipse = sun_earth_separation < ephemeris.earth_radius_angle[i] + ephemeris.sun_radius_angle[i] # For space telescopes, daytime means the spacecraft is in sunlight # So in_constraint is True when NOT in eclipse (i.e., in sunlight) in_constraint = ~in_eclipse return np.asarray(in_constraint, dtype=bool)