Skip to content

Commit 5185d95

Browse files
authored
Convert nD bounds arrays to (n-1)D vertex arrays (#250)
1 parent ac9eba3 commit 5185d95

File tree

2 files changed

+51
-25
lines changed

2 files changed

+51
-25
lines changed

cf_xarray/helpers.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77

88
def bounds_to_vertices(
9-
bounds: DataArray, bounds_dim: str, order: Optional[str] = "counterclockwise"
9+
bounds: DataArray,
10+
bounds_dim: str,
11+
core_dims=None,
12+
order: Optional[str] = "counterclockwise",
1013
) -> DataArray:
1114
"""
1215
Convert bounds variable to vertices. There 2 covered cases:
@@ -18,15 +21,18 @@ def bounds_to_vertices(
1821
Parameters
1922
----------
2023
bounds : DataArray
21-
The bounds to convert. Must be of shape (N, 2) or (N, M, 4).
24+
The bounds to convert.
2225
bounds_dim : str
2326
The name of the bounds dimension of `bounds` (the one of length 2 or 4).
2427
order : {'counterclockwise', 'clockwise', None}
25-
Valid for 2D coordinates only (bounds of shape (N, M, 4), ignored otherwise.
28+
Valid for 2D coordinates only (i.e. bounds of shape (..., N, M, 4), ignored otherwise.
2629
Order the bounds are given in, assuming that ax0-ax1-upward is a right handed
2730
coordinate system, where ax0 and ax1 are the two first dimensions of `bounds`.
2831
If None, the counterclockwise version is computed and then verified. If the
2932
check fails the clockwise version is returned. See Notes for more details.
33+
core_dims : list, optional
34+
List of core dimensions for apply_ufunc. This must not include bounds_dims.
35+
The shape of (*core_dims, bounds_dim) must be (N, 2) or (N, M, 4).
3036
3137
Returns
3238
-------
@@ -44,41 +50,60 @@ def bounds_to_vertices(
4450
4551
Please refer to the CF conventions document : http://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#cell-boundaries.
4652
"""
47-
# Get old and new dimension names and retranspose array to have bounds dim at axis 0.
48-
bnd_dim = (
49-
bounds_dim if isinstance(bounds_dim, str) else bounds.get_axis_num(bounds_dim)
53+
54+
if core_dims is None:
55+
core_dims = [dim for dim in bounds.dims if dim != bounds_dim]
56+
57+
output_sizes = {f"{dim}_vertices": bounds.sizes[dim] + 1 for dim in core_dims}
58+
output_core_dims = list(output_sizes.keys())
59+
60+
n_core_dims = len(core_dims)
61+
nbounds = bounds[bounds_dim].size
62+
63+
if not (n_core_dims == 2 and nbounds == 4) and not (
64+
n_core_dims == 1 and nbounds == 2
65+
):
66+
raise ValueError(
67+
f"Bounds format not understood. Got {bounds.dims} with shape {bounds.shape}."
68+
)
69+
70+
return xr.apply_ufunc(
71+
_bounds_helper,
72+
bounds,
73+
input_core_dims=[core_dims + [bounds_dim]],
74+
dask="parallelized",
75+
kwargs={"n_core_dims": n_core_dims, "nbounds": nbounds, "order": order},
76+
output_core_dims=[output_core_dims],
77+
dask_gufunc_kwargs=dict(output_sizes=output_sizes),
78+
output_dtypes=[bounds.dtype],
5079
)
51-
old_dims = [dim for dim in bounds.dims if dim != bnd_dim]
52-
new_dims = [f"{dim}_vertices" for dim in old_dims]
53-
values = bounds.transpose(bnd_dim, *old_dims).data
54-
if len(old_dims) == 2 and bounds.ndim == 3 and bounds[bnd_dim].size == 4:
80+
81+
82+
def _bounds_helper(values, n_core_dims, nbounds, order):
83+
if n_core_dims == 2 and nbounds == 4:
5584
# Vertices case (2D lat/lon)
5685
if order in ["counterclockwise", None]:
5786
# Names assume we are drawing axis 1 upward et axis 2 rightward.
58-
bot_left = values[0, :, :]
59-
bot_right = values[1, :, -1:]
60-
top_right = values[2, -1:, -1:]
61-
top_left = values[3, -1:, :]
87+
bot_left = values[..., :, :, 0]
88+
bot_right = values[..., :, -1:, 1]
89+
top_right = values[..., -1:, -1:, 2]
90+
top_left = values[..., -1:, :, 3]
6291
vertex_vals = np.block([[bot_left, bot_right], [top_left, top_right]])
6392
if order is None: # We verify if the ccw version works.
6493
calc_bnds = vertices_to_bounds(vertex_vals).values
6594
order = "counterclockwise" if np.all(calc_bnds == values) else "clockwise"
6695
if order == "clockwise":
67-
bot_left = values[0, :, :]
68-
top_left = values[1, -1:, :]
69-
top_right = values[2, -1:, -1:]
70-
bot_right = values[3, :, -1:]
96+
bot_left = values[..., :, :, 0]
97+
top_left = values[..., -1:, :, 1]
98+
top_right = values[..., -1:, -1:, 2]
99+
bot_right = values[..., :, -1:, 3]
71100
# Our asumption was wrong, axis 1 is rightward and axis 2 is upward
72101
vertex_vals = np.block([[bot_left, bot_right], [top_left, top_right]])
73-
elif len(old_dims) == 1 and bounds.ndim == 2 and bounds[bnd_dim].size == 2:
102+
elif n_core_dims == 1 and nbounds == 2:
74103
# Middle points case (1D lat/lon)
75-
vertex_vals = np.concatenate((values[0, :], values[1, -1:]))
76-
else:
77-
raise ValueError(
78-
f"Bounds format not understood. Got {bounds.dims} with shape {bounds.shape}."
79-
)
104+
vertex_vals = np.concatenate((values[..., :, 0], values[..., -1:, 1]), axis=-1)
80105

81-
return xr.DataArray(vertex_vals, dims=new_dims)
106+
return vertex_vals
82107

83108

84109
def vertices_to_bounds(

doc/whats-new.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ What's New
66
v0.6.1 (unreleased)
77
===================
88
- Support detecting pint-backed Variables with units-based criteria. By `Deepak Cherian`_.
9+
- Support reshaping nD bounds arrays to (n-1)D vertex arrays. By `Deepak Cherian`_.
910

1011
v0.6.0 (June 29, 2021)
1112
======================

0 commit comments

Comments
 (0)