cf-xarray 0.10.5__tar.gz → 0.10.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/workflows/ci.yaml +5 -5
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.pre-commit-config.yaml +5 -5
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/PKG-INFO +2 -3
- cf_xarray-0.10.7/cf_xarray/_version.py +1 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/accessor.py +5 -5
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/geometry.py +5 -3
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/helpers.py +228 -3
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_geometry.py +3 -1
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_helpers.py +55 -1
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray.egg-info/PKG-INFO +2 -3
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/ci/doc.yml +1 -1
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/conf.py +2 -2
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/pyproject.toml +2 -3
- cf_xarray-0.10.5/cf_xarray/_version.py +0 -1
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.binder/environment.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.deepsource.toml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/dependabot.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/release.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/workflows/parse_logs.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/workflows/pypi.yaml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/workflows/testpypi-release.yaml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.github/workflows/upstream-dev-ci.yaml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.gitignore +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.readthedocs.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/.tributors +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/CITATION.cff +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/LICENSE +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/README.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/__init__.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/coding.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/criteria.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/datasets.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/formatting.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/groupers.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/options.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/parametric.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/py.typed +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/scripts/make_doc.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/scripts/print_versions.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/sgrid.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/__init__.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/conftest.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_accessor.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_coding.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_groupers.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_options.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_parametric.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_scripts.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/tests/test_units.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/units.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray/utils.py +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray.egg-info/SOURCES.txt +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray.egg-info/dependency_links.txt +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray.egg-info/requires.txt +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/cf_xarray.egg-info/top_level.txt +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/ci/environment-all-min-deps.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/ci/environment-no-optional-deps.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/ci/environment.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/ci/upstream-dev-env.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/codecov.yml +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/2D_bounds_averaged.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/2D_bounds_error.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/2D_bounds_nonunique.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/Makefile +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/dataset-diagram-logo.tex +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/full-logo.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/logo.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/logo.svg +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/rich-repr-example.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/_static/style.css +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/api.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/bounds.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/cartopy_rotated_pole.png +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/coding.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/contributing.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/coord_axes.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/custom-criteria.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/dsg.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/examples/introduction.ipynb +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/faq.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/flags.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/geometry.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/grid_mappings.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/howtouse.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/index.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/make.bat +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/parametricz.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/plotting.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/provenance.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/quickstart.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/roadmap.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/selecting.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/sgrid_ugrid.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/units.md +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/doc/whats-new.rst +0 -0
- {cf_xarray-0.10.5 → cf_xarray-0.10.7}/setup.cfg +0 -0
|
@@ -27,11 +27,11 @@ jobs:
|
|
|
27
27
|
fail-fast: false
|
|
28
28
|
matrix:
|
|
29
29
|
os: ["ubuntu-latest"]
|
|
30
|
-
python-version: ["3.
|
|
30
|
+
python-version: ["3.11", "3.13"]
|
|
31
31
|
env: [""]
|
|
32
32
|
include:
|
|
33
33
|
- env: "all-min-deps"
|
|
34
|
-
python-version: "3.
|
|
34
|
+
python-version: "3.11"
|
|
35
35
|
os: ubuntu-latest
|
|
36
36
|
- env: "no-optional-deps"
|
|
37
37
|
python-version: "3.13"
|
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
run: |
|
|
71
71
|
pytest -n auto --cov=./ --cov-report=xml
|
|
72
72
|
- name: Upload code coverage to Codecov
|
|
73
|
-
uses: codecov/codecov-action@v5.4.
|
|
73
|
+
uses: codecov/codecov-action@v5.4.3
|
|
74
74
|
with:
|
|
75
75
|
file: ./coverage.xml
|
|
76
76
|
flags: unittests
|
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
shell: bash -l {0}
|
|
87
87
|
strategy:
|
|
88
88
|
matrix:
|
|
89
|
-
python-version: ["3.
|
|
89
|
+
python-version: ["3.11", "3.13"]
|
|
90
90
|
steps:
|
|
91
91
|
- uses: actions/checkout@v4
|
|
92
92
|
with:
|
|
@@ -109,7 +109,7 @@ jobs:
|
|
|
109
109
|
run: |
|
|
110
110
|
python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report cf_xarray/
|
|
111
111
|
- name: Upload mypy coverage to Codecov
|
|
112
|
-
uses: codecov/codecov-action@v5.4.
|
|
112
|
+
uses: codecov/codecov-action@v5.4.3
|
|
113
113
|
with:
|
|
114
114
|
file: mypy_report/cobertura.xml
|
|
115
115
|
flags: mypy
|
|
@@ -3,28 +3,28 @@ ci:
|
|
|
3
3
|
|
|
4
4
|
repos:
|
|
5
5
|
- repo: https://github.com/asottile/pyupgrade
|
|
6
|
-
rev: v3.
|
|
6
|
+
rev: v3.20.0
|
|
7
7
|
hooks:
|
|
8
8
|
- id: pyupgrade
|
|
9
9
|
args: ["--py310-plus"]
|
|
10
10
|
|
|
11
11
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
12
12
|
# Ruff version.
|
|
13
|
-
rev: 'v0.
|
|
13
|
+
rev: 'v0.12.2'
|
|
14
14
|
hooks:
|
|
15
15
|
- id: ruff
|
|
16
16
|
args: ["--fix", "--show-fixes"]
|
|
17
17
|
- id: ruff-format
|
|
18
18
|
|
|
19
19
|
- repo: https://github.com/rstcheck/rstcheck
|
|
20
|
-
rev: v6.2.
|
|
20
|
+
rev: v6.2.5
|
|
21
21
|
hooks:
|
|
22
22
|
- id: rstcheck
|
|
23
23
|
additional_dependencies: [sphinx, tomli]
|
|
24
24
|
args: ['--config', 'pyproject.toml']
|
|
25
25
|
|
|
26
26
|
- repo: https://github.com/executablebooks/mdformat
|
|
27
|
-
rev: 0.7.
|
|
27
|
+
rev: 0.7.22
|
|
28
28
|
hooks:
|
|
29
29
|
- id: mdformat
|
|
30
30
|
additional_dependencies:
|
|
@@ -55,7 +55,7 @@ repos:
|
|
|
55
55
|
- id: validate-cff
|
|
56
56
|
|
|
57
57
|
- repo: https://github.com/abravalheri/validate-pyproject
|
|
58
|
-
rev: v0.
|
|
58
|
+
rev: v0.24.1
|
|
59
59
|
hooks:
|
|
60
60
|
- id: validate-pyproject
|
|
61
61
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cf_xarray
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.7
|
|
4
4
|
Summary: A convenience wrapper for using CF attributes on xarray objects
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -214,11 +214,10 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
214
214
|
Classifier: Natural Language :: English
|
|
215
215
|
Classifier: Operating System :: OS Independent
|
|
216
216
|
Classifier: Programming Language :: Python
|
|
217
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
218
217
|
Classifier: Programming Language :: Python :: 3.11
|
|
219
218
|
Classifier: Programming Language :: Python :: 3.12
|
|
220
219
|
Classifier: Programming Language :: Python :: 3.13
|
|
221
|
-
Requires-Python: >=3.
|
|
220
|
+
Requires-Python: >=3.11
|
|
222
221
|
Description-Content-Type: text/x-rst
|
|
223
222
|
License-File: LICENSE
|
|
224
223
|
Requires-Dist: xarray>=2023.09.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.7"
|
|
@@ -39,8 +39,8 @@ except ImportError:
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
|
-
from xarray.core.weighted import (
|
|
43
|
-
Weighted,
|
|
42
|
+
from xarray.core.weighted import ( # type:ignore[import-not-found,no-redef,unused-ignore]
|
|
43
|
+
Weighted,
|
|
44
44
|
)
|
|
45
45
|
except ImportError:
|
|
46
46
|
from xarray.computation.weighted import ( # type:ignore[import-not-found,no-redef,unused-ignore]
|
|
@@ -1376,7 +1376,7 @@ class CFAccessor:
|
|
|
1376
1376
|
|
|
1377
1377
|
def curvefit(
|
|
1378
1378
|
self,
|
|
1379
|
-
coords: Hashable |
|
|
1379
|
+
coords: Hashable | Iterable[Hashable],
|
|
1380
1380
|
func: Callable[..., Any],
|
|
1381
1381
|
reduce_dims: Hashable | Iterable[Hashable] | None = None,
|
|
1382
1382
|
skipna: bool = True,
|
|
@@ -1386,8 +1386,8 @@ class CFAccessor:
|
|
|
1386
1386
|
kwargs: dict[str, Any] | None = None,
|
|
1387
1387
|
):
|
|
1388
1388
|
if coords is not None:
|
|
1389
|
-
if isinstance(coords, Hashable
|
|
1390
|
-
coords_iter: Iterable[Hashable
|
|
1389
|
+
if isinstance(coords, Hashable):
|
|
1390
|
+
coords_iter: Iterable[Hashable] = [coords]
|
|
1391
1391
|
else:
|
|
1392
1392
|
coords_iter = coords
|
|
1393
1393
|
coords = [
|
|
@@ -298,8 +298,10 @@ def encode_geometries(ds: xr.Dataset):
|
|
|
298
298
|
geom_var_names = [
|
|
299
299
|
name
|
|
300
300
|
for name, var in ds._variables.items()
|
|
301
|
-
if var.dtype == "
|
|
301
|
+
if var.dtype == "geometry"
|
|
302
|
+
or (var.dtype == "O" and isinstance(var.data.flat[0], SHAPELY_TYPES))
|
|
302
303
|
]
|
|
304
|
+
|
|
303
305
|
if not geom_var_names:
|
|
304
306
|
return ds
|
|
305
307
|
|
|
@@ -410,8 +412,8 @@ def reshape_unique_geometries(
|
|
|
410
412
|
out = out.unstack(temp_name)
|
|
411
413
|
|
|
412
414
|
# geom_var was reshaped also, reconstruct it from the unique values.
|
|
413
|
-
|
|
414
|
-
out[geom_var] = ds[geom_var].isel({old_name:
|
|
415
|
+
unique_indexes_da = xr.DataArray(unique_indexes, dims=(new_dim,))
|
|
416
|
+
out[geom_var] = ds[geom_var].isel({old_name: unique_indexes_da})
|
|
415
417
|
if old_name not in ds.coords:
|
|
416
418
|
# If there was no coord before, drop the dummy one we made.
|
|
417
419
|
out = out.drop_vars(old_name) # type: ignore[arg-type,unused-ignore] # Hashable/str stuff
|
|
@@ -175,19 +175,79 @@ def bounds_to_vertices(
|
|
|
175
175
|
f"Bounds format not understood. Got {bounds.dims} with shape {bounds.shape}."
|
|
176
176
|
)
|
|
177
177
|
|
|
178
|
+
core_dim_coords = {
|
|
179
|
+
dim: bounds.coords[dim].values for dim in core_dims if dim in bounds.coords
|
|
180
|
+
}
|
|
181
|
+
core_dim_orders = _get_core_dim_orders(core_dim_coords)
|
|
182
|
+
|
|
178
183
|
return xr.apply_ufunc(
|
|
179
184
|
_bounds_helper,
|
|
180
185
|
bounds,
|
|
181
186
|
input_core_dims=[core_dims + [bounds_dim]],
|
|
182
187
|
dask="parallelized",
|
|
183
|
-
kwargs={
|
|
188
|
+
kwargs={
|
|
189
|
+
"n_core_dims": n_core_dims,
|
|
190
|
+
"nbounds": nbounds,
|
|
191
|
+
"order": order,
|
|
192
|
+
"core_dim_orders": core_dim_orders,
|
|
193
|
+
},
|
|
184
194
|
output_core_dims=[output_core_dims],
|
|
185
195
|
dask_gufunc_kwargs=dict(output_sizes=output_sizes),
|
|
186
196
|
output_dtypes=[bounds.dtype],
|
|
187
197
|
)
|
|
188
198
|
|
|
189
199
|
|
|
190
|
-
def
|
|
200
|
+
def _get_core_dim_orders(core_dim_coords: dict[str, np.ndarray]) -> dict[str, str]:
|
|
201
|
+
"""
|
|
202
|
+
Determine the order (ascending, descending, or mixed) of each core dimension
|
|
203
|
+
based on its coordinates.
|
|
204
|
+
|
|
205
|
+
Repeated (equal) coordinates are ignored when determining the order. If all
|
|
206
|
+
coordinates are equal, the order is treated as "ascending".
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
core_dim_coords : dict of str to np.ndarray
|
|
211
|
+
A dictionary mapping dimension names to their coordinate arrays.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
core_dim_orders : dict of str to str
|
|
216
|
+
A dictionary mapping each dimension name to a string indicating the order:
|
|
217
|
+
- "ascending": strictly increasing (ignoring repeated values)
|
|
218
|
+
- "descending": strictly decreasing (ignoring repeated values)
|
|
219
|
+
- "mixed": neither strictly increasing nor decreasing (ignoring repeated values)
|
|
220
|
+
"""
|
|
221
|
+
core_dim_orders = {}
|
|
222
|
+
|
|
223
|
+
for dim, coords in core_dim_coords.items():
|
|
224
|
+
diffs = np.diff(coords)
|
|
225
|
+
|
|
226
|
+
# Handle datetime64 and timedelta64 safely for both numpy 1.26.4 and numpy 2
|
|
227
|
+
if np.issubdtype(coords.dtype, np.datetime64) or np.issubdtype(
|
|
228
|
+
coords.dtype, np.timedelta64
|
|
229
|
+
):
|
|
230
|
+
# Cast to float64 for safe comparison
|
|
231
|
+
diffs_float = diffs.astype("float64")
|
|
232
|
+
nonzero_diffs = diffs_float[diffs_float != 0]
|
|
233
|
+
else:
|
|
234
|
+
zero = 0
|
|
235
|
+
nonzero_diffs = diffs[diffs != zero]
|
|
236
|
+
|
|
237
|
+
if nonzero_diffs.size == 0:
|
|
238
|
+
# All values are equal, treat as ascending
|
|
239
|
+
core_dim_orders[dim] = "ascending"
|
|
240
|
+
elif np.all(nonzero_diffs > 0):
|
|
241
|
+
core_dim_orders[dim] = "ascending"
|
|
242
|
+
elif np.all(nonzero_diffs < 0):
|
|
243
|
+
core_dim_orders[dim] = "descending"
|
|
244
|
+
else:
|
|
245
|
+
core_dim_orders[dim] = "mixed"
|
|
246
|
+
|
|
247
|
+
return core_dim_orders
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _bounds_helper(values, n_core_dims, nbounds, order, core_dim_orders):
|
|
191
251
|
if n_core_dims == 2 and nbounds == 4:
|
|
192
252
|
# Vertices case (2D lat/lon)
|
|
193
253
|
if order in ["counterclockwise", None]:
|
|
@@ -211,11 +271,176 @@ def _bounds_helper(values, n_core_dims, nbounds, order):
|
|
|
211
271
|
vertex_vals = np.block([[bot_left, bot_right], [top_left, top_right]])
|
|
212
272
|
elif n_core_dims == 1 and nbounds == 2:
|
|
213
273
|
# Middle points case (1D lat/lon)
|
|
214
|
-
vertex_vals =
|
|
274
|
+
vertex_vals = _get_ordered_vertices(values, core_dim_orders)
|
|
215
275
|
|
|
216
276
|
return vertex_vals
|
|
217
277
|
|
|
218
278
|
|
|
279
|
+
def _get_ordered_vertices(
|
|
280
|
+
bounds: np.ndarray, core_dim_orders: dict[str, str]
|
|
281
|
+
) -> np.ndarray:
|
|
282
|
+
"""
|
|
283
|
+
Convert a bounds array of shape (..., N, 2) or (N, 2) into a 1D array of vertices.
|
|
284
|
+
|
|
285
|
+
This function reconstructs the vertices from a bounds array, handling both
|
|
286
|
+
monotonic and non-monotonic cases.
|
|
287
|
+
|
|
288
|
+
Monotonic bounds (all values strictly increase or decrease when flattened):
|
|
289
|
+
- Concatenate the left endpoints (bounds[..., :, 0]) with the last right
|
|
290
|
+
endpoint (bounds[..., -1, 1]) to form the vertices.
|
|
291
|
+
|
|
292
|
+
Non-monotonic bounds:
|
|
293
|
+
- Determine the order of the core dimension(s) ('ascending' or 'descending').
|
|
294
|
+
- For ascending order:
|
|
295
|
+
- Use the minimum of each interval as the vertex.
|
|
296
|
+
- Use the maximum of the last interval as the final vertex.
|
|
297
|
+
- For descending order:
|
|
298
|
+
- Use the maximum of each interval as the vertex.
|
|
299
|
+
- Use the minimum of the last interval as the final vertex.
|
|
300
|
+
- Vertices are then sorted to match the coordinate direction.
|
|
301
|
+
|
|
302
|
+
Features:
|
|
303
|
+
- Handles both ascending and descending bounds.
|
|
304
|
+
- Preserves repeated coordinates if present.
|
|
305
|
+
- Output shape is (..., N+1) or (N+1,).
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
bounds : np.ndarray
|
|
310
|
+
Array of bounds, typically with shape (N, 2) or (..., N, 2).
|
|
311
|
+
core_dim_orders : dict[str, str]
|
|
312
|
+
Dictionary mapping core dimension names to their order ('ascending' or
|
|
313
|
+
'descending'). Used for sorting the vertices.
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
np.ndarray
|
|
318
|
+
Array of vertices with shape (..., N+1) or (N+1,).
|
|
319
|
+
"""
|
|
320
|
+
order = _get_order_of_core_dims(core_dim_orders)
|
|
321
|
+
|
|
322
|
+
if _is_bounds_monotonic(bounds):
|
|
323
|
+
vertices = np.concatenate((bounds[..., :, 0], bounds[..., -1:, 1]), axis=-1)
|
|
324
|
+
else:
|
|
325
|
+
if order == "ascending":
|
|
326
|
+
endpoints = np.minimum(bounds[..., :, 0], bounds[..., :, 1])
|
|
327
|
+
last_endpoint = np.maximum(bounds[..., -1, 0], bounds[..., -1, 1])
|
|
328
|
+
elif order == "descending":
|
|
329
|
+
endpoints = np.maximum(bounds[..., :, 0], bounds[..., :, 1])
|
|
330
|
+
last_endpoint = np.minimum(bounds[..., -1, 0], bounds[..., -1, 1])
|
|
331
|
+
|
|
332
|
+
vertices = np.concatenate(
|
|
333
|
+
[endpoints, np.expand_dims(last_endpoint, axis=-1)], axis=-1
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
vertices = _sort_vertices(vertices, order)
|
|
337
|
+
|
|
338
|
+
return vertices
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _is_bounds_monotonic(bounds: np.ndarray) -> bool:
|
|
342
|
+
"""Check if the bounds are monotonic.
|
|
343
|
+
|
|
344
|
+
Arrays are monotonic if all values are increasing or decreasing. This
|
|
345
|
+
functions ignores an intervals where consecutive values are equal, which
|
|
346
|
+
represent repeated coordinates.
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
arr : np.ndarray
|
|
351
|
+
Numpy array to check, typically with shape (..., N, 2).
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
bool
|
|
356
|
+
True if the flattened array is increasing or decreasing, False otherwise.
|
|
357
|
+
"""
|
|
358
|
+
# NOTE: Python 3.10 uses numpy 1.26.4. If the input is a datetime64 array,
|
|
359
|
+
# numpy 1.26.4 may raise: numpy.core._exceptions._UFuncInputCastingError:
|
|
360
|
+
# Cannot cast ufunc 'greater' input 0 from dtype('<m8[ns]') to dtype('<m8')
|
|
361
|
+
# with casting rule 'same_kind' To avoid this, always cast to float64 before
|
|
362
|
+
# np.diff.
|
|
363
|
+
arr_numeric = bounds.astype("float64").flatten()
|
|
364
|
+
diffs = np.diff(arr_numeric)
|
|
365
|
+
nonzero_diffs = diffs[diffs != 0]
|
|
366
|
+
|
|
367
|
+
# All values are equal, treat as monotonic
|
|
368
|
+
if nonzero_diffs.size == 0:
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
return bool(np.all(nonzero_diffs > 0) or np.all(nonzero_diffs < 0))
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _get_order_of_core_dims(core_dim_orders: dict[str, str]) -> str:
|
|
375
|
+
"""
|
|
376
|
+
Determines the common order of core dimensions from a dictionary of
|
|
377
|
+
dimension orders.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
core_dim_orders : dict of str
|
|
382
|
+
A dictionary mapping dimension names to their respective order strings.
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
order : str
|
|
387
|
+
The common order string shared by all core dimensions.
|
|
388
|
+
|
|
389
|
+
Raises
|
|
390
|
+
------
|
|
391
|
+
ValueError
|
|
392
|
+
If the core dimension orders are not all aligned (i.e., not all values
|
|
393
|
+
are the same).
|
|
394
|
+
"""
|
|
395
|
+
orders = set(core_dim_orders.values())
|
|
396
|
+
|
|
397
|
+
if len(orders) != 1:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
f"All core dimension orders must be aligned. Got orders: {core_dim_orders}"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
order = next(iter(orders))
|
|
403
|
+
|
|
404
|
+
return order
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _sort_vertices(vertices: np.ndarray, order: str) -> np.ndarray:
|
|
408
|
+
"""
|
|
409
|
+
Sorts the vertices array along the last axis in ascending or descending order.
|
|
410
|
+
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
vertices : np.ndarray
|
|
414
|
+
An array of vertices to be sorted. Sorting is performed along the last
|
|
415
|
+
axis.
|
|
416
|
+
order : str
|
|
417
|
+
The order in which to sort the vertices. Must be either "ascending" or
|
|
418
|
+
any other value for descending order.
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
np.ndarray
|
|
423
|
+
The sorted array of vertices, with the same shape as the input.
|
|
424
|
+
|
|
425
|
+
Examples
|
|
426
|
+
--------
|
|
427
|
+
>>> import numpy as np
|
|
428
|
+
>>> vertices = np.array([[3, 1, 2], [6, 5, 4]])
|
|
429
|
+
>>> _sort_vertices(vertices, "ascending")
|
|
430
|
+
array([[1, 2, 3],
|
|
431
|
+
[4, 5, 6]])
|
|
432
|
+
>>> _sort_vertices(vertices, "descending")
|
|
433
|
+
array([[3, 2, 1],
|
|
434
|
+
[6, 5, 4]])
|
|
435
|
+
"""
|
|
436
|
+
if order == "ascending":
|
|
437
|
+
new_vertices = np.sort(vertices, axis=-1)
|
|
438
|
+
else:
|
|
439
|
+
new_vertices = np.sort(vertices, axis=-1)[..., ::-1]
|
|
440
|
+
|
|
441
|
+
return new_vertices
|
|
442
|
+
|
|
443
|
+
|
|
219
444
|
def vertices_to_bounds(
|
|
220
445
|
vertices: DataArray, out_dims: Sequence[str] = ("bounds", "x", "y")
|
|
221
446
|
) -> DataArray:
|
|
@@ -507,5 +507,7 @@ def test_encode_decode(geometry_ds, polygon_geometry):
|
|
|
507
507
|
)
|
|
508
508
|
multi_ds = xr.merge([polyds, geometry_ds[1]])
|
|
509
509
|
for ds in (geometry_ds[1], polygon_geometry.to_dataset(), geom_dim_ds, multi_ds):
|
|
510
|
-
|
|
510
|
+
encoded = encode_geometries(ds)
|
|
511
|
+
assert len(encoded.data_vars) > len(ds.data_vars)
|
|
512
|
+
roundtripped = decode_geometries(encoded)
|
|
511
513
|
xr.testing.assert_identical(ds, roundtripped)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import xarray as xr
|
|
1
2
|
from numpy.testing import assert_array_equal
|
|
2
3
|
from xarray.testing import assert_equal
|
|
3
4
|
|
|
@@ -12,7 +13,7 @@ except ImportError:
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def test_bounds_to_vertices() -> None:
|
|
15
|
-
# 1D case
|
|
16
|
+
# 1D case (stricly monotonic, descending bounds)
|
|
16
17
|
ds = airds.cf.add_bounds(["lon", "lat", "time"])
|
|
17
18
|
lat_c = cfxr.bounds_to_vertices(ds.lat_bounds, bounds_dim="bounds")
|
|
18
19
|
assert_array_equal(ds.lat.values + 1.25, lat_c.values[:-1])
|
|
@@ -34,6 +35,59 @@ def test_bounds_to_vertices() -> None:
|
|
|
34
35
|
lon_no = cfxr.bounds_to_vertices(rotds.lon_bounds, bounds_dim="bounds", order=None)
|
|
35
36
|
assert_equal(lon_no, lon_ccw)
|
|
36
37
|
|
|
38
|
+
# 2D case (monotonicly increasing coords, non-monotonic bounds)
|
|
39
|
+
bounds_2d_desc = xr.DataArray(
|
|
40
|
+
[[50.5, 50.0], [51.0, 50.5], [51.0, 50.5], [52.0, 51.5], [52.5, 52.0]],
|
|
41
|
+
dims=("lat", "bounds"),
|
|
42
|
+
coords={"lat": [50.75, 50.75, 51.25, 51.75, 52.25]},
|
|
43
|
+
)
|
|
44
|
+
expected_vertices_2d_desc = xr.DataArray(
|
|
45
|
+
[50.0, 50.5, 50.5, 51.5, 52.0, 52.5],
|
|
46
|
+
dims=["lat_vertices"],
|
|
47
|
+
)
|
|
48
|
+
vertices_2d_desc = cfxr.bounds_to_vertices(bounds_2d_desc, bounds_dim="bounds")
|
|
49
|
+
assert_equal(expected_vertices_2d_desc, vertices_2d_desc)
|
|
50
|
+
|
|
51
|
+
# 3D case (non-monotonic bounds, monotonicly increasing coords)
|
|
52
|
+
bounds_3d = xr.DataArray(
|
|
53
|
+
[
|
|
54
|
+
[
|
|
55
|
+
[50.0, 50.5],
|
|
56
|
+
[50.5, 51.0],
|
|
57
|
+
[51.0, 51.5],
|
|
58
|
+
[51.5, 52.0],
|
|
59
|
+
[52.0, 52.5],
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
[60.0, 60.5],
|
|
63
|
+
[60.5, 61.0],
|
|
64
|
+
[61.0, 61.5],
|
|
65
|
+
[61.5, 62.0],
|
|
66
|
+
[62.0, 62.5],
|
|
67
|
+
],
|
|
68
|
+
],
|
|
69
|
+
dims=("extra", "lat", "bounds"),
|
|
70
|
+
coords={
|
|
71
|
+
"extra": [0, 1],
|
|
72
|
+
"lat": [0, 1, 2, 3, 4],
|
|
73
|
+
"bounds": [0, 1],
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
expected_vertices_3d = xr.DataArray(
|
|
77
|
+
[
|
|
78
|
+
[50.0, 50.5, 51.0, 51.5, 52.0, 52.5],
|
|
79
|
+
[60.0, 60.5, 61.0, 61.5, 62.0, 62.5],
|
|
80
|
+
],
|
|
81
|
+
dims=("extra", "lat_vertices"),
|
|
82
|
+
coords={
|
|
83
|
+
"extra": [0, 1],
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
vertices_3d = cfxr.bounds_to_vertices(
|
|
87
|
+
bounds_3d, bounds_dim="bounds", core_dims=["lat"]
|
|
88
|
+
)
|
|
89
|
+
assert_equal(vertices_3d, expected_vertices_3d)
|
|
90
|
+
|
|
37
91
|
# Transposing the array changes the bounds direction
|
|
38
92
|
ds = mollwds.transpose("x", "y", "x_vertices", "y_vertices", "bounds")
|
|
39
93
|
lon_cw = cfxr.bounds_to_vertices(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cf_xarray
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.7
|
|
4
4
|
Summary: A convenience wrapper for using CF attributes on xarray objects
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -214,11 +214,10 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
214
214
|
Classifier: Natural Language :: English
|
|
215
215
|
Classifier: Operating System :: OS Independent
|
|
216
216
|
Classifier: Programming Language :: Python
|
|
217
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
218
217
|
Classifier: Programming Language :: Python :: 3.11
|
|
219
218
|
Classifier: Programming Language :: Python :: 3.12
|
|
220
219
|
Classifier: Programming Language :: Python :: 3.13
|
|
221
|
-
Requires-Python: >=3.
|
|
220
|
+
Requires-Python: >=3.11
|
|
222
221
|
Description-Content-Type: text/x-rst
|
|
223
222
|
License-File: LICENSE
|
|
224
223
|
Requires-Dist: xarray>=2023.09.0
|
|
@@ -265,8 +265,8 @@ napoleon_use_param = True
|
|
|
265
265
|
napoleon_use_rtype = True
|
|
266
266
|
|
|
267
267
|
numpydoc_show_class_members = False
|
|
268
|
-
#
|
|
269
|
-
numpydoc_validation_checks =
|
|
268
|
+
# Disable numpydoc validation to avoid compatibility issues with Sphinx 8
|
|
269
|
+
numpydoc_validation_checks = set()
|
|
270
270
|
# don't report on objects that match any of these regex
|
|
271
271
|
numpydoc_validation_exclude = {
|
|
272
272
|
"cf_xarray.accessor.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "cf_xarray"
|
|
3
3
|
description = "A convenience wrapper for using CF attributes on xarray objects"
|
|
4
4
|
readme = "README.rst"
|
|
5
|
-
requires-python = ">=3.
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
6
|
license = {file = "LICENSE"}
|
|
7
7
|
keywords = ["xarray", "metadata", "CF conventions"]
|
|
8
8
|
classifiers = [
|
|
@@ -11,7 +11,6 @@ classifiers = [
|
|
|
11
11
|
"Natural Language :: English",
|
|
12
12
|
"Operating System :: OS Independent",
|
|
13
13
|
"Programming Language :: Python",
|
|
14
|
-
"Programming Language :: Python :: 3.10",
|
|
15
14
|
"Programming Language :: Python :: 3.11",
|
|
16
15
|
"Programming Language :: Python :: 3.12",
|
|
17
16
|
"Programming Language :: Python :: 3.13",
|
|
@@ -55,7 +54,7 @@ write_to_template= '__version__ = "{version}"'
|
|
|
55
54
|
tag_regex= "^(?P<prefix>v)?(?P<version>[^\\+]+)(?P<suffix>.*)?$"
|
|
56
55
|
|
|
57
56
|
[tool.ruff]
|
|
58
|
-
target-version = "
|
|
57
|
+
target-version = "py311"
|
|
59
58
|
builtins = ["ellipsis"]
|
|
60
59
|
exclude = [
|
|
61
60
|
".eggs",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.10.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|