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]
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