Skip to content

Utilities

XRegrid provides several utility functions for creating standard grids and loading ESMF-formatted files.

Grid Generation

create_global_grid

xregrid.create_global_grid(res_lat, res_lon, add_bounds=True, chunks=None)

Create a global rectilinear grid dataset.

Parameters:

Name Type Description Default
res_lat float

Latitude resolution in degrees.

required
res_lon float

Longitude resolution in degrees.

required
add_bounds bool

Whether to add cell boundary coordinates.

True
chunks int or dict

Chunk sizes for the resulting dask-backed dataset. If None (default), returns an eager NumPy-backed dataset.

None

Returns:

Type Description
Dataset

The global grid dataset containing 'lat' and 'lon'.

Source code in src/xregrid/utils.py
def create_global_grid(
    res_lat: float,
    res_lon: float,
    add_bounds: bool = True,
    chunks: Optional[Union[int, Dict[str, int]]] = None,
) -> xr.Dataset:
    """
    Create a global rectilinear grid dataset.

    Parameters
    ----------
    res_lat : float
        Latitude resolution in degrees.
    res_lon : float
        Longitude resolution in degrees.
    add_bounds : bool, default True
        Whether to add cell boundary coordinates.
    chunks : int or dict, optional
        Chunk sizes for the resulting dask-backed dataset.
        If None (default), returns an eager NumPy-backed dataset.

    Returns
    -------
    xr.Dataset
        The global grid dataset containing 'lat' and 'lon'.
    """
    return _create_rectilinear_grid(
        lat_range=(-90, 90),
        lon_range=(0, 360),
        res_lat=res_lat,
        res_lon=res_lon,
        add_bounds=add_bounds,
        chunks=chunks,
        history_msg=f"Created global grid ({res_lat}x{res_lon}) using xregrid.",
    )

Create a global rectilinear grid dataset with a specified resolution.

from xregrid import create_global_grid

# Create a 1x1 degree global grid with bounds
ds = create_global_grid(res_lat=1.0, res_lon=1.0)

create_regional_grid

xregrid.create_regional_grid(lat_range, lon_range, res_lat, res_lon, add_bounds=True, chunks=None)

Create a regional rectilinear grid dataset.

Parameters:

Name Type Description Default
lat_range tuple of float

(min_lat, max_lat).

required
lon_range tuple of float

(min_lon, max_lon).

required
res_lat float

Latitude resolution in degrees.

required
res_lon float

Longitude resolution in degrees.

required
add_bounds bool

Whether to add cell boundary coordinates.

True
chunks int or dict

Chunk sizes for the resulting dask-backed dataset. If None (default), returns an eager NumPy-backed dataset.

None

Returns:

Type Description
Dataset

The regional grid dataset containing 'lat' and 'lon'.

Source code in src/xregrid/utils.py
def create_regional_grid(
    lat_range: Tuple[float, float],
    lon_range: Tuple[float, float],
    res_lat: float,
    res_lon: float,
    add_bounds: bool = True,
    chunks: Optional[Union[int, Dict[str, int]]] = None,
) -> xr.Dataset:
    """
    Create a regional rectilinear grid dataset.

    Parameters
    ----------
    lat_range : tuple of float
        (min_lat, max_lat).
    lon_range : tuple of float
        (min_lon, max_lon).
    res_lat : float
        Latitude resolution in degrees.
    res_lon : float
        Longitude resolution in degrees.
    add_bounds : bool, default True
        Whether to add cell boundary coordinates.
    chunks : int or dict, optional
        Chunk sizes for the resulting dask-backed dataset.
        If None (default), returns an eager NumPy-backed dataset.

    Returns
    -------
    xr.Dataset
        The regional grid dataset containing 'lat' and 'lon'.
    """
    return _create_rectilinear_grid(
        lat_range=lat_range,
        lon_range=lon_range,
        res_lat=res_lat,
        res_lon=res_lon,
        add_bounds=add_bounds,
        chunks=chunks,
        history_msg=f"Created regional grid ({res_lat}x{res_lon}) using xregrid.",
    )

Create a regional rectilinear grid dataset for a specific geographic bounding box.

from xregrid import create_regional_grid

# Create a regional grid over Europe
ds = create_regional_grid(
    lat_range=(35, 70),
    lon_range=(-10, 40),
    res_lat=0.25,
    res_lon=0.25
)

create_grid_from_crs

xregrid.create_grid_from_crs(crs, extent, res, add_bounds=True, chunks=None)

Create a structured grid dataset from a CRS and extent.

Parameters:

Name Type Description Default
crs str, int, or pyproj.CRS

The CRS of the grid (Proj4 string, EPSG code, WKT, or CRS object).

