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

from typing import Literal

import astropy.units as u  # type: ignore[import-untyped]
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 shapely import Polygon, points

from ...core.enums.constraint_type import ConstraintType
from ...ephemeris import Ephemeris
from .base import get_slice
from .polygon import PolygonConstraint


[docs] class AltAzConstraint(PolygonConstraint): """ For a given Alt/Az constraint, is a given coordinate inside this constraint? Constraint is both defined by a polygon exclusion region and a minimum and maximum altitude. By default the minimum and maximum altitude values are 0 and 90 degrees respectively. Polygon restriction regions can be combined with minimum and maximum altitude restrictions. Parameters ---------- polygon The polygon defining the exclusion region. min The minimum altitude in degrees. max The maximum altitude in degrees. """
[docs] short_name: str = "AltAz"
[docs] name: Literal[ConstraintType.ALT_AZ] = ConstraintType.ALT_AZ
[docs] polygon: Polygon | None = None
[docs] altitude_min: float | None = Field(default=None, ge=0, le=90)
[docs] altitude_max: float | None = Field(default=None, ge=0, le=90)
[docs] azimuth_min: float | None = Field(default=None, ge=0, lt=360)
[docs] azimuth_max: float | None = Field(default=None, ge=0, lt=360)
[docs] def __call__(self, time: Time, ephemeris: Ephemeris, coordinate: SkyCoord) -> npt.NDArray[np.bool_]: """ Calculate the Alt/Az constraint for a given time, ephemeris, and sky coordinates. Parameters ---------- time : Time The time for which to calculate the constraint. ephemeris : Ephemeris The ephemeris containing the Earth location. coordinate : SkyCoord The sky coordinates to calculate the constraint for. Returns ------- np.ndarray The calculated constraint values as a NumPy array. """ # Get the range of the ephemeris that we're using i = get_slice(time, ephemeris) # Convert the sky coordinates to Alt/Az coordinates assert ephemeris.earth_location is not None self.computed_values.alt_az = coordinate.transform_to( AltAz( obstime=time[i], location=ephemeris.earth_location if ephemeris.earth_location.isscalar else ephemeris.earth_location[i], ) ) # Initialize the constraint array as all False in_constraint = np.zeros(len(self.computed_values.alt_az), dtype=bool) # Calculate the basic Alt/Az min/max constraints if self.altitude_min is not None: in_constraint |= self.computed_values.alt_az.alt < self.altitude_min * u.deg if self.altitude_max is not None: in_constraint |= self.computed_values.alt_az.alt > self.altitude_max * u.deg if self.azimuth_min is not None: in_constraint |= self.computed_values.alt_az.az < self.azimuth_min * u.deg if self.azimuth_max is not None: in_constraint |= self.computed_values.alt_az.az > self.azimuth_max * u.deg # If a polygon is defined, then check if the Alt/Az is inside the polygon if self.polygon is not None: in_constraint |= self.polygon.contains( points(self.computed_values.alt_az.alt, self.computed_values.alt_az.az) ) # Return the value as a scalar or array return in_constraint