Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ddc9338
REF: Refactor convention handling and add CF support in rioxarray
emmanuelmathot Jan 28, 2026
81ca317
ENH: Add `convention` option to `set_options()` and extract CF conven…
emmanuelmathot Jan 28, 2026
00956fe
Merge branch 'master' into feature/convention-option-cf-refactor
emmanuelmathot Jan 28, 2026
fde2529
Add Convention option & extract CF logic to cf.py
emmanuelmathot Jan 28, 2026
6c023fa
Refactor code structure for improved readability and maintainability
emmanuelmathot Jan 28, 2026
d88e548
REF: Remove unused functions and constants to streamline code in riox…
emmanuelmathot Jan 28, 2026
8ea47c1
Refactor code structure for improved readability and maintainability
emmanuelmathot Jan 28, 2026
43d6bdd
Refactor code structure for improved readability and maintainability
emmanuelmathot Jan 28, 2026
2fe9212
Implement feature X to enhance user experience and fix bug Y in module Z
emmanuelmathot Jan 28, 2026
5587996
Update rioxarray/_options.py
emmanuelmathot Jan 29, 2026
524ae5c
Update rioxarray/_options.py
emmanuelmathot Jan 29, 2026
b52cdb3
Refactor convention handling to support auto-detection of CRS, transf…
emmanuelmathot Jan 29, 2026
db71c16
Update rioxarray/_convention/__init__.py
emmanuelmathot Jan 29, 2026
7cfe70c
Refactor convention handling to support writing CRS and transform, an…
emmanuelmathot Jan 29, 2026
ef5ca43
Refactor convention handling to improve grid mapping writing and enha…
emmanuelmathot Jan 30, 2026
6d5b2db
grid_mapping in CF
emmanuelmathot Jan 30, 2026
4ebffdf
Refactor grid mapping handling to improve clarity and consistency in …
emmanuelmathot Jan 30, 2026
f4a0fee
Pass grid_mapping_name to read_transform in write_crs for accurate tr…
emmanuelmathot Jan 30, 2026
7d3fcae
Refactor grid mapping handling in write_crs and write_transform to us…
emmanuelmathot Jan 30, 2026
03b5d7b
Refactor convention handling to prioritize globally set conventions f…
emmanuelmathot Jan 30, 2026
56b466c
Refactor code structure for improved readability and maintainability
emmanuelmathot Jan 30, 2026
7194119
Update rioxarray/rioxarray.py
emmanuelmathot Jan 30, 2026
f733dbc
Update rioxarray/rioxarray.py
emmanuelmathot Jan 30, 2026
921cece
Update rioxarray/rioxarray.py
emmanuelmathot Jan 30, 2026
10e0019
Update rioxarray/rioxarray.py
emmanuelmathot Jan 30, 2026
af4efcf
pylint
emmanuelmathot Jan 30, 2026
41caadd
remove uv.lock
emmanuelmathot Jan 30, 2026
2fcf2e7
Add asterisk for keyword-only arguments in write_crs and write_transf…
emmanuelmathot Jan 30, 2026
d35805d
Refactor write_crs and write_transform functions to use keyword-only …
emmanuelmathot Jan 30, 2026
bf0e184
satisfy mypy
emmanuelmathot Jan 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions docs/getting_started/crs_management.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,56 @@
"Operations on xarray objects can cause data loss. Due to this, rioxarray writes and expects the spatial reference information to exist in the coordinates."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conventions\n",
"\n",
"rioxarray supports multiple conventions for storing geospatial metadata. The convention system provides a flexible way to read and write CRS and transform information.\n",
"\n",
"### Supported Conventions\n",
"\n",
"- **CF (Climate and Forecasts)**: The default convention, using `grid_mapping` coordinates with attributes like `spatial_ref`, `crs_wkt`, and `GeoTransform`. This is the standard for netCDF files in the geospatial community.\n",
"\n",
"### How Conventions Work\n",
"\n",
"- **Reading**: If a convention is set globally, that convention is tried **first** for better performance. If not found, other conventions are tried as fallback. This allows you to optimize reads when you know the data format.\n",
"- **Writing**: Uses the global `convention` setting (default: CF) or a per-method `convention` parameter.\n",
"\n",
"### Setting the Convention\n",
"\n",
"You can set the convention globally using `set_options()`:\n",
"\n",
"```python\n",
"from rioxarray import set_options\n",
"from rioxarray.enum import Convention\n",
"\n",
"# Set globally - reads will try CF first, writes will use CF\n",
"set_options(convention=Convention.CF)\n",
"\n",
"# Or use as a context manager\n",
"with set_options(convention=Convention.CF):\n",
" # CF convention is tried first when reading\n",
" crs = data.rio.crs\n",
" # CF convention is used for writing\n",
" data.rio.write_crs(\"EPSG:4326\", inplace=True)\n",
"```\n",
"\n",
"Or specify the convention per-method (for writing only):\n",
"\n",
"```python\n",
"from rioxarray.enum import Convention\n",
"\n",
"data.rio.write_crs(\"EPSG:4326\", convention=Convention.CF, inplace=True)\n",
"```\n",
"\n",
"#### API Documentation\n",
"\n",
"- [rioxarray.set_options](../rioxarray.rst#rioxarray.set_options)\n",
"- [rioxarray.enum.Convention](../rioxarray.rst#rioxarray.enum.Convention)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -524,12 +574,12 @@
"## Setting the CRS\n",
"\n",
"Use the `rio.write_crs` method to set the CRS on your `xarray.Dataset` or `xarray.DataArray`.\n",
"This modifies the `xarray.Dataset` or `xarray.DataArray` and sets the CRS in a CF compliant manner.\n",
"This modifies the `xarray.Dataset` or `xarray.DataArray` and sets the CRS using the configured convention (default: CF).\n",
"\n",
"- [rio.write_crs()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_crs)\n",
"- [rio.crs](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.crs)\n",
"\n",
"**Note:** It is recommended to use `rio.write_crs()` if you want the CRS to persist on the Dataset/DataArray and to write the CRS CF compliant metadata. Calling only `rio.set_crs()` CRS storage method is lossy and will not modify the Dataset/DataArray metadata."
"**Note:** It is recommended to use `rio.write_crs()` if you want the CRS to persist on the Dataset/DataArray and to write convention-compliant metadata. Calling only `rio.set_crs()` is lossy and will not modify the Dataset/DataArray metadata."
]
},
{
Expand Down
2 changes: 2 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ History

Latest
------
- ENH: Add `convention` option to `set_options()` for future multi-convention support (pull #899)
- REF: Extract CF convention logic to `_convention/cf.py` module (pull #899)


0.21.0
Expand Down
9 changes: 9 additions & 0 deletions docs/rioxarray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ rioxarray.show_versions
.. autofunction:: rioxarray.show_versions


rioxarray.enum module
---------------------

.. automodule:: rioxarray.enum
:members:
:undoc-members:
:show-inheritance:


rioxarray `rio` accessors
--------------------------

Expand Down
214 changes: 214 additions & 0 deletions rioxarray/_convention/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
"""Convention modules for rioxarray.

This module defines the common interface for convention implementations
and provides helpers for selecting conventions.
"""

from typing import Dict, Optional, Protocol, Tuple, Union

import rasterio.crs
import xarray
from affine import Affine

from rioxarray._convention.cf import CFConvention
from rioxarray._options import CONVENTION, get_option
from rioxarray.crs import crs_from_user_input
from rioxarray.enum import Convention


class ConventionProtocol(Protocol):
"""Protocol defining the interface for convention modules."""

@staticmethod
def read_crs(
obj: Union[xarray.Dataset, xarray.DataArray], **kwargs
) -> Optional[rasterio.crs.CRS]:
"""Read CRS from the object using this convention."""

@staticmethod
def read_transform(
obj: Union[xarray.Dataset, xarray.DataArray], **kwargs
) -> Optional[Affine]:
"""Read transform from the object using this convention."""

@staticmethod
def read_spatial_dimensions(
obj: Union[xarray.Dataset, xarray.DataArray],
) -> Optional[Tuple[str, str]]:
"""Read spatial dimensions (y_dim, x_dim) from the object using this convention."""

@staticmethod
def write_crs(
obj: Union[xarray.Dataset, xarray.DataArray],
crs: rasterio.crs.CRS,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
crs: rasterio.crs.CRS,
*,
crs: rasterio.crs.CRS,

**kwargs,
) -> Union[xarray.Dataset, xarray.DataArray]:
"""Write CRS to the object using this convention."""

@staticmethod
def write_transform(
obj: Union[xarray.Dataset, xarray.DataArray],
transform: Affine,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
transform: Affine,
*,
transform: Affine,

**kwargs,
) -> Union[xarray.Dataset, xarray.DataArray]:
"""Write transform to the object using this convention."""


# Convention classes mapped by Convention enum
_CONVENTION_MODULES: Dict[Convention, ConventionProtocol] = {
Convention.CF: CFConvention # type: ignore[dict-item]
}


def _get_convention(convention: Convention | None) -> ConventionProtocol:
"""
Get the convention module for writing.

Parameters
----------
convention : Convention enum value or None
The convention to use. If None, uses the global default.

Returns
-------
ConventionProtocol
The module implementing the convention
"""
if convention is None:
convention = get_option(CONVENTION) or Convention.CF
convention = Convention(convention)
return _CONVENTION_MODULES[convention]


def read_crs_auto(
obj: Union[xarray.Dataset, xarray.DataArray],
**kwargs,
) -> Optional[rasterio.crs.CRS]:
"""
Auto-detect and read CRS by trying convention readers.

If a convention is set globally via set_options(), that convention
is tried first for better performance. Then other conventions are
tried as fallback.

Parameters
----------
obj : xarray.Dataset or xarray.DataArray
Object to read CRS from
**kwargs
Convention-specific parameters (e.g., grid_mapping for CF)

Returns
-------
rasterio.crs.CRS or None
CRS object, or None if not found in any convention
"""
# Try the configured convention first (if set)
configured_convention = get_option(CONVENTION)
if configured_convention is not None:
result = _CONVENTION_MODULES[configured_convention].read_crs(obj, **kwargs)
if result is not None:
return result

# Try all other conventions
for conv_enum, convention in _CONVENTION_MODULES.items():
if conv_enum == configured_convention:
continue # Already tried this one
result = convention.read_crs(obj, **kwargs)
if result is not None:
return result

# Legacy fallback: look in attrs for 'crs' (not part of any convention)
try:
return crs_from_user_input(obj.attrs["crs"])
except KeyError:
pass

return None


def read_transform_auto(
obj: Union[xarray.Dataset, xarray.DataArray],
**kwargs,
) -> Optional[Affine]:
"""
Auto-detect and read transform by trying convention readers.

If a convention is set globally via set_options(), that convention
is tried first for better performance. Then other conventions are
tried as fallback.

Parameters
----------
obj : xarray.Dataset or xarray.DataArray
Object to read transform from
**kwargs
Convention-specific parameters (e.g., grid_mapping for CF)

Returns
-------
affine.Affine or None
Transform object, or None if not found in any convention
"""
# Try the configured convention first (if set)
configured_convention = get_option(CONVENTION)
if configured_convention is not None:
result = _CONVENTION_MODULES[configured_convention].read_transform(
obj, **kwargs
)
if result is not None:
return result

# Try all other conventions
for conv_enum, convention in _CONVENTION_MODULES.items():
if conv_enum == configured_convention:
continue # Already tried this one
result = convention.read_transform(obj, **kwargs)
if result is not None:
return result

# Legacy fallback: look in attrs for 'transform' (not part of any convention)
try:
return Affine(*obj.attrs["transform"][:6])
except KeyError:
pass

return None


def read_spatial_dimensions_auto(
obj: Union[xarray.Dataset, xarray.DataArray],
) -> Optional[Tuple[str, str]]:
"""
Auto-detect and read spatial dimensions by trying convention readers.

If a convention is set globally via set_options(), that convention
is tried first for better performance. Then other conventions are
tried as fallback.

Parameters
----------
obj : xarray.Dataset or xarray.DataArray
Object to read spatial dimensions from

Returns
-------
tuple of (y_dim, x_dim) or None
Tuple of dimension names, or None if not found in any convention
"""
# Try the configured convention first (if set)
configured_convention = get_option(CONVENTION)
if configured_convention is not None:
result = _CONVENTION_MODULES[configured_convention].read_spatial_dimensions(obj)
if result is not None:
return result

# Try all other conventions
for conv_enum, convention in _CONVENTION_MODULES.items():
if conv_enum == configured_convention:
continue # Already tried this one
result = convention.read_spatial_dimensions(obj)
if result is not None:
return result

return None
Loading
Loading