Source code for stilt.config.spatial

"""Spatial config models."""

from __future__ import annotations

from typing import Literal, cast

from pydantic import BaseModel, ConfigDict

from .fields import cfg_field

VerticalReference = Literal["agl", "msl"]


def validate_vertical_reference(reference: str) -> VerticalReference:
    """Return a normalized vertical reference or raise for invalid input."""
    normalized = reference.lower()
    if normalized not in {"agl", "msl"}:
        raise ValueError(
            f"Vertical reference must be 'agl' or 'msl'. Got {reference!r}."
        )
    return cast(VerticalReference, normalized)


def kmsl_from_vertical_reference(reference: VerticalReference) -> int:
    """Map a vertical reference onto the HYSPLIT ``KMSL`` control value."""
    return 0 if reference == "agl" else 1


[docs] class Bounds(BaseModel): """Immutable geographic bounding box (always lon/lat degrees).""" model_config = ConfigDict(frozen=True) xmin: float = cfg_field(..., description="Western longitude (degrees).") xmax: float = cfg_field(..., description="Eastern longitude (degrees).") ymin: float = cfg_field(..., description="Southern latitude (degrees).") ymax: float = cfg_field(..., description="Northern latitude (degrees).")
[docs] class Grid(Bounds): """Footprint grid: lon/lat bounds, optional output projection, cell resolution.""" model_config = ConfigDict(frozen=True) xres: float = cfg_field( ..., description="Cell width in projection units (degrees for longlat, metres for UTM, etc.).", ) yres: float = cfg_field( ..., description="Cell height in projection units (degrees for longlat, metres for UTM, etc.).", ) projection: str = cfg_field( "+proj=longlat", description=( "Output CRS as a PROJ string. Bounds are always lon/lat; " "particles and bounds are projected to this CRS before gridding." ), ) @property def resolution(self) -> str: """Human-readable cell resolution string, e.g. ``'0.01x0.01'``.""" return f"{self.xres}x{self.yres}"
__all__ = ["Bounds", "Grid"]