xarray & Zarr Interop#
Handing a mesh to xarray, and reading one back, in the UGRID-CF encoding.
xarray, zarr and dask are peer dependencies: they are imported inside these functions
through the optional-extras helpers, never at this module's import, and a missing one raises the
[lazy] install hint rather than a bare ImportError. There is no xugrid dependency — the
UGRID-CF encoding is written and read directly.
One caveat, measured rather than assumed: importing the parent pyramids.netcdf package already
pulls xarray and dask for reasons unrelated to this module, so import pyramids.netcdf.ugrid
pays that cost today even though the ugrid package itself adds none of it.
The encoding trap this module exists to handle is the connectivity round trip: a file's
start_index and _FillValue are restored on the way out and normalised back to the internal
0-indexed / -1-fill form on the way in. The dummy topology and grid-mapping variables carry a
length-1 dimension rather than being true scalars, because GDAL's multidimensional reader does not
expose a dimensionless variable — without it the exported dataset could be written to NetCDF but
not read back.
pyramids.netcdf.ugrid.interop_xarray
#
Standalone xarray / Zarr / dask interop for UGRID meshes.
Pure functions that translate between a :class:~pyramids.netcdf.ugrid.mesh.Mesh2d plus a
{name: MeshVariable} mapping and the UGRID-CF encoding carried by an xarray.Dataset --
the in-memory mirror of the on-disk encoding written by
:mod:pyramids.netcdf.ugrid.io. Keeping these functions mesh/variable-shaped (never a
UgridDataset) keeps the module free of the dataset import cycle; the UgridDataset facade
wraps the results.
xarray, zarr and dask are optional dependencies. None of them is imported at module
scope: every guarded import happens inside the function that needs it, through
:func:pyramids.base._utils.import_xarray / :func:~pyramids.base._utils.require_optional /
:func:~pyramids.base._utils.import_dask, mirroring NetCDF.to_xarray. A missing package raises
:class:pyramids.errors.OptionalPackageDoesNotExist.
The one encoding trap -- the connectivity start_index / _FillValue round-trip -- is handled
by :func:_connectivity_to_disk / :func:_connectivity_from_disk, which mirror the write-side
reverse mapping in :func:pyramids.netcdf.ugrid.io._write_connectivity_array and the read-side
normalisation in :meth:pyramids.netcdf.ugrid.connectivity.Connectivity.from_gdal_array. The
internal 0-indexed / -1-fill arrays are never emitted to xarray.
to_xarray(mesh, data_variables, mesh_name=DEFAULT_MESH_NAME, crs_wkt=None, volume_topology=None)
#
Encode a mesh and its variables as a UGRID-CF xarray.Dataset.
Produces a plain xarray.Dataset carrying the mesh topology the UGRID convention defines -- a
dummy mesh_topology variable, the node coordinates, the face_node_connectivity (with its
start_index and file _FillValue restored to the on-disk convention), optional
edge connectivity and face-centre coordinates, a CRS grid-mapping variable, and each data variable
on its location dimension with mesh / location attributes. The result is readable by
ecosystem tools such as xugrid.UgridDataset.from_dataset and round-trips losslessly through
:func:from_xarray.
xarray is optional; it is imported through
:func:pyramids.base._utils.import_xarray inside this function -- never at module scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh topology to export. |
required |
data_variables
|
dict[str, MeshVariable]
|
Mapping of variable name to :class: |
required |
mesh_name
|
str
|
Name for the exported topology variable. Defaults to |
DEFAULT_MESH_NAME
|
crs_wkt
|
str | None
|
WKT of the CRS to encode in the grid-mapping variable. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
xarray.Dataset: The UGRID-CF encoded dataset. |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | |
volume_topology_from_xarray(ds)
#
Rebuild a ragged :class:VolumeTopology from an exported dataset, or None.
The inverse of :func:_add_volume_topology_vars: finds the layer_count /
volume_offset arrays by their pyramids_role (the required pair) and the optional
vertical coordinates — matched by role across the whole dataset, so no mesh name is needed —
and reconstructs the object. None when the dataset carries no such arrays.
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
from_xarray(ds)
#
Reconstruct a mesh and its variables from a UGRID-CF xarray.Dataset.
The inverse of :func:to_xarray: reads the mesh_topology variable, the node coordinates, the
connectivity arrays (normalising start_index / _FillValue back to the internal
0-indexed / -1-fill form), and every data variable (a variable carrying a location
attribute). Round-tripping to_xarray then from_xarray reproduces the same nodes, faces and
values.
xarray is optional; it is imported inside this function, never at module scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Any
|
A UGRID-CF |
required |
Returns:
| Type | Description |
|---|---|
tuple[Mesh2d, dict[str, MeshVariable]]
|
A |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|
TypeError
|
|
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
read_mesh_name(ds)
#
The name of the topology variable a UGRID-CF dataset declares.
from_xarray answers the mesh and its variables, which leaves the topology's own name
unreachable to a caller that wants to keep it. Exposed separately so from_xarray's
two-value contract stays as it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Any
|
The |
required |
Returns:
| Type | Description |
|---|---|
str
|
The topology variable's name, or the default when the dataset names none. |
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
read_crs_wkt(ds)
#
The CRS WKT a UGRID-CF dataset carries, or None.
from_xarray answers the mesh and its variables; the CRS lives beside them on the grid-mapping
variable, and the caller needs it to stamp the dataset it builds. Exposed separately so
from_xarray's two-value contract stays as it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Any
|
The |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The WKT string, or |
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
to_zarr(mesh, data_variables, store, mesh_name=DEFAULT_MESH_NAME, crs_wkt=None, **to_zarr_kwargs)
#
Write a mesh and its variables to a UGRID-encoded Zarr store.
Routes through :func:to_xarray and xarray.Dataset.to_zarr, so the connectivity
start_index / _FillValue encoding is handled once in :func:to_xarray and never
re-encoded here. xarray and zarr are optional and imported inside this function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh topology to write. |
required |
data_variables
|
dict[str, MeshVariable]
|
Mapping of variable name to :class: |
required |
store
|
Any
|
A Zarr store -- a path, |
required |
mesh_name
|
str
|
Name for the exported topology variable. Defaults to |
DEFAULT_MESH_NAME
|
crs_wkt
|
str | None
|
WKT of the CRS to encode, forwarded to :func: |
None
|
**to_zarr_kwargs
|
Any
|
Extra keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Whatever |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
from_zarr(store, **open_zarr_kwargs)
#
Read a mesh and its variables back from a UGRID-encoded Zarr store.
The inverse of :func:to_zarr: opens the store with xarray.open_zarr and hands the dataset to
:func:from_xarray. xarray and zarr are optional and imported inside this function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Any
|
A Zarr store -- a path, |
required |
**open_zarr_kwargs
|
Any
|
Extra keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
tuple[Mesh2d, dict[str, MeshVariable]]
|
A |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
open_zarr_dataset(store, **open_zarr_kwargs)
#
Open a Zarr store as an xarray.Dataset, without decoding the mesh.
:func:from_zarr answers the mesh and its variables, which leaves the CRS (a grid-mapping
variable beside them) unreachable to a caller that needs to stamp it onto a dataset. Opening is
therefore exposed on its own, so a caller can read both the mesh and
:func:read_crs_wkt from one open.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Any
|
A Zarr store -- a path, |
required |
**open_zarr_kwargs
|
Any
|
Extra keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The opened |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|
Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
chunked_variables(data_variables, chunks='auto')
#
Return dask-backed arrays for the given mesh variables, for out-of-core reads.
The lazy counterpart of reading MeshVariable.data eagerly: each variable becomes a
dask.array so a large temporal variable can be reduced block by block without materialising
the whole (n_time, n_elements) array. A file-backed variable (one carrying a source path) is
wrapped with :func:pyramids.netcdf._lazy.build_lazy_array so its chunks are read straight from
the store; an in-memory variable is wrapped with dask.array.from_array.
A variable with neither -- a metadata-only variable, carrying a declared shape and dtype but no array and no source -- has nothing to chunk and is left out of the result, so the mapping can be smaller than the input.
dask is optional and imported inside this function; a missing dask raises the [lazy]
install hint rather than failing with a bare import error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_variables
|
dict[str, MeshVariable]
|
Mapping of variable name to :class: |
required |
chunks
|
Any
|
Chunk spec forwarded to the lazy reader -- |
'auto'
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A |
Raises:
| Type | Description |
|---|---|
OptionalPackageDoesNotExist
|
|