rioxarray — Geospatial xarray
Goal: Open GeoTIFFs as xarray objects with correct CRS, bounds, and dimensions using rioxarray, then clip, align a DEM, and write outputs.
Lessons 1–2 streamed rasters from STAC. This lesson uses files on disk so clip, reproject_match, and write stay in the foreground.
What is rioxarray?
rioxarray extends xarray so that raster I/O and spatial metadata are first-class:
- Open rasters (GeoTIFF, COG, etc.) from a path or URL into a
DataArrayorDataset. - Attach CRS and transform via the
.rioaccessor. - Reproject, clip, align, and write rasters using the same accessor.
Under the hood it uses rasterio for reading/writing and GDAL for formats.
Sample Data
The lesson notebook reads GeoTIFFs from data/:
| File | Variable |
|---|---|
prec_daily_2024_ERA5LAND_native.tif |
ERA5-Land daily precipitation (m), one band per day in 2024 |
temp_daily_2024_ERA5LAND_native.tif |
ERA5-Land daily 2 m temperature (K) |
copernicus_dem_30m.tif |
Copernicus DEM, 30 m |
NL_provinces.shp |
Province polygons (clip to Utrecht) |
import rioxarray as rxr
prec = rxr.open_rasterio("data/prec_daily_2024_ERA5LAND_native.tif", masked=True)
print(prec.rio.crs, prec.rio.bounds(), prec.rio.resolution())
Default behavior:
- Dimensions: often
band,y,x(daily stacks usebandfor time until you rename it). - Coordinates:
xandycome from the geotransform. - CRS / bounds / transform:
data.rio.crs,data.rio.bounds(),data.rio.transform().
masked=True turns NoData into NaN.
The .rio accessor
| Method / property | Purpose |
|---|---|
.rio.crs |
Coordinate reference system |
.rio.bounds() |
(left, bottom, right, top) |
.rio.resolution() |
Pixel size |
.rio.transform() |
Affine transform (pixel ↔ geo) |
.rio.clip_box() |
Clip to a bounding box |
.rio.clip() |
Clip with a geometry (e.g. GeoDataFrame) |
.rio.reproject() |
Reproject to another CRS |
.rio.reproject_match() |
Warp onto another raster's grid |
.rio.to_raster() |
Write to GeoTIFF/COG |
Daily climate bands are mapped to a time coordinate. The 30 m DEM does not share that grid — reproject_match warps it onto one ERA5 slice:
dem = dem.rio.reproject_match(prec.isel(time=0))
ds = xr.Dataset({"prec": prec, "temp": temp, "dem": dem})
Then clip a rectangle or a polygon (Utrecht), convert kelvin → °C, mask wet days (prec > 0.001 m), and write GeoTIFF / NetCDF.
Chunked (lazy) opening
For large rasters and Dask-based workflows, open with chunks so the underlying array is a Dask array:
Operations then stay lazy until .compute() or plotting is triggered. This is the bridge to Lesson 4 (Dask).
Summary
| Concept | Detail |
|---|---|
| rioxarray | Geospatial extension for xarray: open rasters, CRS, bounds, clip, reproject |
| Opening | rioxarray.open_rasterio(path) → DataArray with band, y, x |
| Align | reproject_match puts the DEM on the ERA5 grid |
| Clip | clip_box (rectangle) or clip (polygon) |
| .rio | Accessor for CRS, bounds, transform, clip, reproject, to_raster |
Next, Dask runs parallel, chunked analytics on these structures.
Download the notebook for this lesson: notebooks/03-rioxarray.ipynb.