Skip to content

Commit fae9460

Browse files
authored
Add grid_mapping for geometries if possible (#521)
1 parent 5dfae7f commit fae9460

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

cf_xarray/geometry.py

+49-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@
1717
]
1818

1919

20+
# Useful convention language:
21+
# 1. Whether linked to normal CF space-time coordinates with a nodes attribute or not, inclusion of such coordinates is
22+
# recommended to maintain backward compatibility with software that has not implemented geometry capabilities.
23+
# 2. The geometry node coordinate variables must each have an axis attribute whose allowable values are X, Y, and Z.
24+
# 3. If a coordinates attribute is carried by the geometry container variable or its parent data variable, then those coordinate variables
25+
# that have a meaningful correspondence with node coordinates are indicated as such by a nodes attribute that names the corresponding node
26+
# coordinates, but only if the grid_mapping associated the geometry node variables is the same as that of the coordinate variables.
27+
# If a different grid mapping is used, then the provided coordinates must not have the nodes attribute.
28+
#
29+
# Interpretation:
30+
# 1. node coordinates are exact; the 'normal' coordinates are a reasonable value to use, if you do not know how to interpret the nodes.
31+
32+
2033
def decode_geometries(encoded: xr.Dataset) -> xr.Dataset:
2134
"""
2235
Decode CF encoded geometries to a numpy object array containing shapely geometries.
@@ -282,6 +295,14 @@ def shapely_to_cf(geometries: xr.DataArray | Sequence, grid_mapping: str | None
282295
----------
283296
Please refer to the CF conventions document: http://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#geometries
284297
"""
298+
299+
if isinstance(geometries, xr.DataArray) and grid_mapping is not None:
300+
raise DeprecationWarning(
301+
"Explicitly passing `grid_mapping` with DataArray of geometries is deprecated. "
302+
"Please set a `grid_mapping` attribute on `geometries`, ",
303+
"and set the grid mapping variable as a coordinate",
304+
)
305+
285306
# Get all types to call the appropriate translation function.
286307
types = {
287308
geom.item().geom_type if isinstance(geom, xr.DataArray) else geom.geom_type
@@ -300,19 +321,38 @@ def shapely_to_cf(geometries: xr.DataArray | Sequence, grid_mapping: str | None
300321

301322
ds[GEOMETRY_CONTAINER_NAME].attrs.update(coordinates="crd_x crd_y")
302323

324+
if (
325+
grid_mapping is None
326+
and isinstance(geometries, xr.DataArray)
327+
and (grid_mapping_varname := geometries.attrs.get("grid_mapping"))
328+
):
329+
if grid_mapping_varname in geometries.coords:
330+
grid_mapping = geometries.coords[grid_mapping_varname].attrs[
331+
"grid_mapping_name"
332+
]
333+
for name_ in ["x", "y", "crd_x", "crd_y"]:
334+
ds[name_].attrs["grid_mapping"] = grid_mapping_varname
335+
303336
# Special treatment of selected grid mappings
304-
if grid_mapping == "longitude_latitude":
305-
# Special case for longitude_latitude grid mapping
337+
if grid_mapping in ["latitude_longitude", "rotated_latitude_longitude"]:
338+
# Special case for longitude_latitude type grid mappings
306339
ds = ds.rename(crd_x="lon", crd_y="lat")
307-
ds.lon.attrs.update(units="degrees_east", standard_name="longitude")
308-
ds.lat.attrs.update(units="degrees_north", standard_name="latitude")
340+
if grid_mapping == "latitude_longitude":
341+
ds.lon.attrs.update(units="degrees_east", standard_name="longitude")
342+
ds.x.attrs.update(units="degrees_east", standard_name="longitude")
343+
ds.lat.attrs.update(units="degrees_north", standard_name="latitude")
344+
ds.y.attrs.update(units="degrees_north", standard_name="latitude")
345+
elif grid_mapping == "rotated_latitude_longitude":
346+
ds.lon.attrs.update(units="degrees", standard_name="grid_longitude")
347+
ds.x.attrs.update(units="degrees", standard_name="grid_longitude")
348+
ds.lat.attrs.update(units="degrees", standard_name="grid_latitude")
349+
ds.y.attrs.update(units="degrees", standard_name="grid_latitude")
309350
ds[GEOMETRY_CONTAINER_NAME].attrs.update(coordinates="lon lat")
310-
ds.x.attrs.update(units="degrees_east", standard_name="longitude")
311-
ds.y.attrs.update(units="degrees_north", standard_name="latitude")
312351
elif grid_mapping is not None:
313-
raise NotImplementedError(
314-
f"Only grid mapping longitude_latitude is implemented. Got {grid_mapping}."
315-
)
352+
ds.crd_x.attrs.update(standard_name="projection_x_coordinate")
353+
ds.x.attrs.update(standard_name="projection_x_coordinate")
354+
ds.crd_y.attrs.update(standard_name="projection_y_coordinate")
355+
ds.y.attrs.update(standard_name="projection_y_coordinate")
316356

317357
return ds
318358

cf_xarray/tests/test_geometry.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ def test_shapely_to_cf(geometry_ds):
265265
[
266266
in_ds.drop_vars("geometry").isel(index=slice(1, None)),
267267
cfxr.shapely_to_cf(
268-
in_ds.geometry.isel(index=slice(1, None)),
269-
grid_mapping="longitude_latitude",
268+
in_ds.geometry.isel(index=slice(1, None)).data,
269+
grid_mapping="latitude_longitude",
270270
),
271271
]
272272
)
@@ -355,10 +355,11 @@ def test_shapely_to_cf_errors():
355355
with pytest.raises(ValueError, match="Mixed geometry types are not supported"):
356356
cfxr.shapely_to_cf(geoms)
357357

358-
with pytest.raises(
359-
NotImplementedError, match="Only grid mapping longitude_latitude"
360-
):
361-
cfxr.shapely_to_cf([Point(4, 5)], grid_mapping="albers_conical_equal_area")
358+
encoded = cfxr.shapely_to_cf(
359+
[Point(4, 5)], grid_mapping="albers_conical_equal_area"
360+
)
361+
assert encoded["x"].attrs["standard_name"] == "projection_x_coordinate"
362+
assert encoded["y"].attrs["standard_name"] == "projection_y_coordinate"
362363

363364

364365
@requires_shapely
@@ -491,7 +492,6 @@ def test_reshape_unique_geometries(geometry_ds):
491492

492493
@requires_shapely
493494
def test_encode_decode(geometry_ds, polygon_geometry):
494-
495495
geom_dim_ds = xr.Dataset()
496496
geom_dim_ds = geom_dim_ds.assign_coords(
497497
xr.Coordinates(

0 commit comments

Comments
 (0)