Skip to content

Commit 413e95c

Browse files
committed
Remove rioxarray dependency
1 parent c6bb13a commit 413e95c

13 files changed

+148
-32
lines changed

docs/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@
155155
napoleon_type_aliases = {
156156
# general terms
157157
"sequence": ":term:`sequence`",
158-
"iterable": ":term:`iterable`",
158+
"Hashable": ":term:`sequence`",
159+
"iterable": "~collections.abc.Hashable",
159160
"callable": ":py:func:`callable`",
160161
"dict_like": ":term:`dict-like <mapping>`",
161162
"dict-like": ":term:`dict-like <mapping>`",

docs/rasterize/exactextract.ipynb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
"outputs": [],
2929
"source": [
3030
"import xarray as xr\n",
31+
"import xproj # noqa\n",
3132
"\n",
3233
"ds = xr.tutorial.open_dataset(\"eraint_uvz\")\n",
33-
"ds = ds.rio.write_crs(\"epsg:4326\")\n",
34+
"ds = ds.proj.assign_crs(spatial_ref=\"epsg:4326\")\n",
3435
"ds"
3536
]
3637
},

docs/rasterize/geometry_mask.ipynb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
"outputs": [],
2929
"source": [
3030
"import xarray as xr\n",
31+
"import xproj # noqa\n",
3132
"\n",
3233
"ds = xr.tutorial.open_dataset(\"eraint_uvz\")[[\"u\"]]\n",
33-
"ds = ds.rio.write_crs(\"epsg:4326\")\n",
34+
"ds = ds.proj.assign_crs(spatial_ref=\"epsg:4326\")\n",
3435
"ds"
3536
]
3637
},

