Source code for across.tools.footprint.footprint

from __future__ import annotations

from typing import Any

import astropy.units as u  # type: ignore[import-untyped]
import numpy as np
import plotly.graph_objects as go
from astropy.coordinates import Angle, SkyCoord  # type: ignore[import-untyped]
from mocpy import MOC  # type: ignore[import-untyped]

from ..core.plotting import plot_footprint
from ..core.schemas import BaseSchema, Coordinate, HealpixOrder, Polygon, RollAngle
from .projection import project_detector


[docs] class Footprint(BaseSchema): """ Class to represent a astronomical instrument's imaging footprint """
[docs] detectors: list[Polygon]
[docs] def __repr__(self) -> str: """ Overrides the print statement """ statement = f"{self.__class__.__name__}(\n" for detector in self.detectors: statement += f"\t{detector.__class__.__name__}(\n" for coordinate in detector.coordinates: statement += f"\t\t{coordinate.__class__.__name__}({coordinate.ra}, {coordinate.dec}),\n" statement += "\t),\n" statement += ")" return statement
[docs] def __eq__(self, other: object) -> bool: """ Overrides the Footprint equals """ if not isinstance(other, Footprint): return NotImplemented if len(self.detectors) != len(other.detectors): return False else: equivalence: list[bool] = [] for detector_iterable in range(len(self.detectors)): equivalence.append(self.detectors[detector_iterable] == other.detectors[detector_iterable]) return all(equivalence)
[docs] def _to_moc(self, order: int = 10) -> list[MOC]: """ Convert a Footprint into a MOC object. Parameters ---------- order : int, optional HEALPix order (10 = NSIDE 1024), by default 10 Returns ------- list[MOC] MOC object representing the footprint. """ hp_order = HealpixOrder(value=order) vertices: list[SkyCoord] = [] for detector in self.detectors: lon = [coord.ra for coord in detector.coordinates] lat = [coord.dec for coord in detector.coordinates] # Build MOC from polygon vertices.append( SkyCoord( lon, lat, unit="deg", ) ) mocs: list[MOC] = MOC.from_polygons(vertices, max_depth=hp_order.value) return mocs
[docs] def project(self, coordinate: Coordinate, roll_angle: float) -> Footprint: """ Projects the footprint to a new coordinate with a given roll angle. Parameters ---------- coordinate : Coordinate The new center coordinate to project to. roll_angle : float The roll angle in degrees to apply during projection. Returns ------- Footprint The projected footprint. """ angle = RollAngle(value=roll_angle) projected_detectors = [] for detector in self.detectors: projected_detectors.append( project_detector(detector=detector, coordinate=coordinate, roll_angle=angle.value) ) return Footprint(detectors=projected_detectors)
[docs] def query_pixels(self, order: int = 10) -> list[int]: """ Queries the healpix pixels of a footprint at a given order. Parameters ---------- order : int, optional HEALPix order (10 = NSIDE 1024), by default 10 Returns ------- list[int] HEALPix NESTED pixel indices at requested order. """ footprint_moc = self._to_moc(order=order) all_pixels: list[int] = [] for moc in footprint_moc: moc_pixels: np.ndarray[Any, Any] = moc.flatten() all_pixels.extend(moc_pixels.tolist()) return all_pixels
[docs] def contains(self, coordinate: Coordinate, order: int = 10) -> bool: """ Tests if a point exists in a footprint. Parameters ---------- coordinate : Coordinate The coordinate to check for containment. order : int, optional HEALPix order (10 = NSIDE 1024), by default 10 Returns ------- bool True if the coordinate is contained within the footprint, False otherwise. """ footprint_moc = self._to_moc(order=order) coord_lon = Angle([coordinate.ra * u.deg], unit=u.deg) coord_lat = Angle([coordinate.dec * u.deg], unit=u.deg) for moc in footprint_moc: # noqa: SIM110 if any(moc.contains_lonlat(coord_lon, coord_lat)): return True return False
[docs] def plot( self, fig: go.Figure | None = None, name: str | None = None, color: str | None = None, lat_axis_tick: int = 30, lon_axis_tick: int = 60, ) -> go.Figure: """ Method to plot the footprint using plotly. Calls the across-tools plotting core functionality and configures the plot layout to user specifications. Parameters ---------- fig : go.Figure, optional An existing plotly figure to add the footprint to, by default None name : str | None, optional The name to assign to the detector traces, by default None color : str | None, optional The color to assign to the detector traces, by default None lat_axis_tick : int, optional The latitude axis tick interval, by default 30 lon_axis_tick : int, optional The longitude axis tick interval, by default 60 Returns ------- go.Figure The plotly figure containing the footprint plot """ if fig is None: fig = go.Figure() fig.update_layout( title="Footprint Visualization", geo=dict( projection_type="mollweide", showland=False, showcountries=False, showcoastlines=False, lataxis=dict(showgrid=True, dtick=lat_axis_tick), lonaxis=dict(showgrid=True, dtick=lon_axis_tick), ), ) fig = plot_footprint( detectors=[detector.model_dump() for detector in self.detectors], fig=fig, name=name, color=color, ) return fig