voxcity.utils.GridProjector =========================== .. py:class:: voxcity.utils.GridProjector(geom: GridGeom) Projects between lon_lat (WGS84) and uv_m (local grid metres). The 2×2 affine A maps (u_cell, v_cell) → (dlon, dlat):: A = [[u_vec[0]*du_m, v_vec[0]*dv_m], [u_vec[1]*du_m, v_vec[1]*dv_m]] where (du_m, dv_m) = adj_mesh. A⁻¹ is pre-computed for O(1) calls. Accepts both scalars and numpy arrays. .. py:method:: lon_lat_to_uv_m(lon: ArrayLike, lat: ArrayLike) -> tuple[ArrayLike, ArrayLike] lon_lat → uv_m. Accepts scalars or numpy arrays. Returns (u_m, v_m) where u_m = metres along u_vec from grid origin, v_m = metres along v_vec from grid origin. Cell (i, j) occupies uv_m in [i*du_m, (i+1)*du_m) × [j*dv_m, (j+1)*dv_m). .. py:method:: uv_m_to_lon_lat(u_m: ArrayLike, v_m: ArrayLike) -> tuple[ArrayLike, ArrayLike] uv_m → lon_lat. Exact inverse of lon_lat_to_uv_m. .. py:method:: lon_lat_to_cell(lon: ArrayLike, lat: ArrayLike) -> tuple lon_lat → (i, j) integer cell index. Uses floor division — safe for boundary points and negative coordinates. Scalar inputs return Python ints; array inputs return int ndarrays. .. py:method:: cell_to_lon_lat(i: ArrayLike, j: ArrayLike) -> tuple[ArrayLike, ArrayLike] Cell centre (i+0.5, j+0.5) → lon_lat.