How to Convert Between Vector and Grid Formats#

This guide covers converting between vector geometries (points, lines, polygons) and unstructured grids.

Exporting Grid to GeoDataFrame#

Convert grid faces to polygons:

import xugrid as xu

uda = xu.data.elevation_nl()

# Export to GeoDataFrame with data values
gdf = uda.ugrid.to_geodataframe()

# Result is a GeoDataFrame with polygon geometry and data column
print(gdf.head())

Export grid topology without data:

grid = uda.ugrid.grid

# Faces as polygons
gdf_faces = grid.to_geodataframe(dim=grid.face_dimension)

# Edges as lines
gdf_edges = grid.to_geodataframe(dim=grid.edge_dimension)

# Nodes as points
import geopandas as gpd
gdf_nodes = gpd.GeoDataFrame(
    geometry=gpd.points_from_xy(grid.node_x, grid.node_y),
    crs=uda.ugrid.crs
)

Creating Grid from GeoDataFrame#

Convert polygons to an unstructured grid:

import geopandas as gpd
import xugrid as xu

# Load polygon data
gdf = gpd.read_file("regions.gpkg")

# Convert to UgridDataset
uds = xu.UgridDataset.from_geodataframe(gdf)

# Access specific columns as UgridDataArray
population = uds["population"]

Burning Vector Geometries to Grid#

Rasterize vector features onto an existing grid:

import geopandas as gpd
import xugrid as xu
from shapely.geometry import box

# Load grid
uda = xu.data.elevation_nl()

# Create or load polygon(s)
polygon = box(100_000, 450_000, 150_000, 500_000)

# Burn to grid (returns boolean mask by default)
mask = xu.burn_vector_geometry(polygon, uda)

# Apply mask
subset = uda.where(mask)

Burn with specific values:

# Burn with a specific value
burned = xu.burn_vector_geometry(polygon, uda, fill_value=1, all_touched=False)

# Burn multiple polygons with different values
gdf = gpd.GeoDataFrame({
    "geometry": [polygon1, polygon2, polygon3],
    "value": [1, 2, 3]
})
burned = xu.burn_vector_geometry(gdf, uda, column="value")

Control which faces are selected:

# Only faces fully inside polygon
mask = xu.burn_vector_geometry(polygon, uda, all_touched=False)

# Any face that touches polygon (default)
mask = xu.burn_vector_geometry(polygon, uda, all_touched=True)

Snapping Geometries to Grid#

Snap vector geometries to grid coordinates:

from shapely.geometry import Point, LineString

uda = xu.data.elevation_nl()
grid = uda.ugrid.grid

# Snap points to nearest nodes
points = [Point(155_000, 463_000), Point(160_000, 465_000)]
snapped = xu.snap_to_grid(points, grid, max_distance=1000)

For line features:

line = LineString([(155_000, 463_000), (160_000, 465_000)])
snapped_line = xu.snap_to_grid(line, grid, max_distance=500)

Polygonizing Grid Data#

Convert grid faces back to vector polygons based on values:

# Create categorical data
categories = (uda > 0).astype(int)  # 0 = below sea level, 1 = above

# Polygonize - merge adjacent faces with same value
gdf = xu.polygonize(categories)

Triangulating Polygons#

Convert polygons to triangular mesh:

from shapely.geometry import Polygon
import xugrid as xu

# Define polygon
polygon = Polygon([(0, 0), (10, 0), (10, 10), (5, 15), (0, 10)])

# Triangulate
grid = xu.earcut_triangulate_polygons([polygon])

# Create data on the triangulated mesh
import numpy as np
import xarray as xr

data = np.random.rand(grid.n_face)
da = xr.DataArray(data, dims=[grid.face_dimension])
uda = xu.UgridDataArray(da, grid)

Working with CRS (Coordinate Reference Systems)#

Set and transform coordinate systems:

# Set CRS
uda_with_crs = uda.ugrid.set_crs("EPSG:28992")  # Dutch RD

# Transform to different CRS
uda_wgs84 = uda_with_crs.ugrid.to_crs("EPSG:4326")

# Check CRS
print(uda.ugrid.crs)

Export with CRS preserved:

# GeoDataFrame will have CRS
gdf = uda_with_crs.ugrid.to_geodataframe()
print(gdf.crs)  # EPSG:28992

# NetCDF will have grid_mapping
uda_with_crs.ugrid.to_netcdf("with_crs.nc")

Integration with Shapely#

Direct use of Shapely geometries:

from shapely.geometry import Point, LineString, Polygon, MultiPolygon
from shapely.ops import unary_union

# Create complex selection geometry
poly1 = box(100_000, 450_000, 125_000, 475_000)
poly2 = box(150_000, 475_000, 175_000, 500_000)
combined = unary_union([poly1, poly2])

# Use with burn
mask = xu.burn_vector_geometry(combined, uda)

Integration with GeoPandas#

Full round-trip workflow:

import geopandas as gpd
import xugrid as xu

# 1. Load vector data
regions = gpd.read_file("regions.gpkg")

# 2. Load unstructured grid data
uda = xu.data.elevation_nl()

# 3. Compute zonal statistics
results = []
for idx, row in regions.iterrows():
    mask = xu.burn_vector_geometry(row.geometry, uda)
    masked = uda.where(mask)
    results.append({
        "region": row["name"],
        "mean_elevation": float(masked.mean()),
        "max_elevation": float(masked.max()),
    })

# 4. Add results back to GeoDataFrame
stats_df = pd.DataFrame(results)
regions = regions.merge(stats_df, left_on="name", right_on="region")

# 5. Save
regions.to_file("regions_with_stats.gpkg")