Source code for stilt.observations.geometry

"""Observation geometry models."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal

from stilt.config import VerticalReference, validate_vertical_reference

GeometryKind = Literal["point", "polygon", "ellipse", "swath_cell", "line"]


[docs] @dataclass(slots=True) class ViewingGeometry: """Solar/view geometry attached to one observation.""" solar_zenith_angle: float | None = None viewing_zenith_angle: float | None = None solar_azimuth_angle: float | None = None viewing_azimuth_angle: float | None = None relative_azimuth_angle: float | None = None scan_angle: float | None = None glint_angle: float | None = None
[docs] @dataclass(slots=True) class HorizontalGeometry: """Horizontal measurement geometry for one observation.""" kind: GeometryKind center_longitude: float center_latitude: float vertices: list[tuple[float, float]] = field(default_factory=list) major_axis_km: float | None = None minor_axis_km: float | None = None orientation_deg: float | None = None along_track_index: int | None = None across_track_index: int | None = None swath: int | str | None = None resolution_km: tuple[float, float] | None = None metadata: dict[str, Any] = field(default_factory=dict)
[docs] @dataclass(slots=True) class LineOfSight: """Physical line-of-sight geometry and sampling choices for one observation.""" altitude_ref: VerticalReference = "msl" altitude_levels: list[float] = field(default_factory=list) start_altitude: float | None = None end_altitude: float | None = None count: int | None = None frequency: float | None = None anchor_altitude: float | None = None surface_altitude: float | None = None max_altitude: float | None = None metadata: dict[str, Any] = field(default_factory=dict) def __post_init__(self) -> None: self.altitude_ref = validate_vertical_reference(self.altitude_ref) if self.altitude_levels and ( self.start_altitude is not None or self.end_altitude is not None or self.count is not None or self.frequency is not None ): raise ValueError( "LineOfSight.altitude_levels cannot be combined with " "start/end/count/frequency sampling arguments." ) if self.altitude_levels: return if (self.start_altitude is None) != (self.end_altitude is None): raise ValueError( "LineOfSight requires both start_altitude and end_altitude " "when explicit altitude_levels are not provided." ) if self.start_altitude is None and self.end_altitude is None: raise ValueError( "LineOfSight requires altitude_levels or a start/end altitude range." ) if (self.count is None) == (self.frequency is None): raise ValueError( "LineOfSight requires exactly one of count or frequency when " "sampling from a start/end altitude range." )