docs/rasterize/rasterio.ipynb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
"outputs": [],
2929
"source": [
3030
"import xarray as xr\n",
31+
"import xproj # noqa\n",
3132
"\n",
3233
"ds = xr.tutorial.open_dataset(\"eraint_uvz\")\n",
33-
"ds = ds.rio.write_crs(\"epsg:4326\")\n",
34+
"ds = ds.proj.assign_crs(spatial_ref=\"epsg:4326\")\n",
3435
"ds"
3536
]
3637
},

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ docs = [
5454
"pooch",
5555
"dask-geopandas",
5656
"rasterio",
57-
"rioxarray",
5857
"exactextract",
5958
"sparse",
6059
"netCDF4",

src/rasterix/raster_index.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from rasterix.odc_compat import BoundingBox, bbox_intersection, bbox_union, maybe_int, snap_grid
2323
from rasterix.rioxarray_compat import guess_dims
24+
from rasterix.utils import get_affine
2425

2526
T_Xarray = TypeVar("T_Xarray", "DataArray", "Dataset")
2627

@@ -37,10 +38,14 @@ def assign_index(
3738
) -> T_Xarray:
3839
"""Assign a RasterIndex to an Xarray DataArray or Dataset.
3940
41+
By default, the affine transform is guessed by first looking for a ``GeoTransform`` attribute
42+
on a CF "grid mapping" variable (commonly ``"spatial_ref"``). If not present, then the affine is determined from 1D coordinate
43+
variables named ``x_dim`` and ``y_dim`` provided to this function.
44+
4045
Parameters
4146
----------
4247
obj : xarray.DataArray or xarray.Dataset
43-
The object to assign the index to. Must have a rio accessor with a transform.
48+
The object to assign the index to.
4449
x_dim : str, optional
4550
Name of the x dimension. If None, will be automatically detected.
4651
y_dim : str, optional
@@ -53,22 +58,37 @@ def assign_index(
5358
xarray.DataArray or xarray.Dataset
5459
The input object with RasterIndex coordinates assigned.
5560
61+
Notes
62+
-----
63+
The "grid mapping" variable is determined following the CF conventions:
64+
65+
- If a DataArray is provided, we look for an attribute named ``"grid_mapping"``.
66+
- For a Dataset, we pull the first detected ``"grid_mapping"`` attribute when iterating over data variables.
67+
68+
The value of this attribute is a variable name containing projection information (commonly ``"spatial_ref"``).
69+
We then look for a ``"GeoTransform"`` attribute on this variable (following GDAL convention).
70+
71+
References
72+
----------
73+
- `CF conventions document <http://cfconventions.org/Data/cf-conventions/cf-conventions-1.11/cf-conventions.html#grid-mappings-and-projections>`_.
74+
- `GDAL docs on GeoTransform <https://gdal.org/en/stable/tutorials/geotransforms_tut.html>`_.
75+
5676
Examples
5777
--------
5878
>>> import xarray as xr
59-
>>> import rioxarray # Required for rio accessor
79+
>>> import rioxarray # Required for reading TIFF
6080
>>> da = xr.open_dataset("path/to/raster.tif", engine="rasterio")
6181
>>> indexed_da = assign_index(da)
6282
"""
63-
import rioxarray # noqa
64-
6583
if x_dim is None or y_dim is None:
6684
guessed_x, guessed_y = guess_dims(obj)
6785
x_dim = x_dim or guessed_x
6886
y_dim = y_dim or guessed_y
6987

88+
affine = get_affine(obj, x_dim=x_dim, y_dim=y_dim, clear_transform=True)
89+
7090
index = RasterIndex.from_transform(
71-
obj.rio.transform(),
91+
affine,
7292
width=obj.sizes[x_dim],
7393
height=obj.sizes[y_dim],
7494
x_dim=x_dim,

src/rasterix/rasterize/rasterio.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
from rasterio.features import geometry_mask as geometry_mask_rio
1616
from rasterio.features import rasterize as rasterize_rio
1717

18-
from .utils import XAXIS, YAXIS, clip_to_bbox, get_affine, is_in_memory, prepare_for_dask
18+
from ..utils import get_affine
19+
from .utils import XAXIS, YAXIS, clip_to_bbox, is_in_memory, prepare_for_dask
1920

2021
F = TypeVar("F", bound=Callable[..., Any])
2122

@@ -161,7 +162,7 @@ def rasterize(
161162
obj = clip_to_bbox(obj, geometries, xdim=xdim, ydim=ydim)
162163

163164
rasterize_kwargs = dict(
164-
all_touched=all_touched, merge_alg=merge_alg, affine=get_affine(obj, xdim=xdim, ydim=ydim), env=env
165+
all_touched=all_touched, merge_alg=merge_alg, affine=get_affine(obj, x_dim=xdim, y_dim=ydim), env=env
165166
)
166167
# FIXME: box.crs == geometries.crs
167168

@@ -325,7 +326,7 @@ def geometry_mask(
325326
obj = clip_to_bbox(obj, geometries, xdim=xdim, ydim=ydim)
326327

327328
geometry_mask_kwargs = dict(
328-
all_touched=all_touched, affine=get_affine(obj, xdim=xdim, ydim=ydim), env=env
329+
all_touched=all_touched, affine=get_affine(obj, x_dim=xdim, y_dim=ydim), env=env
329330
)
330331

331332
if is_in_memory(obj=obj, geometries=geometries):

src/rasterix/utils.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import xarray as xr
2+
from affine import Affine
3+
4+
5+
def get_grid_mapping_var(obj: xr.Dataset | xr.DataArray) -> xr.DataArray | None:
6+
grid_mapping_var = None
7+
if isinstance(obj, xr.DataArray):
8+
if maybe := obj.attrs.get("grid_mapping", None):
9+
if maybe in obj.coords:
10+
grid_mapping_var = maybe
11+
else:
12+
# for datasets, grab the first one for simplicity
13+
for var in obj.data_vars.values():
14+
if maybe := var.attrs.get("grid_mapping"):
15+
if maybe in obj.coords:
16+
# make sure it exists and is not an out-of-date attribute
17+
grid_mapping_var = maybe
18+
break
19+
if grid_mapping_var is None and "spatial_ref" in obj.coords:
20+
# hardcode this
21+
grid_mapping_var = "spatial_ref"
22+
if grid_mapping_var is not None:
23+
return obj[grid_mapping_var]
24+
return None
25+
26+
27+
def get_affine(obj: xr.Dataset | xr.DataArray, *, x_dim="x", y_dim="y") -> Affine:
28+
"""
29+
Grabs an affine transform from an Xarray object.
30+
31+
This method will first look for the ``"GeoTransform"`` attribute on a variable named
32+
``"spatial_ref"``. If not, it will auto-guess the transform from the provided ``x_dim``,
33+
and ``y_dim``.
34+
35+
Parameters
36+
----------
37+
obj: xr.DataArray or xr.Dataset
38+
x_dim: str, optional
39+
Name of the X dimension coordinate variable.
40+
y_dim: str, optional
41+
Name of the Y dimension coordinate variable.
42+
43+
Returns
44+
-------
45+
affine: Affine
46+
"""
47+
grid_mapping_var = get_grid_mapping_var(obj)
48+
if grid_mapping_var is not None and (transform := grid_mapping_var.attrs.get("GeoTransform")):
49+
return Affine.from_gdal(*map(float, transform.split(" ")))
50+
else:
51+
x = obj.coords[x_dim]
52+
y = obj.coords[y_dim]
53+
if x.ndim != 1:
54+
raise ValueError(f"Coordinate variable {x_dim=!r} must be 1D.")
55+
if y.ndim != 1:
56+
raise ValueError(f"Coordinate variable {y_dim=!r} must be 1D.")
57+
dx = (x[1] - x[0]).item()
58+
dy = (y[1] - y[0]).item()
59+
return Affine.translation(
60+
x[0].item() - dx / 2, (y[0] if dy < 0 else y[-1]).item() - dy / 2
61+
) * Affine.scale(dx, dy)

tests/geometry_mask_snapshot.nc

2.41 KB
Binary file not shown.

tests/rasterize_snapshot.nc

2.41 KB
Binary file not shown.

0 commit comments

Comments
 (0)