Source code for stilt.config.geometry

"""
Declarative geometry specs for footprint configs.

A :class:`~stilt.config.FootprintConfig` may name the *state geometry* it is
meant to serve (a shapefile, H3 hexagons, point windows) instead of, or as
well as, its native raster ``grid``.  The spec is plain YAML-able data; its
:meth:`build` method returns the live :class:`stilt.Mesh`, and when ``grid``
is omitted the config derives one with :meth:`stilt.Grid.from_geometry`.

.. code-block:: yaml

   footprints:
     counties:
       geometry:
         kind: file
         path: counties.shp
         ids: NAME
     hexes:
       geometry: {kind: h3, resolution: 8, bounds: {xmin: -112.3, xmax: -111.6, ymin: 40.4, ymax: 41.0}}
       cells_per_target: 4
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated, Literal

from pydantic import BaseModel, ConfigDict, Field

from .spatial import Bounds

if TYPE_CHECKING:
    from stilt.geometry import Mesh


[docs] class FileGeometrySpec(BaseModel): """Polygons read from a vector file (shapefile, GeoPackage, GeoJSON).""" model_config = ConfigDict(frozen=True) kind: Literal["file"] = "file" path: str = Field(..., description="Path to the vector file.") ids: str | None = Field( None, description="Attribute column to use as cell ids (default: row index)." ) layer: str | None = Field(None, description="Layer name for multi-layer files.") where: str | None = Field( None, description="Optional attribute filter (OGR SQL WHERE clause)." )
[docs] def build(self) -> Mesh: """Read the file into a :class:`stilt.Mesh` (requires geopandas).""" from stilt.geometry import Mesh kwargs = {} if self.layer is not None: kwargs["layer"] = self.layer if self.where is not None: kwargs["where"] = self.where return Mesh.from_file(self.path, ids=self.ids, **kwargs)
[docs] class H3GeometrySpec(BaseModel): """H3 hexagons of one resolution covering a lon/lat bounding box.""" model_config = ConfigDict(frozen=True) kind: Literal["h3"] = "h3" resolution: int = Field(..., description="H3 resolution (0-15).", ge=0, le=15) bounds: Bounds = Field(..., description="Lon/lat box the hexagons must cover.")
[docs] def build(self) -> Mesh: """Generate the hexagons (requires the ``h3`` package).""" from stilt.geometry import Mesh return Mesh.from_h3(self.resolution, self.bounds)
[docs] class WindowsGeometrySpec(BaseModel): """Rectangular windows centred on points, e.g. named point sources.""" model_config = ConfigDict(frozen=True) kind: Literal["windows"] = "windows" coords: list[tuple[float, float]] = Field( ..., description="Window centres as (x, y) pairs." ) size: float | tuple[float, float] = Field( ..., description="Window width, or (width, height), in CRS units." ) ids: list[str] | None = Field(None, description="Optional label per point.") crs: str = Field("+proj=longlat", description="CRS of the coordinates.")
[docs] def build(self) -> Mesh: """Build the windows as a :class:`stilt.Mesh`.""" from stilt.geometry import Mesh return Mesh.from_windows(self.coords, self.size, ids=self.ids, crs=self.crs)
GeometrySpec = Annotated[ FileGeometrySpec | H3GeometrySpec | WindowsGeometrySpec, Field(discriminator="kind"), ] """Any declarative geometry accepted by ``FootprintConfig.geometry``.""" __all__ = [ "FileGeometrySpec", "GeometrySpec", "H3GeometrySpec", "WindowsGeometrySpec", ]