required
extent tuple of float

Grid extent in CRS units: (min_x, max_x, min_y, max_y).

required
res float or tuple of float

Grid resolution in CRS units. If float, same resolution in x and y. If tuple, (res_x, res_y).

required
add_bounds bool

Whether to add cell boundary coordinates.

True
chunks int or dict

Chunk sizes for the resulting dask-backed dataset. If None (default), returns an eager NumPy-backed dataset.

None

Returns:

Type Description
Dataset

The grid dataset containing 'lat', 'lon' and projected coordinates 'x', 'y'.

Source code in src/xregrid/utils.py
def create_grid_from_crs(
    crs: Union[str, int, Any],
    extent: Tuple[float, float, float, float],
    res: Union[float, Tuple[float, float]],
    add_bounds: bool = True,
    chunks: Optional[Union[int, Dict[str, int]]] = None,
) -> xr.Dataset:
    """
    Create a structured grid dataset from a CRS and extent.

    Parameters
    ----------
    crs : str, int, or pyproj.CRS
        The CRS of the grid (Proj4 string, EPSG code, WKT, or CRS object).
    extent : tuple of float
        Grid extent in CRS units: (min_x, max_x, min_y, max_y).
    res : float or tuple of float
        Grid resolution in CRS units. If float, same resolution in x and y.
        If tuple, (res_x, res_y).
    add_bounds : bool, default True
        Whether to add cell boundary coordinates.
    chunks : int or dict, optional
        Chunk sizes for the resulting dask-backed dataset.
        If None (default), returns an eager NumPy-backed dataset.

    Returns
    -------
    xr.Dataset
        The grid dataset containing 'lat', 'lon' and projected coordinates 'x', 'y'.
    """
    if isinstance(res, (int, float)):
        res_x = res_y = float(res)
    else:
        res_x, res_y = map(float, res)

    # Generate 1D coordinates in projected space
    if chunks is not None and da is not None:
        # Handle dict or int chunks for 1D arrays
        x_chunks = chunks.get("x", -1) if isinstance(chunks, dict) else chunks
        y_chunks = chunks.get("y", -1) if isinstance(chunks, dict) else chunks

        x = da.arange(extent[0] + res_x / 2, extent[1], res_x, chunks=x_chunks)
        y = da.arange(extent[2] + res_y / 2, extent[3], res_y, chunks=y_chunks)
    else:
        x = np.arange(extent[0] + res_x / 2, extent[1], res_x)
        y = np.arange(extent[2] + res_y / 2, extent[3], res_y)

    x_da = xr.DataArray(x, dims=["x"], name="x")
    y_da = xr.DataArray(y, dims=["y"], name="y")

    # Use xr.broadcast for lazy 2D arrays
    yy_da, xx_da = xr.broadcast(y_da, x_da)

    # Ensure (y, x) order
    yy_da = yy_da.transpose("y", "x")
    xx_da = xx_da.transpose("y", "x")

    # Transform to lat/lon
    if pyproj is None:
        raise ImportError(
            "pyproj is required for create_grid_from_crs. "
            "Install it with `pip install pyproj`."
        )
    crs_obj = pyproj.CRS(crs)

    # Use apply_ufunc with dask='parallelized'
    lon, lat = xr.apply_ufunc(
        _transform_coords,
        xx_da,
        yy_da,
        kwargs={"crs_in": crs_obj},
        dask="parallelized",
        output_dtypes=[float, float],
        input_core_dims=[[], []],
        output_core_dims=[[], []],
    )

    # Try to get units from CRS, default to 'm'
    try:
        units = crs_obj.axis_info[0].unit_name or "m"
    except (IndexError, AttributeError):
        units = "m"

    ds = xr.Dataset(
        coords={
            "y": (
                ["y"],
                y,
                {"units": units, "standard_name": "projection_y_coordinate"},
            ),
            "x": (
                ["x"],
                x,
                {"units": units, "standard_name": "projection_x_coordinate"},
            ),
            "lat": (
                ["y", "x"],
                lat.data,
                {"units": "degrees_north", "standard_name": "latitude"},
            ),
            "lon": (
                ["y", "x"],
                lon.data,
                {"units": "degrees_east", "standard_name": "longitude"},
            ),
        }
    )

    # Store CRS info
    ds.attrs["crs"] = crs_obj.to_wkt()

    if add_bounds:
        # Create CF-compliant curvilinear bounds (Y, X, 4)
        # This ensures bounds are sliced correctly with centers

        if chunks is not None and da is not None:
            x_b_raw = da.stack(
                [x - res_x / 2, x + res_x / 2, x + res_x / 2, x - res_x / 2]
            )
            y_b_raw = da.stack(
                [y - res_y / 2, y - res_y / 2, y + res_y / 2, y + res_y / 2]
            )
        else:
            x_b_raw = np.stack(
                [x - res_x / 2, x + res_x / 2, x + res_x / 2, x - res_x / 2]
            )
            y_b_raw = np.stack(
                [y - res_y / 2, y - res_y / 2, y + res_y / 2, y + res_y / 2]
            )

        x_b_da = xr.DataArray(x_b_raw, dims=["nv", "x"], name="x_b")
        y_b_da = xr.DataArray(y_b_raw, dims=["nv", "y"], name="y_b")

        # Broadcast them to (nv, y, x)
        yy_b_da, xx_b_da = xr.broadcast(y_b_da, x_b_da)

        # Transform corners lazily
        lon_b, lat_b = xr.apply_ufunc(
            _transform_coords,
            xx_b_da,
            yy_b_da,
            kwargs={"crs_in": crs_obj},
            dask="parallelized",
            output_dtypes=[float, float],
            input_core_dims=[[], []],
            output_core_dims=[[], []],
        )

        # Reshape to (y, x, nv) for CF compliance
        lat_b = lat_b.transpose("y", "x", "nv")
        lon_b = lon_b.transpose("y", "x", "nv")

        ds.coords["lat_b"] = (["y", "x", "nv"], lat_b.data, {"units": "degrees_north"})
        ds.coords["lon_b"] = (["y", "x", "nv"], lon_b.data, {"units": "degrees_east"})

        ds["lat"].attrs["bounds"] = "lat_b"
        ds["lon"].attrs["bounds"] = "lon_b"

    update_history(ds, f"Created grid from CRS {crs} using xregrid (Lazy Generation).")

    return ds

