Source code for voxcity.utils.zoning

"""Lon/lat polygon utilities for VoxCity grids.

Bridges geographic polygons (closed lon/lat rings, GeoJSON-style) into
the grid's (i, j) cell frame. Algorithm: project every cell centre to
lon/lat via GridProjector, then test point-in-polygon with MplPath.
"""

from typing import List, Sequence, Tuple

import numpy as np
from matplotlib.path import Path as MplPath

from .projector import GridGeom, GridProjector


[docs] def mask_from_lonlat_ring( ring: Sequence[Sequence[float]], grid_geom: GridGeom, ) -> np.ndarray: """Rasterize a closed lon/lat ring to a (nx, ny) boolean mask. A cell is True iff its centre lies inside the polygon. """ nx, ny = grid_geom["grid_size"] if len(ring) < 3: return np.zeros((nx, ny), dtype=bool) proj = GridProjector(grid_geom) ii, jj = np.meshgrid(np.arange(nx), np.arange(ny), indexing="ij") lons, lats = proj.cell_to_lon_lat(ii.ravel(), jj.ravel()) centres = np.stack([np.asarray(lons), np.asarray(lats)], axis=1) inside = MplPath(np.asarray(ring, dtype=float)).contains_points(centres) return inside.reshape(nx, ny)
[docs] def polygon_lonlat_to_cells( ring: Sequence[Sequence[float]], grid_geom: GridGeom, ) -> List[Tuple[int, int]]: """Rasterize a lon/lat ring to a list of (i, j) cell indices. Equivalent to ``np.argwhere(mask_from_lonlat_ring(...))`` returned as a list of Python tuples. Provided for parity with the helper that has historically lived in app/backend/zoning.py; new code should prefer ``mask_from_lonlat_ring``. """ mask = mask_from_lonlat_ring(ring, grid_geom) if not mask.any(): return [] cells = np.argwhere(mask) return [(int(i), int(j)) for i, j in cells]
[docs] def points_in_polygon_lonlat( points_lonlat: np.ndarray, # (N, 2) ring: Sequence[Sequence[float]], ) -> np.ndarray: """Vectorized point-in-polygon test (boolean array of length N).""" if len(ring) < 3 or points_lonlat.size == 0: return np.zeros(len(points_lonlat), dtype=bool) return MplPath(np.asarray(ring, dtype=float)).contains_points(points_lonlat)