Mesh Construction#
Building a mesh from something that is not already a mesh.
A structured grid becomes a quad mesh, and a GeoDataFrame of polygons becomes a mesh of those
polygons with shared vertices merged. These back UgridDataset.from_structured and
UgridDataset.from_geodataframe.
pyramids.netcdf.ugrid.construct
#
Mesh-construction helpers that flatten structured grids and polygons into UGRID topology.
Pure, dependency-light functions that turn a pyramids :class:~pyramids.dataset.dataset.Dataset
(regular raster grid) or a :class:geopandas.GeoDataFrame of polygons into the node coordinate
and face_node_connectivity arrays a UGRID 2D mesh needs. Each function returns a plain tuple
(node_x, node_y, face_node_connectivity, data) so the caller can hand it straight to
:meth:pyramids.netcdf.ugrid.dataset.UgridDataset.from_arrays together with the CRS it reads from
the source (Dataset.epsg / GeoDataFrame.crs). Keeping the result as bare arrays — rather
than a built :class:~pyramids.netcdf.ugrid.mesh.Mesh2d — avoids an import cycle with
ugrid.dataset and lets the facade own the single from_arrays call.
The two entry points:
- :func:
from_structured/ :func:from_dataset— each raster cell becomes a quad face, cell corners become deduplicated nodes, and every band becomes a per-face data array. - :func:
from_geodataframe— each polygon becomes a face, its exterior vertices become deduplicated nodes, and every attribute column becomes a per-face data array.
from_structured(dataset, band_names=None)
#
Flatten a regular raster grid into a quad mesh.
Every raster cell becomes one quad face: the cell's four corners become nodes on an
(ny + 1) x (nx + 1) lattice (shared corners are naturally deduplicated by the lattice
indexing, so no post-hoc np.unique is needed), and each band's values become a per-face
data array in row-major order matching the face ordering.
Faces are emitted counter-clockwise, as UGRID expects. Each face is built in
counter-clockwise (column, row) index order and then reversed when the geotransform
flips orientation, which the sign of its Jacobian determinant
(gt[1] * gt[5] - gt[2] * gt[4]) decides. That covers a north-up raster (where y
descends down the rows), a mirrored one, and a rotated one alike -- including an exact
90-degree rotation, where both pixel sizes are 0.0 and their signs say nothing.
No-data cells are kept as faces carrying their raw band value (including the raster's
no-data value) — they are not dropped — so n_face == rows * columns and face ordering
stays aligned with the source grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
Dataset
|
Source pyramids :class: |
required |
band_names
|
list[str] | None
|
Optional names for the per-band face variables, one per band. Defaults to
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
MeshArrays
|
|
MeshArrays
|
|
|
MeshArrays
|
|
|
MeshArrays
|
band name to a |
|
MeshArrays
|
meth: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the dataset has no rows or columns. |
Source code in src/pyramids/netcdf/ugrid/construct.py
from_dataset(dataset, band_names=None)
#
Alias for :func:from_structured.
Provided so the UGRID facade can expose both UgridDataset.from_dataset and
UgridDataset.from_structured names over the same quad-meshing logic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
Dataset
|
Source pyramids :class: |
required |
band_names
|
list[str] | None
|
Optional per-band face-variable names (see :func: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
MeshArrays
|
|
Source code in src/pyramids/netcdf/ugrid/construct.py
from_geodataframe(gdf, include_attributes=True)
#
Build a face mesh from polygon geometries — the inverse of UgridDataset.to_geodataframe.
Each polygon becomes one face. Exterior-ring vertices (the closing duplicate dropped) are
stacked across all polygons and deduplicated with np.unique(..., axis=0,
return_inverse=True), so shared vertices collapse to a single node. Deduplication is
exact — two vertices are shared only if their coordinates are bit-for-bit equal; no
snapping tolerance is applied. Ragged faces (a mix of triangles, quads, ...) are padded with
the -1 fill value to the widest ring. Each face's vertex order is reoriented
counter-clockwise (rings whose shapely is_ccw is false are reversed, which preserves the
polygon exactly), and degenerate polygons are dropped: empty, null, fewer than three distinct
vertices, or enclosing no area at all. The last case needs its own test -- three distinct
vertices can be collinear, as [(0,0), (1,0), (2,0)] is. Attribute columns become per-face data arrays, aligned to the kept faces -- a
dropped row's attribute value goes with it, so the alignment holds.
Only each polygon's exterior ring is used; interior rings (holes) are ignored, matching the
single-ring face model of a UGRID mesh. A MultiPolygon has no single exterior ring, so
such a row is dropped along with its attributes; call gdf.explode() first to turn
each part into its own face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gdf
|
GeoDataFrame
|
GeoDataFrame of :class: |
required |
include_attributes
|
bool
|
When |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
MeshArrays
|
|
MeshArrays
|
|
|
MeshArrays
|
|
|
MeshArrays
|
attribute column to an |
|
MeshArrays
|
meth: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the GeoDataFrame holds no valid (non-degenerate) polygon. |
Source code in src/pyramids/netcdf/ugrid/construct.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |