cf-xarray 0.10.10__tar.gz → 0.11.0__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.10 → cf_xarray-0.11.0}/.github/workflows/ci.yaml +2 -2
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/workflows/pypi.yaml +5 -5
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/workflows/testpypi-release.yaml +2 -2
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.pre-commit-config.yaml +4 -4
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/PKG-INFO +1 -1
- cf_xarray-0.11.0/cf_xarray/_version.py +1 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/accessor.py +123 -21
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/datasets.py +188 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/helpers.py +16 -17
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_accessor.py +241 -4
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray.egg-info/PKG-INFO +1 -1
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/index.rst +1 -1
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/uv.lock +265 -235
- cf_xarray-0.10.10/cf_xarray/_version.py +0 -1
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.binder/environment.yml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.deepsource.toml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/dependabot.yml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/release.yml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/workflows/parse_logs.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.github/workflows/upstream-dev-ci.yaml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.gitignore +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.readthedocs.yml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/.tributors +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/CITATION.cff +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/LICENSE +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/README.rst +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/__init__.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/coding.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/criteria.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/formatting.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/geometry.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/groupers.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/options.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/parametric.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/py.typed +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/scripts/make_doc.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/scripts/print_versions.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/sgrid.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/__init__.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/conftest.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_coding.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_geometry.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_groupers.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_helpers.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_options.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_parametric.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_scripts.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/tests/test_units.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/units.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray/utils.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray.egg-info/SOURCES.txt +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray.egg-info/dependency_links.txt +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray.egg-info/requires.txt +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/cf_xarray.egg-info/top_level.txt +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/codecov.yml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/2D_bounds_averaged.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/2D_bounds_error.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/2D_bounds_nonunique.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/Makefile +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/dataset-diagram-logo.tex +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/full-logo.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/logo.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/logo.svg +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/rich-repr-example.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/_static/style.css +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/api.rst +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/bounds.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/cartopy_rotated_pole.png +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/coding.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/conf.py +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/contributing.rst +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/coord_axes.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/custom-criteria.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/dsg.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/examples/introduction.ipynb +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/faq.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/flags.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/geometry.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/grid_mappings.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/howtouse.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/make.bat +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/parametricz.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/plotting.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/provenance.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/quickstart.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/roadmap.rst +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/selecting.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/sgrid_ugrid.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/units.md +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/doc/whats-new.rst +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/pyproject.toml +0 -0
- {cf_xarray-0.10.10 → cf_xarray-0.11.0}/setup.cfg +0 -0
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
pytest -n auto --cov=./ --cov-report=xml
|
|
59
59
|
|
|
60
60
|
- name: Upload code coverage to Codecov
|
|
61
|
-
uses: codecov/codecov-action@
|
|
61
|
+
uses: codecov/codecov-action@v6.0.0
|
|
62
62
|
with:
|
|
63
63
|
file: ./coverage.xml
|
|
64
64
|
flags: unittests
|
|
@@ -98,7 +98,7 @@ jobs:
|
|
|
98
98
|
python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report cf_xarray/
|
|
99
99
|
|
|
100
100
|
- name: Upload mypy coverage to Codecov
|
|
101
|
-
uses: codecov/codecov-action@
|
|
101
|
+
uses: codecov/codecov-action@v6.0.0
|
|
102
102
|
with:
|
|
103
103
|
file: mypy_report/cobertura.xml
|
|
104
104
|
flags: mypy
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
else
|
|
42
42
|
echo "✅ Looks good"
|
|
43
43
|
fi
|
|
44
|
-
- uses: actions/upload-artifact@
|
|
44
|
+
- uses: actions/upload-artifact@v7
|
|
45
45
|
with:
|
|
46
46
|
name: releases
|
|
47
47
|
path: dist
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
name: Install Python
|
|
55
55
|
with:
|
|
56
56
|
python-version: "3.11"
|
|
57
|
-
- uses: actions/download-artifact@
|
|
57
|
+
- uses: actions/download-artifact@v8
|
|
58
58
|
with:
|
|
59
59
|
name: releases
|
|
60
60
|
path: dist
|
|
@@ -72,7 +72,7 @@ jobs:
|
|
|
72
72
|
|
|
73
73
|
- name: Publish package to TestPyPI
|
|
74
74
|
if: github.event_name == 'push'
|
|
75
|
-
uses: pypa/gh-action-pypi-publish@v1.
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@v1.14.0
|
|
76
76
|
with:
|
|
77
77
|
password: ${{ secrets.TESTPYPI_TOKEN }}
|
|
78
78
|
repository_url: https://test.pypi.org/legacy/
|
|
@@ -91,11 +91,11 @@ jobs:
|
|
|
91
91
|
id-token: write
|
|
92
92
|
|
|
93
93
|
steps:
|
|
94
|
-
- uses: actions/download-artifact@
|
|
94
|
+
- uses: actions/download-artifact@v8
|
|
95
95
|
with:
|
|
96
96
|
name: releases
|
|
97
97
|
path: dist
|
|
98
98
|
- name: Publish package to PyPI
|
|
99
|
-
uses: pypa/gh-action-pypi-publish@v1.
|
|
99
|
+
uses: pypa/gh-action-pypi-publish@v1.14.0
|
|
100
100
|
with:
|
|
101
101
|
verbose: true
|
|
@@ -53,7 +53,7 @@ jobs:
|
|
|
53
53
|
echo "✅ Looks good"
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
-
- uses: actions/upload-artifact@
|
|
56
|
+
- uses: actions/upload-artifact@v7
|
|
57
57
|
with:
|
|
58
58
|
name: releases
|
|
59
59
|
path: dist
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
name: Install Python
|
|
67
67
|
with:
|
|
68
68
|
python-version: "3.11"
|
|
69
|
-
- uses: actions/download-artifact@
|
|
69
|
+
- uses: actions/download-artifact@v8
|
|
70
70
|
with:
|
|
71
71
|
name: releases
|
|
72
72
|
path: dist
|
|
@@ -3,14 +3,14 @@ ci:
|
|
|
3
3
|
|
|
4
4
|
repos:
|
|
5
5
|
- repo: https://github.com/asottile/pyupgrade
|
|
6
|
-
rev: v3.
|
|
6
|
+
rev: v3.21.2
|
|
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.15.9'
|
|
14
14
|
hooks:
|
|
15
15
|
- id: ruff
|
|
16
16
|
args: ["--fix", "--show-fixes"]
|
|
@@ -24,7 +24,7 @@ repos:
|
|
|
24
24
|
args: ['--config', 'pyproject.toml']
|
|
25
25
|
|
|
26
26
|
- repo: https://github.com/executablebooks/mdformat
|
|
27
|
-
rev: 0.
|
|
27
|
+
rev: 1.0.0
|
|
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.25
|
|
59
59
|
hooks:
|
|
60
60
|
- id: validate-pyproject
|
|
61
61
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.11.0"
|
|
@@ -48,6 +48,11 @@ except ImportError:
|
|
|
48
48
|
Weighted,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
try:
|
|
52
|
+
from regex import match as regex_match
|
|
53
|
+
except ImportError:
|
|
54
|
+
from re import match as regex_match # type: ignore[no-redef]
|
|
55
|
+
|
|
51
56
|
|
|
52
57
|
from . import parametric, sgrid
|
|
53
58
|
from .criteria import (
|
|
@@ -305,12 +310,6 @@ def _get_custom_criteria(
|
|
|
305
310
|
List[str]
|
|
306
311
|
Variable name(s) in parent xarray object that matches axis, coordinate, or custom ``key``
|
|
307
312
|
"""
|
|
308
|
-
|
|
309
|
-
try:
|
|
310
|
-
from regex import match as regex_match
|
|
311
|
-
except ImportError:
|
|
312
|
-
from re import match as regex_match # type: ignore[no-redef]
|
|
313
|
-
|
|
314
313
|
if criteria is None:
|
|
315
314
|
if not OPTIONS["custom_criteria"]:
|
|
316
315
|
return []
|
|
@@ -589,7 +588,83 @@ def _create_grid_mapping(
|
|
|
589
588
|
cf_name = var.attrs.get("grid_mapping_name", var_name)
|
|
590
589
|
|
|
591
590
|
# Create CRS from the grid mapping variable
|
|
592
|
-
|
|
591
|
+
if cf_name == "reduced_gaussian":
|
|
592
|
+
# pyproj does not recognize "reduced_gaussian" as a grid mapping name,
|
|
593
|
+
# but the grid uses geographic (lat/lon) coordinates on a sphere or
|
|
594
|
+
# spheroid. Build a geographic CRS from the earth shape parameters.
|
|
595
|
+
crs = pyproj.CRS.from_json_dict(
|
|
596
|
+
{
|
|
597
|
+
"$schema": "https://proj.org/schemas/v0.6/projjson.schema.json",
|
|
598
|
+
"type": "GeographicCRS",
|
|
599
|
+
"name": "Reduced Gaussian Grid",
|
|
600
|
+
"datum": {
|
|
601
|
+
"type": "GeodeticReferenceFrame",
|
|
602
|
+
"name": "Unknown",
|
|
603
|
+
"ellipsoid": {
|
|
604
|
+
"name": "Custom",
|
|
605
|
+
"semi_major_axis": var.attrs.get("semi_major_axis", 6371229.0),
|
|
606
|
+
"semi_minor_axis": var.attrs.get("semi_minor_axis", 6371229.0),
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
"coordinate_system": {
|
|
610
|
+
"subtype": "ellipsoidal",
|
|
611
|
+
"axis": [
|
|
612
|
+
{
|
|
613
|
+
"name": "Latitude",
|
|
614
|
+
"abbreviation": "lat",
|
|
615
|
+
"direction": "north",
|
|
616
|
+
"unit": "degree",
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"name": "Longitude",
|
|
620
|
+
"abbreviation": "lon",
|
|
621
|
+
"direction": "east",
|
|
622
|
+
"unit": "degree",
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
}
|
|
627
|
+
)
|
|
628
|
+
elif cf_name == "healpix":
|
|
629
|
+
# pyproj does not recognize "healpix" as a grid mapping name,
|
|
630
|
+
# but the grid uses geographic (lat/lon) coordinates on a sphere.
|
|
631
|
+
# Build a geographic CRS from the earth_radius parameter.
|
|
632
|
+
earth_radius = var.attrs.get("earth_radius", 6371229.0)
|
|
633
|
+
crs = pyproj.CRS.from_json_dict(
|
|
634
|
+
{
|
|
635
|
+
"$schema": "https://proj.org/schemas/v0.6/projjson.schema.json",
|
|
636
|
+
"type": "GeographicCRS",
|
|
637
|
+
"name": "HEALPix Grid",
|
|
638
|
+
"datum": {
|
|
639
|
+
"type": "GeodeticReferenceFrame",
|
|
640
|
+
"name": "Unknown",
|
|
641
|
+
"ellipsoid": {
|
|
642
|
+
"name": "Custom",
|
|
643
|
+
"semi_major_axis": earth_radius,
|
|
644
|
+
"semi_minor_axis": earth_radius,
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
"coordinate_system": {
|
|
648
|
+
"subtype": "ellipsoidal",
|
|
649
|
+
"axis": [
|
|
650
|
+
{
|
|
651
|
+
"name": "Latitude",
|
|
652
|
+
"abbreviation": "lat",
|
|
653
|
+
"direction": "north",
|
|
654
|
+
"unit": "degree",
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
"name": "Longitude",
|
|
658
|
+
"abbreviation": "lon",
|
|
659
|
+
"direction": "east",
|
|
660
|
+
"unit": "degree",
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
},
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
else:
|
|
667
|
+
crs = pyproj.CRS.from_cf(var.attrs)
|
|
593
668
|
|
|
594
669
|
# Get associated coordinate variables, fallback to dimension names
|
|
595
670
|
coordinates: list[Hashable] = grid_mapping_dict.get(var_name, [])
|
|
@@ -600,16 +675,47 @@ def _create_grid_mapping(
|
|
|
600
675
|
# The appropriate values of the standard_name depend on the grid mapping and are given in Appendix F, Grid Mappings.
|
|
601
676
|
# """
|
|
602
677
|
if not coordinates and len(grid_mapping_dict) == 1:
|
|
603
|
-
if
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
678
|
+
if cf_name == "healpix":
|
|
679
|
+
# For HEALPix grids, the primary coordinate is the pixel index.
|
|
680
|
+
coords_found = apply_mapper(
|
|
681
|
+
_get_with_standard_name, ds, "healpix_index", error=False, default=[[]]
|
|
682
|
+
)
|
|
683
|
+
coordinates = list(itertools.chain(coords_found))
|
|
684
|
+
elif cf_name == "reduced_gaussian":
|
|
685
|
+
# For reduced gaussian grids, the primary coordinate is the grid
|
|
686
|
+
# point index. For compressed subsets, also look for the gather
|
|
687
|
+
# variable (with compress attribute).
|
|
688
|
+
idx_coords = apply_mapper(
|
|
689
|
+
_get_with_standard_name,
|
|
690
|
+
ds,
|
|
691
|
+
"reduced_gaussian_index",
|
|
692
|
+
error=False,
|
|
693
|
+
default=[[]],
|
|
694
|
+
)
|
|
695
|
+
coordinates = list(itertools.chain(idx_coords))
|
|
696
|
+
# Also find any compress/gather variable that references
|
|
697
|
+
# the detected index coordinate(s)
|
|
698
|
+
for vname in ds.coords:
|
|
699
|
+
if "compress" in ds[vname].attrs:
|
|
700
|
+
compress_target = ds[vname].attrs["compress"]
|
|
701
|
+
if any(c in compress_target for c in coordinates):
|
|
702
|
+
if vname not in coordinates:
|
|
703
|
+
coordinates.append(vname)
|
|
704
|
+
else:
|
|
705
|
+
if crs.to_cf().get("grid_mapping_name") == "rotated_latitude_longitude":
|
|
706
|
+
xname, yname = "grid_longitude", "grid_latitude"
|
|
707
|
+
elif crs.is_geographic:
|
|
708
|
+
xname, yname = "longitude", "latitude"
|
|
709
|
+
elif crs.is_projected:
|
|
710
|
+
xname, yname = "projection_x_coordinate", "projection_y_coordinate"
|
|
711
|
+
|
|
712
|
+
x = apply_mapper(
|
|
713
|
+
_get_with_standard_name, ds, xname, error=False, default=[[]]
|
|
714
|
+
)
|
|
715
|
+
y = apply_mapper(
|
|
716
|
+
_get_with_standard_name, ds, yname, error=False, default=[[]]
|
|
717
|
+
)
|
|
718
|
+
coordinates = list(itertools.chain(x, y))
|
|
613
719
|
|
|
614
720
|
return GridMapping(name=cf_name, crs=crs, array=da, coordinates=tuple(coordinates))
|
|
615
721
|
|
|
@@ -1707,10 +1813,6 @@ class CFAccessor:
|
|
|
1707
1813
|
for k, v in value.items()
|
|
1708
1814
|
)
|
|
1709
1815
|
)
|
|
1710
|
-
|
|
1711
|
-
elif value is Ellipsis:
|
|
1712
|
-
pass
|
|
1713
|
-
|
|
1714
1816
|
else:
|
|
1715
1817
|
# things like sum which have dim
|
|
1716
1818
|
newvalue = [
|
|
@@ -784,6 +784,52 @@ try:
|
|
|
784
784
|
np.zeros((10, 20)),
|
|
785
785
|
{"standard_name": "projected_y_coordinate"},
|
|
786
786
|
)
|
|
787
|
+
# HEALPix dataset: refinement_level=1, nside=2, 12*nside^2 = 48 pixels, nested ordering
|
|
788
|
+
healpix_ds = xr.Dataset(
|
|
789
|
+
{
|
|
790
|
+
"tas": xr.DataArray(
|
|
791
|
+
np.random.default_rng(42).standard_normal((2, 48)).astype(np.float32),
|
|
792
|
+
dims=["time", "healpix_index"],
|
|
793
|
+
attrs={
|
|
794
|
+
"standard_name": "air_temperature",
|
|
795
|
+
"units": "K",
|
|
796
|
+
"coordinates": "height",
|
|
797
|
+
"grid_mapping": "healpix",
|
|
798
|
+
"cell_methods": "time: mean area: mean",
|
|
799
|
+
},
|
|
800
|
+
),
|
|
801
|
+
"healpix": xr.DataArray(
|
|
802
|
+
np.int32(0),
|
|
803
|
+
attrs={
|
|
804
|
+
"grid_mapping_name": "healpix",
|
|
805
|
+
"earth_radius": 6371000,
|
|
806
|
+
"indexing_scheme": "nested",
|
|
807
|
+
"refinement_level": 1,
|
|
808
|
+
},
|
|
809
|
+
),
|
|
810
|
+
},
|
|
811
|
+
coords={
|
|
812
|
+
"time": xr.DataArray(
|
|
813
|
+
[0.0, 1.0],
|
|
814
|
+
dims=["time"],
|
|
815
|
+
attrs={
|
|
816
|
+
"standard_name": "time",
|
|
817
|
+
"calendar": "proleptic_gregorian",
|
|
818
|
+
"units": "days since 2025-06-01",
|
|
819
|
+
},
|
|
820
|
+
),
|
|
821
|
+
"healpix_index": xr.DataArray(
|
|
822
|
+
np.arange(48),
|
|
823
|
+
dims=["healpix_index"],
|
|
824
|
+
attrs={"standard_name": "healpix_index"},
|
|
825
|
+
),
|
|
826
|
+
"height": xr.DataArray(
|
|
827
|
+
np.float32(2.0),
|
|
828
|
+
attrs={"standard_name": "height", "units": "m"},
|
|
829
|
+
),
|
|
830
|
+
},
|
|
831
|
+
)
|
|
832
|
+
|
|
787
833
|
except ImportError:
|
|
788
834
|
pass
|
|
789
835
|
|
|
@@ -815,3 +861,145 @@ def encoded_point_dataset():
|
|
|
815
861
|
{"geometry": "geometry_container"},
|
|
816
862
|
)
|
|
817
863
|
return ds
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
# --- Reduced Gaussian Grid test fixtures ---
|
|
867
|
+
# A tiny O2 octahedral reduced gaussian grid with 4 latitudes and 40 total points.
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _create_reduced_gaussian_global():
|
|
871
|
+
"""Create a small O2 reduced gaussian grid dataset (full global)."""
|
|
872
|
+
lat = np.array([59.44, 19.47, -19.47, -59.44])
|
|
873
|
+
pl = np.array([8, 12, 12, 8], dtype=np.int32)
|
|
874
|
+
total = int(np.sum(pl)) # 40
|
|
875
|
+
|
|
876
|
+
rng = np.random.default_rng(42)
|
|
877
|
+
temp_data = rng.standard_normal((1, 1, total)).astype(np.float32)
|
|
878
|
+
|
|
879
|
+
ds = xr.Dataset(
|
|
880
|
+
{
|
|
881
|
+
"air_temperature": xr.DataArray(
|
|
882
|
+
temp_data,
|
|
883
|
+
dims=["time", "height", "reduced_gaussian_index"],
|
|
884
|
+
attrs={
|
|
885
|
+
"grid_mapping": "reduced_gaussian",
|
|
886
|
+
"coordinates": "reduced_gaussian_index",
|
|
887
|
+
"standard_name": "air_temperature",
|
|
888
|
+
"units": "K",
|
|
889
|
+
},
|
|
890
|
+
),
|
|
891
|
+
"reduced_gaussian": xr.DataArray(
|
|
892
|
+
np.int32(0),
|
|
893
|
+
attrs={
|
|
894
|
+
"grid_mapping_name": "reduced_gaussian",
|
|
895
|
+
"grid_subtype": "octahedral",
|
|
896
|
+
"points_per_latitude": "pl",
|
|
897
|
+
"latitude_dimension": "lat",
|
|
898
|
+
"semi_major_axis": 6371229.0,
|
|
899
|
+
"semi_minor_axis": 6371229.0,
|
|
900
|
+
},
|
|
901
|
+
),
|
|
902
|
+
},
|
|
903
|
+
coords={
|
|
904
|
+
"lat": xr.DataArray(
|
|
905
|
+
lat,
|
|
906
|
+
dims=["lat"],
|
|
907
|
+
attrs={"standard_name": "latitude", "units": "degrees_north"},
|
|
908
|
+
),
|
|
909
|
+
"pl": xr.DataArray(
|
|
910
|
+
pl,
|
|
911
|
+
dims=["lat"],
|
|
912
|
+
attrs={"long_name": "number of points per latitude"},
|
|
913
|
+
),
|
|
914
|
+
"reduced_gaussian_index": xr.DataArray(
|
|
915
|
+
np.arange(total, dtype=np.int32),
|
|
916
|
+
dims=["reduced_gaussian_index"],
|
|
917
|
+
attrs={"standard_name": "reduced_gaussian_index"},
|
|
918
|
+
),
|
|
919
|
+
"time": xr.DataArray(
|
|
920
|
+
[0.0], dims=["time"], attrs={"units": "hours since 2024-01-01"}
|
|
921
|
+
),
|
|
922
|
+
"height": xr.DataArray([2.0], dims=["height"], attrs={"units": "m"}),
|
|
923
|
+
},
|
|
924
|
+
)
|
|
925
|
+
return ds
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _create_reduced_gaussian_land():
|
|
929
|
+
"""Create a small O2 reduced gaussian grid with compression by gathering (land subset)."""
|
|
930
|
+
global_ds = _create_reduced_gaussian_global()
|
|
931
|
+
land_indices = np.array([2, 5, 10, 15, 20, 25, 30, 35], dtype=np.int32)
|
|
932
|
+
|
|
933
|
+
rng = np.random.default_rng(43)
|
|
934
|
+
temp_data = rng.standard_normal((1, 1, len(land_indices))).astype(np.float32)
|
|
935
|
+
|
|
936
|
+
ds = xr.Dataset(
|
|
937
|
+
{
|
|
938
|
+
"air_temperature": xr.DataArray(
|
|
939
|
+
temp_data,
|
|
940
|
+
dims=["time", "height", "grid_points"],
|
|
941
|
+
attrs={
|
|
942
|
+
"grid_mapping": "reduced_gaussian",
|
|
943
|
+
"coordinates": "time height reduced_gaussian_index",
|
|
944
|
+
},
|
|
945
|
+
),
|
|
946
|
+
"reduced_gaussian": global_ds["reduced_gaussian"],
|
|
947
|
+
},
|
|
948
|
+
coords={
|
|
949
|
+
"lat": global_ds["lat"],
|
|
950
|
+
"pl": global_ds["pl"],
|
|
951
|
+
"reduced_gaussian_index": global_ds["reduced_gaussian_index"],
|
|
952
|
+
"grid_points": xr.DataArray(
|
|
953
|
+
land_indices,
|
|
954
|
+
dims=["grid_points"],
|
|
955
|
+
attrs={"compress": "reduced_gaussian_index"},
|
|
956
|
+
),
|
|
957
|
+
"time": global_ds["time"],
|
|
958
|
+
"height": global_ds["height"],
|
|
959
|
+
},
|
|
960
|
+
)
|
|
961
|
+
return ds
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _create_reduced_gaussian_region():
|
|
965
|
+
"""Create a small O2 reduced gaussian grid with compression (regional subset)."""
|
|
966
|
+
global_ds = _create_reduced_gaussian_global()
|
|
967
|
+
region_indices = np.array([8, 9, 10, 11, 12, 13, 14], dtype=np.int32)
|
|
968
|
+
|
|
969
|
+
rng = np.random.default_rng(44)
|
|
970
|
+
temp_data = rng.standard_normal((1, 1, len(region_indices))).astype(np.float32)
|
|
971
|
+
|
|
972
|
+
ds = xr.Dataset(
|
|
973
|
+
{
|
|
974
|
+
"air_temperature": xr.DataArray(
|
|
975
|
+
temp_data,
|
|
976
|
+
dims=["time", "height", "grid_points"],
|
|
977
|
+
attrs={
|
|
978
|
+
"grid_mapping": "reduced_gaussian",
|
|
979
|
+
"coordinates": "time height reduced_gaussian_index",
|
|
980
|
+
},
|
|
981
|
+
),
|
|
982
|
+
"reduced_gaussian": global_ds["reduced_gaussian"],
|
|
983
|
+
},
|
|
984
|
+
coords={
|
|
985
|
+
"lat": global_ds["lat"],
|
|
986
|
+
"pl": global_ds["pl"],
|
|
987
|
+
"reduced_gaussian_index": global_ds["reduced_gaussian_index"],
|
|
988
|
+
"grid_points": xr.DataArray(
|
|
989
|
+
region_indices,
|
|
990
|
+
dims=["grid_points"],
|
|
991
|
+
attrs={
|
|
992
|
+
"standard_name": "reduced_gaussian_index",
|
|
993
|
+
"compress": "reduced_gaussian_index",
|
|
994
|
+
},
|
|
995
|
+
),
|
|
996
|
+
"time": global_ds["time"],
|
|
997
|
+
"height": global_ds["height"],
|
|
998
|
+
},
|
|
999
|
+
)
|
|
1000
|
+
return ds
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
reduced_gaussian_global_ds = _create_reduced_gaussian_global()
|
|
1004
|
+
reduced_gaussian_land_ds = _create_reduced_gaussian_land()
|
|
1005
|
+
reduced_gaussian_region_ds = _create_reduced_gaussian_region()
|
|
@@ -17,24 +17,23 @@ def _guess_bounds_1d(da, dim):
|
|
|
17
17
|
"""
|
|
18
18
|
if dim not in da.dims:
|
|
19
19
|
(dim,) = da.cf.axes[dim]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
upper = da
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
|
|
21
|
+
bound_position = 0.5
|
|
22
|
+
|
|
23
|
+
diff = da.diff(dim).pad({dim: (1, 1)}, mode="edge")
|
|
24
|
+
lower = da.copy(
|
|
25
|
+
deep=False,
|
|
26
|
+
data=da.data - bound_position * diff.isel({dim: slice(0, -1)}).data,
|
|
27
|
+
)
|
|
28
|
+
upper = da.copy(
|
|
29
|
+
deep=False,
|
|
30
|
+
data=da.data + bound_position * diff.isel({dim: slice(1, None)}).data,
|
|
31
|
+
)
|
|
32
|
+
return (
|
|
33
|
+
xr.concat([lower, upper], dim="bounds")
|
|
34
|
+
.transpose(..., "bounds")
|
|
35
|
+
.drop_attrs(deep=False)
|
|
33
36
|
)
|
|
34
|
-
result = xr.concat([first, bounds], dim=dim).transpose(..., "bounds")
|
|
35
|
-
if ADDED_INDEX:
|
|
36
|
-
result = result.drop_vars(dim)
|
|
37
|
-
return result.drop_attrs(deep=False)
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
def _guess_bounds_2d(da, dims):
|