Create a structured grid dataset from a Coordinate Reference System (CRS) and extent.

from xregrid import create_grid_from_crs

# Create a Lambert Conformal Conic grid over North America
extent = (-2500000, 2500000, -2000000, 2000000)
res = (12000, 12000) # 12km
crs = "+proj=lcc +lat_1=33 +lat_2=45 +lat_0=40 +lon_0=-97 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs"

ds = create_grid_from_crs(crs, extent, res)

create_grid_from_ioapi

xregrid.create_grid_from_ioapi(metadata, add_bounds=True, chunks=None)

Create a structured grid dataset from IOAPI-compliant metadata.

Supports GDTYP: - 1: Lat-Lon - 2: Lambert Conformal - 5: Polar Stereographic - 6: Albers Equal Area - 7: Mercator

Parameters:

Name Type Description Default
metadata dict

IOAPI metadata containing GDTYP, P_ALP, P_BET, P_GAM, XCENT, YCENT, XORIG, YORIG, XCELL, YCELL, NCOLS, NROWS.

required
add_bounds bool

Whether to add cell boundary coordinates.

True
chunks int or dict

Chunk sizes for the resulting dask-backed dataset.

None

Returns:

Type Description
Dataset

The grid dataset.

Source code in src/xregrid/utils.py
def create_grid_from_ioapi(
    metadata: Dict[str, Any],
    add_bounds: bool = True,
    chunks: Optional[Union[int, Dict[str, int]]] = None,
) -> xr.Dataset:
    """
    Create a structured grid dataset from IOAPI-compliant metadata.

    Supports GDTYP:
    - 1: Lat-Lon
    - 2: Lambert Conformal
    - 5: Polar Stereographic
    - 6: Albers Equal Area
    - 7: Mercator

    Parameters
    ----------
    metadata : dict
        IOAPI metadata containing GDTYP, P_ALP, P_BET, P_GAM, XCENT, YCENT,
        XORIG, YORIG, XCELL, YCELL, NCOLS, NROWS.
    add_bounds : bool, default True
        Whether to add cell boundary coordinates.
    chunks : int or dict, optional
        Chunk sizes for the resulting dask-backed dataset.

    Returns
    -------
    xr.Dataset
        The grid dataset.
    """
    gdtyp = metadata["GDTYP"]
    p_alp = metadata["P_ALP"]
    p_bet = metadata["P_BET"]
    xcent = metadata["XCENT"]
    ycent = metadata["YCENT"]
    xorig = metadata["XORIG"]
    yorig = metadata["YORIG"]
    xcell = metadata["XCELL"]
    ycell = metadata["YCELL"]
    ncols = metadata["NCOLS"]
    nrows = metadata["NROWS"]

    if gdtyp == 1:  # Lat-Lon
        crs = "EPSG:4326"
        # In IOAPI Lat-Lon, XORIG/YORIG are degrees, XCELL/YCELL are degrees
    elif gdtyp == 2:  # Lambert Conformal
        crs = (
            f"+proj=lcc +lat_1={p_alp} +lat_2={p_bet} +lat_0={ycent} "
            f"+lon_0={xcent} +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
        )
    elif gdtyp == 5:  # Polar Stereographic
        crs = (
            f"+proj=stere +lat_0={ycent} +lat_ts={p_alp} +lon_0={xcent} "
            f"+k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
        )
    elif gdtyp == 6:  # Albers Equal Area
        crs = (
            f"+proj=aea +lat_1={p_alp} +lat_2={p_bet} +lat_0={ycent} "
            f"+lon_0={xcent} +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
        )
    elif gdtyp == 7:  # Mercator
        crs = (
            f"+proj=merc +lat_ts={p_alp} +lon_0={xcent} "
            f"+x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
        )
    else:
        raise ValueError(f"Unsupported IOAPI GDTYP: {gdtyp}")

    extent = (xorig, xorig + ncols * xcell, yorig, yorig + nrows * ycell)
    res = (xcell, ycell)

    ds = create_grid_from_crs(crs, extent, res, add_bounds=add_bounds, chunks=chunks)

    # Attach IOAPI metadata for provenance
    for k, v in metadata.items():
        ds.attrs[f"ioapi_{k}"] = v

    update_history(ds, f"Created grid from IOAPI metadata (GDTYP={gdtyp})")

    return ds

