Skip to content

Usage

This page walks through the full workflow: build a pyramid from the Xarray air_temperature tutorial dataset, write it to a Zarr store, and open it back up.

Load and assign a CRS

create_pyramid needs to know the dataset's coordinate reference system (CRS). xproj adds a .proj accessor for assigning one.

import xarray as xr
import xproj  # noqa: F401

ds = xr.tutorial.open_dataset("air_temperature").drop_encoding()
ds = ds.proj.assign_crs(spatial_ref="EPSG:4326")
ds

Plan and write

create_pyramid is lazy. It returns a plan and writes nothing until you call write. levels is the total number of levels. Level 0 is the original resolution, and each level after it is 2x coarser along each spatial dimension.

level 0 8 ร— 8 ยท native resolution level 1 4 ร— 4 ยท 2ร— coarser level 2 2 ร— 2 ยท 4ร— coarser create_pyramid(ds, levels=3) same extent, half the cells per dimension
from topozarr import create_pyramid

pyramid = create_pyramid(
    ds,
    levels=3,
    x_dim="lon",
    y_dim="lat",
    method="mean",  # "mean" (default) | "max" | "min" | "sum" | "nearest"
)
pyramid.write("pyramid.zarr")

For land cover, masks, or other class codes, use method="nearest" so that averaging doesn't invent new classes (see Categorical data).

Open as a DataTree

Each level is a child group that covers the same extent at half the resolution of the level before it:

dt = xr.open_datatree("pyramid.zarr", engine="zarr", consolidated=False)
dt
import matplotlib.pyplot as plt


def plot(mode):
    levels = ["0", str(len(dt.children) - 1)]  # finest and coarsest
    fig, axes = plt.subplots(1, 2, figsize=(10, 3.5), layout="constrained")
    for ax, level in zip(axes, levels):
        im = dt[level].air.isel(time=0).plot(
            ax=ax, vmin=230, vmax=305, cmap=f"warm_{mode}", add_colorbar=False
        )
        ax.set_title(f"level {level}")
    fig.colorbar(im, ax=axes, label="air temperature [K]")
    return fig


show(plot)

Open the store in zarr-layer to view it on a map.

Choosing a store

pyramid.write accepts a local path, an Obstore store, or an Icechunk store. Obstore and Icechunk can write to either local disk or cloud object storage.

pyramid.write("pyramid.zarr")
import icechunk

storage = icechunk.s3_storage(
    bucket="<your_bucket>", prefix="<your_prefix>", from_env=True
)
# local: storage = icechunk.local_filesystem_storage("pyramid.icechunk")
repo = icechunk.Repository.create(storage)
session = repo.writable_session("main")
pyramid.write(session.store, mode="w")
session.commit("write pyramid")
from obstore.store import from_url
from zarr.storage import ObjectStore

store = ObjectStore(from_url("s3://<your_bucket>/air.zarr", region="us-west-2"))
# local: store = ObjectStore(from_url("file:///absolute/path/to/air.zarr", mkdir=True))
pyramid.write(store, mode="w")

Single-resolution stores

Small datasets often don't need a pyramid. zarr-layer can render a single-resolution store as long as its chunks are sized for the web. attach_geozarr_metadata adds the GeoZarr attributes, and recommend_encoding returns the same chunking that create_pyramid uses for each level:

from topozarr import attach_geozarr_metadata, recommend_encoding

flat = attach_geozarr_metadata(ds, x_dim="lon", y_dim="lat")
flat.to_zarr(
    "flat.zarr",
    zarr_format=3,
    consolidated=False,
    encoding=recommend_encoding(flat, x_dim="lon", y_dim="lat"),
)

Visualization hints

layer_hints is optional. It embeds a default colormap and color range for zarr-layer and has no effect on the data.

from topozarr.metadata import ZarrLayerVarConfig

pyramid = create_pyramid(
    ds,
    levels=2,
    x_dim="lon",
    y_dim="lat",
    layer_hints={"air": ZarrLayerVarConfig(colormap="warm", clim=[230, 310])},
)

Next, see Tuning for level counts and chunk sizes, or the examples for categorical data, regridding, hybrid Icechunk stores, and Dask.