Create a structured grid dataset from IOAPI-compliant metadata.

from xregrid.utils import create_grid_from_ioapi

metadata = {
    "GDTYP": 2,
    "P_ALP": 30.0,
    "P_BET": 60.0,
    "XCENT": -97.0,
    "YCENT": 40.0,
    "XORIG": -1000.0,
    "YORIG": -1000.0,
    "XCELL": 500.0,
    "YCELL": 500.0,
    "NCOLS": 100,
    "NROWS": 100,
}

ds = create_grid_from_ioapi(metadata)

ESMF File Support

load_esmf_file

xregrid.load_esmf_file(filepath)

Load an ESMF mesh, mosaic, or grid file into an xarray Dataset.

Automatically recognizes SCRIP/ESMF standard variable names and renames them to 'lat', 'lon', 'lat_b', 'lon_b' while adding CF attributes.

Parameters:

Name Type Description Default
filepath str

Path to the ESMF file.

required

Returns:

Type Description
Dataset

The dataset representation of the ESMF file.

Source code in src/xregrid/utils.py
def load_esmf_file(filepath: str) -> xr.Dataset:
    """
    Load an ESMF mesh, mosaic, or grid file into an xarray Dataset.

    Automatically recognizes SCRIP/ESMF standard variable names and renames
    them to 'lat', 'lon', 'lat_b', 'lon_b' while adding CF attributes.

    Parameters
    ----------
    filepath : str
        Path to the ESMF file.

    Returns
    -------
    xr.Dataset
        The dataset representation of the ESMF file.
    """
    ds = xr.open_dataset(filepath)

    # Recognize SCRIP/ESMF standard names
    rename_map = {
        "grid_center_lat": "lat",
        "grid_center_lon": "lon",
        "grid_corner_lat": "lat_b",
        "grid_corner_lon": "lon_b",
        "grid_imask": "mask",
    }

    found_renames = {k: v for k, v in rename_map.items() if k in ds}

    if found_renames:
        ds = ds.rename(found_renames)
        message = f"Loaded ESMF file and renamed standard variables: {found_renames}"
    else:
        message = f"Loaded ESMF file from {filepath}."

    # Add CF attributes if missing for better cf-xarray discovery
    if "lat" in ds:
        if "units" not in ds["lat"].attrs:
            ds["lat"].attrs["units"] = "degrees_north"
        if "standard_name" not in ds["lat"].attrs:
            ds["lat"].attrs["standard_name"] = "latitude"

    if "lon" in ds:
        if "units" not in ds["lon"].attrs:
            ds["lon"].attrs["units"] = "degrees_east"
        if "standard_name" not in ds["lon"].attrs:
            ds["lon"].attrs["standard_name"] = "longitude"

    # Link bounds if present
    if "lat" in ds and "lat_b" in ds:
        ds["lat"].attrs["bounds"] = "lat_b"
    if "lon" in ds and "lon_b" in ds:
        ds["lon"].attrs["bounds"] = "lon_b"

    update_history(ds, message)

    return ds

Load an ESMF mesh, mosaic, or grid file into an xarray Dataset.

from xregrid import load_esmf_file

# Load an ESMF mesh file
ds = load_esmf_file("path/to/mesh.nc")