cf-xarray 0.10.9__tar.gz → 0.10.11__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.9 → cf_xarray-0.10.11}/.github/workflows/ci.yaml +8 -8
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/workflows/pypi.yaml +6 -6
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/workflows/testpypi-release.yaml +5 -5
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/workflows/upstream-dev-ci.yaml +3 -3
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.pre-commit-config.yaml +4 -4
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/PKG-INFO +1 -1
- cf_xarray-0.10.11/cf_xarray/_version.py +1 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/accessor.py +118 -12
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/datasets.py +188 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/geometry.py +5 -1
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/helpers.py +7 -1
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_accessor.py +194 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/units.py +1 -1
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray.egg-info/PKG-INFO +1 -1
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/pyproject.toml +0 -1
- cf_xarray-0.10.9/cf_xarray/_version.py +0 -1
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.binder/environment.yml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.deepsource.toml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/dependabot.yml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/release.yml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.github/workflows/parse_logs.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.gitignore +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.readthedocs.yml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/.tributors +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/CITATION.cff +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/LICENSE +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/README.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/__init__.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/coding.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/criteria.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/formatting.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/groupers.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/options.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/parametric.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/py.typed +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/scripts/make_doc.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/scripts/print_versions.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/sgrid.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/__init__.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/conftest.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_coding.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_geometry.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_groupers.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_helpers.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_options.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_parametric.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_scripts.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/tests/test_units.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray/utils.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray.egg-info/SOURCES.txt +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray.egg-info/dependency_links.txt +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray.egg-info/requires.txt +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/cf_xarray.egg-info/top_level.txt +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/codecov.yml +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/2D_bounds_averaged.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/2D_bounds_error.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/2D_bounds_nonunique.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/Makefile +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/dataset-diagram-logo.tex +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/full-logo.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/logo.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/logo.svg +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/rich-repr-example.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/_static/style.css +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/api.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/bounds.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/cartopy_rotated_pole.png +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/coding.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/conf.py +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/contributing.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/coord_axes.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/custom-criteria.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/dsg.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/examples/introduction.ipynb +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/faq.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/flags.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/geometry.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/grid_mappings.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/howtouse.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/index.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/make.bat +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/parametricz.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/plotting.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/provenance.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/quickstart.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/roadmap.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/selecting.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/sgrid_ugrid.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/units.md +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/doc/whats-new.rst +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/setup.cfg +0 -0
- {cf_xarray-0.10.9 → cf_xarray-0.10.11}/uv.lock +0 -0
|
@@ -34,17 +34,17 @@ jobs:
|
|
|
34
34
|
python-version: "3.13"
|
|
35
35
|
os: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@v6
|
|
38
38
|
with:
|
|
39
39
|
fetch-depth: 0 # Fetch all history for all branches and tags.
|
|
40
40
|
|
|
41
41
|
- name: Set up Python
|
|
42
|
-
uses: actions/setup-python@
|
|
42
|
+
uses: actions/setup-python@v6
|
|
43
43
|
with:
|
|
44
44
|
python-version: ${{ matrix.python-version }}
|
|
45
45
|
|
|
46
46
|
- name: Install uv
|
|
47
|
-
uses: astral-sh/setup-uv@
|
|
47
|
+
uses: astral-sh/setup-uv@v7
|
|
48
48
|
with:
|
|
49
49
|
enable-cache: true
|
|
50
50
|
|
|
@@ -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@v5.5.
|
|
61
|
+
uses: codecov/codecov-action@v5.5.2
|
|
62
62
|
with:
|
|
63
63
|
file: ./coverage.xml
|
|
64
64
|
flags: unittests
|
|
@@ -73,17 +73,17 @@ jobs:
|
|
|
73
73
|
matrix:
|
|
74
74
|
python-version: ["3.11", "3.13"]
|
|
75
75
|
steps:
|
|
76
|
-
- uses: actions/checkout@
|
|
76
|
+
- uses: actions/checkout@v6
|
|
77
77
|
with:
|
|
78
78
|
fetch-depth: 0 # Fetch all history for all branches and tags.
|
|
79
79
|
|
|
80
80
|
- name: Set up Python
|
|
81
|
-
uses: actions/setup-python@
|
|
81
|
+
uses: actions/setup-python@v6
|
|
82
82
|
with:
|
|
83
83
|
python-version: ${{ matrix.python-version }}
|
|
84
84
|
|
|
85
85
|
- name: Install uv
|
|
86
|
-
uses: astral-sh/setup-uv@
|
|
86
|
+
uses: astral-sh/setup-uv@v7
|
|
87
87
|
with:
|
|
88
88
|
enable-cache: true
|
|
89
89
|
|
|
@@ -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@v5.5.
|
|
101
|
+
uses: codecov/codecov-action@v5.5.2
|
|
102
102
|
with:
|
|
103
103
|
file: mypy_report/cobertura.xml
|
|
104
104
|
flags: mypy
|
|
@@ -12,10 +12,10 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
if: github.repository == 'xarray-contrib/cf-xarray'
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
16
|
with:
|
|
17
17
|
fetch-depth: 0
|
|
18
|
-
- uses: actions/setup-python@
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
19
|
name: Install Python
|
|
20
20
|
with:
|
|
21
21
|
python-version: "3.11"
|
|
@@ -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@v6
|
|
45
45
|
with:
|
|
46
46
|
name: releases
|
|
47
47
|
path: dist
|
|
@@ -50,11 +50,11 @@ jobs:
|
|
|
50
50
|
needs: build-artifacts
|
|
51
51
|
runs-on: ubuntu-latest
|
|
52
52
|
steps:
|
|
53
|
-
- uses: actions/setup-python@
|
|
53
|
+
- uses: actions/setup-python@v6
|
|
54
54
|
name: Install Python
|
|
55
55
|
with:
|
|
56
56
|
python-version: "3.11"
|
|
57
|
-
- uses: actions/download-artifact@
|
|
57
|
+
- uses: actions/download-artifact@v7
|
|
58
58
|
with:
|
|
59
59
|
name: releases
|
|
60
60
|
path: dist
|
|
@@ -91,7 +91,7 @@ jobs:
|
|
|
91
91
|
id-token: write
|
|
92
92
|
|
|
93
93
|
steps:
|
|
94
|
-
- uses: actions/download-artifact@
|
|
94
|
+
- uses: actions/download-artifact@v7
|
|
95
95
|
with:
|
|
96
96
|
name: releases
|
|
97
97
|
path: dist
|
|
@@ -17,11 +17,11 @@ jobs:
|
|
|
17
17
|
if: ${{ contains( github.event.pull_request.labels.*.name, 'test-build') && github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@v6
|
|
21
21
|
with:
|
|
22
22
|
fetch-depth: 0
|
|
23
23
|
|
|
24
|
-
- uses: actions/setup-python@
|
|
24
|
+
- uses: actions/setup-python@v6
|
|
25
25
|
name: Install Python
|
|
26
26
|
with:
|
|
27
27
|
python-version: "3.11"
|
|
@@ -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@v6
|
|
57
57
|
with:
|
|
58
58
|
name: releases
|
|
59
59
|
path: dist
|
|
@@ -62,11 +62,11 @@ jobs:
|
|
|
62
62
|
needs: build-artifacts
|
|
63
63
|
runs-on: ubuntu-latest
|
|
64
64
|
steps:
|
|
65
|
-
- uses: actions/setup-python@
|
|
65
|
+
- uses: actions/setup-python@v6
|
|
66
66
|
name: Install Python
|
|
67
67
|
with:
|
|
68
68
|
python-version: "3.11"
|
|
69
|
-
- uses: actions/download-artifact@
|
|
69
|
+
- uses: actions/download-artifact@v7
|
|
70
70
|
with:
|
|
71
71
|
name: releases
|
|
72
72
|
path: dist
|
|
@@ -28,17 +28,17 @@ jobs:
|
|
|
28
28
|
matrix:
|
|
29
29
|
python-version: ["3.13"]
|
|
30
30
|
steps:
|
|
31
|
-
- uses: actions/checkout@
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
32
|
with:
|
|
33
33
|
fetch-depth: 0 # Fetch all history for all branches and tags.
|
|
34
34
|
|
|
35
35
|
- name: Set up Python
|
|
36
|
-
uses: actions/setup-python@
|
|
36
|
+
uses: actions/setup-python@v6
|
|
37
37
|
with:
|
|
38
38
|
python-version: ${{ matrix.python-version }}
|
|
39
39
|
|
|
40
40
|
- name: Install uv
|
|
41
|
-
uses: astral-sh/setup-uv@
|
|
41
|
+
uses: astral-sh/setup-uv@v7
|
|
42
42
|
|
|
43
43
|
- name: Install dependencies
|
|
44
44
|
run: |
|
|
@@ -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.14.10'
|
|
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:
|
|
@@ -41,7 +41,7 @@ repos:
|
|
|
41
41
|
additional_dependencies: [mdformat==0.7.17]
|
|
42
42
|
|
|
43
43
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
44
|
-
rev:
|
|
44
|
+
rev: v6.0.0
|
|
45
45
|
hooks:
|
|
46
46
|
- id: trailing-whitespace
|
|
47
47
|
- id: end-of-file-fixer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.11"
|
|
@@ -589,7 +589,83 @@ def _create_grid_mapping(
|
|
|
589
589
|
cf_name = var.attrs.get("grid_mapping_name", var_name)
|
|
590
590
|
|
|
591
591
|
# Create CRS from the grid mapping variable
|
|
592
|
-
|
|
592
|
+
if cf_name == "reduced_gaussian":
|
|
593
|
+
# pyproj does not recognize "reduced_gaussian" as a grid mapping name,
|
|
594
|
+
# but the grid uses geographic (lat/lon) coordinates on a sphere or
|
|
595
|
+
# spheroid. Build a geographic CRS from the earth shape parameters.
|
|
596
|
+
crs = pyproj.CRS.from_json_dict(
|
|
597
|
+
{
|
|
598
|
+
"$schema": "https://proj.org/schemas/v0.6/projjson.schema.json",
|
|
599
|
+
"type": "GeographicCRS",
|
|
600
|
+
"name": "Reduced Gaussian Grid",
|
|
601
|
+
"datum": {
|
|
602
|
+
"type": "GeodeticReferenceFrame",
|
|
603
|
+
"name": "Unknown",
|
|
604
|
+
"ellipsoid": {
|
|
605
|
+
"name": "Custom",
|
|
606
|
+
"semi_major_axis": var.attrs.get("semi_major_axis", 6371229.0),
|
|
607
|
+
"semi_minor_axis": var.attrs.get("semi_minor_axis", 6371229.0),
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
"coordinate_system": {
|
|
611
|
+
"subtype": "ellipsoidal",
|
|
612
|
+
"axis": [
|
|
613
|
+
{
|
|
614
|
+
"name": "Latitude",
|
|
615
|
+
"abbreviation": "lat",
|
|
616
|
+
"direction": "north",
|
|
617
|
+
"unit": "degree",
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"name": "Longitude",
|
|
621
|
+
"abbreviation": "lon",
|
|
622
|
+
"direction": "east",
|
|
623
|
+
"unit": "degree",
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
},
|
|
627
|
+
}
|
|
628
|
+
)
|
|
629
|
+
elif cf_name == "healpix":
|
|
630
|
+
# pyproj does not recognize "healpix" as a grid mapping name,
|
|
631
|
+
# but the grid uses geographic (lat/lon) coordinates on a sphere.
|
|
632
|
+
# Build a geographic CRS from the earth_radius parameter.
|
|
633
|
+
earth_radius = var.attrs.get("earth_radius", 6371229.0)
|
|
634
|
+
crs = pyproj.CRS.from_json_dict(
|
|
635
|
+
{
|
|
636
|
+
"$schema": "https://proj.org/schemas/v0.6/projjson.schema.json",
|
|
637
|
+
"type": "GeographicCRS",
|
|
638
|
+
"name": "HEALPix Grid",
|
|
639
|
+
"datum": {
|
|
640
|
+
"type": "GeodeticReferenceFrame",
|
|
641
|
+
"name": "Unknown",
|
|
642
|
+
"ellipsoid": {
|
|
643
|
+
"name": "Custom",
|
|
644
|
+
"semi_major_axis": earth_radius,
|
|
645
|
+
"semi_minor_axis": earth_radius,
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
"coordinate_system": {
|
|
649
|
+
"subtype": "ellipsoidal",
|
|
650
|
+
"axis": [
|
|
651
|
+
{
|
|
652
|
+
"name": "Latitude",
|
|
653
|
+
"abbreviation": "lat",
|
|
654
|
+
"direction": "north",
|
|
655
|
+
"unit": "degree",
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"name": "Longitude",
|
|
659
|
+
"abbreviation": "lon",
|
|
660
|
+
"direction": "east",
|
|
661
|
+
"unit": "degree",
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
}
|
|
666
|
+
)
|
|
667
|
+
else:
|
|
668
|
+
crs = pyproj.CRS.from_cf(var.attrs)
|
|
593
669
|
|
|
594
670
|
# Get associated coordinate variables, fallback to dimension names
|
|
595
671
|
coordinates: list[Hashable] = grid_mapping_dict.get(var_name, [])
|
|
@@ -600,16 +676,47 @@ def _create_grid_mapping(
|
|
|
600
676
|
# The appropriate values of the standard_name depend on the grid mapping and are given in Appendix F, Grid Mappings.
|
|
601
677
|
# """
|
|
602
678
|
if not coordinates and len(grid_mapping_dict) == 1:
|
|
603
|
-
if
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
679
|
+
if cf_name == "healpix":
|
|
680
|
+
# For HEALPix grids, the primary coordinate is the pixel index.
|
|
681
|
+
coords_found = apply_mapper(
|
|
682
|
+
_get_with_standard_name, ds, "healpix_index", error=False, default=[[]]
|
|
683
|
+
)
|
|
684
|
+
coordinates = list(itertools.chain(coords_found))
|
|
685
|
+
elif cf_name == "reduced_gaussian":
|
|
686
|
+
# For reduced gaussian grids, the primary coordinate is the grid
|
|
687
|
+
# point index. For compressed subsets, also look for the gather
|
|
688
|
+
# variable (with compress attribute).
|
|
689
|
+
idx_coords = apply_mapper(
|
|
690
|
+
_get_with_standard_name,
|
|
691
|
+
ds,
|
|
692
|
+
"reduced_gaussian_index",
|
|
693
|
+
error=False,
|
|
694
|
+
default=[[]],
|
|
695
|
+
)
|
|
696
|
+
coordinates = list(itertools.chain(idx_coords))
|
|
697
|
+
# Also find any compress/gather variable that references
|
|
698
|
+
# the detected index coordinate(s)
|
|
699
|
+
for vname in ds.coords:
|
|
700
|
+
if "compress" in ds[vname].attrs:
|
|
701
|
+
compress_target = ds[vname].attrs["compress"]
|
|
702
|
+
if any(c in compress_target for c in coordinates):
|
|
703
|
+
if vname not in coordinates:
|
|
704
|
+
coordinates.append(vname)
|
|
705
|
+
else:
|
|
706
|
+
if crs.to_cf().get("grid_mapping_name") == "rotated_latitude_longitude":
|
|
707
|
+
xname, yname = "grid_longitude", "grid_latitude"
|
|
708
|
+
elif crs.is_geographic:
|
|
709
|
+
xname, yname = "longitude", "latitude"
|
|
710
|
+
elif crs.is_projected:
|
|
711
|
+
xname, yname = "projection_x_coordinate", "projection_y_coordinate"
|
|
712
|
+
|
|
713
|
+
x = apply_mapper(
|
|
714
|
+
_get_with_standard_name, ds, xname, error=False, default=[[]]
|
|
715
|
+
)
|
|
716
|
+
y = apply_mapper(
|
|
717
|
+
_get_with_standard_name, ds, yname, error=False, default=[[]]
|
|
718
|
+
)
|
|
719
|
+
coordinates = list(itertools.chain(x, y))
|
|
613
720
|
|
|
614
721
|
return GridMapping(name=cf_name, crs=crs, array=da, coordinates=tuple(coordinates))
|
|
615
722
|
|
|
@@ -796,7 +903,6 @@ def _guess_bounds(da, dim=None, out_dim="bounds"):
|
|
|
796
903
|
f"If dim is None, variable {da.name} must be 1D or 2D. Received {da.ndim}D variable instead."
|
|
797
904
|
)
|
|
798
905
|
dim = da.dims
|
|
799
|
-
|
|
800
906
|
if not isinstance(dim, str):
|
|
801
907
|
if len(dim) > 2:
|
|
802
908
|
raise NotImplementedError(
|
|
@@ -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()
|
|
@@ -586,7 +586,11 @@ def points_to_cf(
|
|
|
586
586
|
coord = None
|
|
587
587
|
pts_ = pts
|
|
588
588
|
|
|
589
|
-
x
|
|
589
|
+
x: list[np.ndarray] = []
|
|
590
|
+
y: list[np.ndarray] = []
|
|
591
|
+
node_count: list[int] = []
|
|
592
|
+
crdX: list[float] = []
|
|
593
|
+
crdY: list[float] = []
|
|
590
594
|
for pt in pts_:
|
|
591
595
|
if isinstance(pt, MultiPoint):
|
|
592
596
|
xy = np.concatenate([p.coords for p in pt.geoms])
|
|
@@ -34,7 +34,7 @@ def _guess_bounds_1d(da, dim):
|
|
|
34
34
|
result = xr.concat([first, bounds], dim=dim).transpose(..., "bounds")
|
|
35
35
|
if ADDED_INDEX:
|
|
36
36
|
result = result.drop_vars(dim)
|
|
37
|
-
return result
|
|
37
|
+
return result.drop_attrs(deep=False)
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _guess_bounds_2d(da, dims):
|
|
@@ -338,6 +338,12 @@ def _get_ordered_vertices(
|
|
|
338
338
|
elif order == "descending":
|
|
339
339
|
endpoints = np.maximum(bounds[..., :, 0], bounds[..., :, 1])
|
|
340
340
|
last_endpoint = np.minimum(bounds[..., -1, 0], bounds[..., -1, 1])
|
|
341
|
+
else:
|
|
342
|
+
raise NotImplementedError(
|
|
343
|
+
f"Cannot determine vertices for non-monotonic bounds with {order} core "
|
|
344
|
+
"dimension orders. Try normalizing the coordinates to a monotonic "
|
|
345
|
+
"convention and try again."
|
|
346
|
+
)
|
|
341
347
|
|
|
342
348
|
vertices = np.concatenate(
|
|
343
349
|
[endpoints, np.expand_dims(last_endpoint, axis=-1)], axis=-1
|
|
@@ -832,6 +832,9 @@ def test_add_bounds(dims):
|
|
|
832
832
|
assert_allclose(
|
|
833
833
|
added[name].reset_coords(drop=True), expected[dim].transpose(..., "bounds")
|
|
834
834
|
)
|
|
835
|
+
if dim == "lat":
|
|
836
|
+
# The CF axes shouldn't have changed
|
|
837
|
+
assert added.cf.axes["Y"] == ["lat"]
|
|
835
838
|
|
|
836
839
|
_check_unchanged(original, ds)
|
|
837
840
|
|
|
@@ -1261,6 +1264,132 @@ def test_grid_mappings_coordinates_attribute():
|
|
|
1261
1264
|
)
|
|
1262
1265
|
|
|
1263
1266
|
|
|
1267
|
+
@requires_pyproj
|
|
1268
|
+
def test_reduced_gaussian_grid_mapping_global():
|
|
1269
|
+
"""Test GridMapping integration for a full reduced gaussian grid."""
|
|
1270
|
+
from ..datasets import reduced_gaussian_global_ds as ds
|
|
1271
|
+
|
|
1272
|
+
# Grid mapping discovery
|
|
1273
|
+
assert ds.cf.grid_mapping_names == {"reduced_gaussian": ["reduced_gaussian"]}
|
|
1274
|
+
|
|
1275
|
+
# CF indexing returns the grid mapping variable
|
|
1276
|
+
gm_var = ds.cf["reduced_gaussian"]
|
|
1277
|
+
assert gm_var.attrs["grid_mapping_name"] == "reduced_gaussian"
|
|
1278
|
+
assert gm_var.attrs["grid_subtype"] == "octahedral"
|
|
1279
|
+
|
|
1280
|
+
# Grid mapping propagation to data variables
|
|
1281
|
+
da = ds.cf["air_temperature"]
|
|
1282
|
+
assert "reduced_gaussian" in da.coords
|
|
1283
|
+
|
|
1284
|
+
# grid_mapping_name property
|
|
1285
|
+
assert da.cf.grid_mapping_name == "reduced_gaussian"
|
|
1286
|
+
|
|
1287
|
+
# .cf.grid_mappings property returns valid GridMapping
|
|
1288
|
+
gms = ds.cf.grid_mappings
|
|
1289
|
+
assert len(gms) == 1
|
|
1290
|
+
gm = gms[0]
|
|
1291
|
+
assert gm.name == "reduced_gaussian"
|
|
1292
|
+
assert gm.crs is not None
|
|
1293
|
+
assert gm.crs.is_geographic
|
|
1294
|
+
assert gm.array.name == "reduced_gaussian"
|
|
1295
|
+
assert gm.array.shape == () # scalar variable
|
|
1296
|
+
assert isinstance(gm.coordinates, tuple)
|
|
1297
|
+
# Should detect reduced_gaussian_index via standard_name
|
|
1298
|
+
assert "reduced_gaussian_index" in gm.coordinates
|
|
1299
|
+
|
|
1300
|
+
# DataArray grid_mappings should also work
|
|
1301
|
+
da_gms = da.cf.grid_mappings
|
|
1302
|
+
assert len(da_gms) == 1
|
|
1303
|
+
assert da_gms[0].name == "reduced_gaussian"
|
|
1304
|
+
assert da_gms[0].crs.is_geographic
|
|
1305
|
+
|
|
1306
|
+
# Repr should include reduced_gaussian
|
|
1307
|
+
assert "reduced_gaussian" in ds.cf.__repr__()
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
@requires_pyproj
|
|
1311
|
+
def test_reduced_gaussian_grid_mapping_land():
|
|
1312
|
+
"""Test GridMapping for a land-only subset using CF compress/gather."""
|
|
1313
|
+
from ..datasets import reduced_gaussian_land_ds as ds
|
|
1314
|
+
|
|
1315
|
+
# Grid mapping discovery
|
|
1316
|
+
assert ds.cf.grid_mapping_names == {"reduced_gaussian": ["reduced_gaussian"]}
|
|
1317
|
+
|
|
1318
|
+
# Grid mapping propagation
|
|
1319
|
+
da = ds.cf["air_temperature"]
|
|
1320
|
+
assert "reduced_gaussian" in da.coords
|
|
1321
|
+
|
|
1322
|
+
# .cf.grid_mappings property
|
|
1323
|
+
gms = ds.cf.grid_mappings
|
|
1324
|
+
assert len(gms) == 1
|
|
1325
|
+
gm = gms[0]
|
|
1326
|
+
assert gm.name == "reduced_gaussian"
|
|
1327
|
+
assert gm.crs.is_geographic
|
|
1328
|
+
assert gm.array.attrs["grid_subtype"] == "octahedral"
|
|
1329
|
+
|
|
1330
|
+
# Coordinates should include the compress/gather variable
|
|
1331
|
+
assert "grid_points" in gm.coordinates
|
|
1332
|
+
|
|
1333
|
+
# Verify the dataset structure: data on grid_points, with compress attribute
|
|
1334
|
+
assert "grid_points" in ds.dims
|
|
1335
|
+
assert "compress" in ds.grid_points.attrs
|
|
1336
|
+
assert ds.grid_points.attrs["compress"] == "reduced_gaussian_index"
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
@requires_pyproj
|
|
1340
|
+
def test_reduced_gaussian_grid_mapping_region():
|
|
1341
|
+
"""Test GridMapping for a regional subset using CF compress/gather."""
|
|
1342
|
+
from ..datasets import reduced_gaussian_region_ds as ds
|
|
1343
|
+
|
|
1344
|
+
# Grid mapping discovery
|
|
1345
|
+
assert ds.cf.grid_mapping_names == {"reduced_gaussian": ["reduced_gaussian"]}
|
|
1346
|
+
|
|
1347
|
+
# Grid mapping propagation
|
|
1348
|
+
da = ds.cf["air_temperature"]
|
|
1349
|
+
assert "reduced_gaussian" in da.coords
|
|
1350
|
+
|
|
1351
|
+
# .cf.grid_mappings property
|
|
1352
|
+
gms = ds.cf.grid_mappings
|
|
1353
|
+
assert len(gms) == 1
|
|
1354
|
+
gm = gms[0]
|
|
1355
|
+
assert gm.name == "reduced_gaussian"
|
|
1356
|
+
assert gm.crs.is_geographic
|
|
1357
|
+
|
|
1358
|
+
# Coordinates should include both reduced_gaussian_index and grid_points
|
|
1359
|
+
assert "reduced_gaussian_index" in gm.coordinates
|
|
1360
|
+
assert "grid_points" in gm.coordinates
|
|
1361
|
+
|
|
1362
|
+
# Verify compress structure
|
|
1363
|
+
assert "grid_points" in ds.dims
|
|
1364
|
+
assert ds.grid_points.attrs["compress"] == "reduced_gaussian_index"
|
|
1365
|
+
|
|
1366
|
+
# CRS earth parameters should match the grid mapping variable
|
|
1367
|
+
gm_attrs = ds.reduced_gaussian.attrs
|
|
1368
|
+
# The CRS should be built from semi_major/minor_axis
|
|
1369
|
+
assert gm.crs.ellipsoid is not None
|
|
1370
|
+
assert gm.crs.ellipsoid.semi_major_metre == gm_attrs["semi_major_axis"]
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
@requires_pyproj
|
|
1374
|
+
def test_reduced_gaussian_crs_properties():
|
|
1375
|
+
"""Test that the CRS built for reduced_gaussian has correct properties."""
|
|
1376
|
+
from ..datasets import reduced_gaussian_global_ds as ds
|
|
1377
|
+
|
|
1378
|
+
gm = ds.cf.grid_mappings[0]
|
|
1379
|
+
crs = gm.crs
|
|
1380
|
+
|
|
1381
|
+
# Must be geographic (lat/lon on a sphere)
|
|
1382
|
+
assert crs.is_geographic
|
|
1383
|
+
assert not crs.is_projected
|
|
1384
|
+
|
|
1385
|
+
# Earth shape parameters from the grid mapping variable
|
|
1386
|
+
assert crs.ellipsoid.semi_major_metre == 6371229.0
|
|
1387
|
+
assert crs.ellipsoid.semi_minor_metre == 6371229.0
|
|
1388
|
+
|
|
1389
|
+
# Should not have an EPSG code (custom sphere)
|
|
1390
|
+
assert crs.to_epsg() is None
|
|
1391
|
+
|
|
1392
|
+
|
|
1264
1393
|
@requires_pyproj
|
|
1265
1394
|
def test_bad_grid_mapping_attribute():
|
|
1266
1395
|
ds = rotds.copy(deep=False)
|
|
@@ -1282,6 +1411,71 @@ def test_bad_grid_mapping_attribute():
|
|
|
1282
1411
|
assert grid_mappings == () # No valid grid mappings since 'foo' doesn't exist
|
|
1283
1412
|
|
|
1284
1413
|
|
|
1414
|
+
@requires_pyproj
|
|
1415
|
+
def test_healpix_grid_mapping():
|
|
1416
|
+
"""Test GridMapping integration for HEALPix grids."""
|
|
1417
|
+
from ..datasets import healpix_ds as ds
|
|
1418
|
+
|
|
1419
|
+
# Grid mapping discovery
|
|
1420
|
+
assert ds.cf.grid_mapping_names == {"healpix": ["healpix"]}
|
|
1421
|
+
|
|
1422
|
+
# Grid mapping propagation to data variables
|
|
1423
|
+
da = ds.cf["air_temperature"]
|
|
1424
|
+
assert "healpix" in da.coords
|
|
1425
|
+
|
|
1426
|
+
# grid_mapping_name property
|
|
1427
|
+
assert da.cf.grid_mapping_name == "healpix"
|
|
1428
|
+
|
|
1429
|
+
# .cf.grid_mappings property on Dataset
|
|
1430
|
+
gms = ds.cf.grid_mappings
|
|
1431
|
+
assert len(gms) == 1
|
|
1432
|
+
gm = gms[0]
|
|
1433
|
+
assert gm.name == "healpix"
|
|
1434
|
+
assert gm.crs is not None
|
|
1435
|
+
assert gm.crs.is_geographic
|
|
1436
|
+
assert gm.array.name == "healpix"
|
|
1437
|
+
assert gm.array.shape == () # scalar variable
|
|
1438
|
+
assert isinstance(gm.coordinates, tuple)
|
|
1439
|
+
# Should detect healpix_index via standard_name
|
|
1440
|
+
assert "healpix_index" in gm.coordinates
|
|
1441
|
+
|
|
1442
|
+
# DataArray grid_mappings should also work
|
|
1443
|
+
da_gms = da.cf.grid_mappings
|
|
1444
|
+
assert len(da_gms) == 1
|
|
1445
|
+
assert da_gms[0].name == "healpix"
|
|
1446
|
+
assert da_gms[0].crs.is_geographic
|
|
1447
|
+
|
|
1448
|
+
# Repr should include healpix
|
|
1449
|
+
assert "healpix" in ds.cf.__repr__()
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
@requires_pyproj
|
|
1453
|
+
def test_healpix_crs_properties():
|
|
1454
|
+
"""Test that the CRS built for HEALPix has correct properties."""
|
|
1455
|
+
from ..datasets import healpix_ds as ds
|
|
1456
|
+
|
|
1457
|
+
gm = ds.cf.grid_mappings[0]
|
|
1458
|
+
crs = gm.crs
|
|
1459
|
+
|
|
1460
|
+
# Must be geographic (lat/lon on a sphere)
|
|
1461
|
+
assert crs.is_geographic
|
|
1462
|
+
assert not crs.is_projected
|
|
1463
|
+
|
|
1464
|
+
# Earth shape parameters from the grid mapping variable
|
|
1465
|
+
assert crs.ellipsoid.semi_major_metre == 6371000
|
|
1466
|
+
assert crs.ellipsoid.semi_minor_metre == 6371000
|
|
1467
|
+
|
|
1468
|
+
# Should not have an EPSG code (custom sphere)
|
|
1469
|
+
assert crs.to_epsg() is None
|
|
1470
|
+
|
|
1471
|
+
# Verify HEALPix-specific attributes are preserved in the grid mapping array
|
|
1472
|
+
gm_attrs = gm.array.attrs
|
|
1473
|
+
assert gm_attrs["grid_mapping_name"] == "healpix"
|
|
1474
|
+
assert gm_attrs["refinement_level"] == 1
|
|
1475
|
+
assert gm_attrs["indexing_scheme"] == "nested"
|
|
1476
|
+
assert gm_attrs["earth_radius"] == 6371000
|
|
1477
|
+
|
|
1478
|
+
|
|
1285
1479
|
def test_docstring() -> None:
|
|
1286
1480
|
assert "One of ('X'" in airds.cf.groupby.__doc__
|
|
1287
1481
|
assert "Time variable accessor e.g. 'T.month'" in airds.cf.groupby.__doc__
|
|
@@ -60,7 +60,7 @@ def short_formatter(unit, registry, **options):
|
|
|
60
60
|
# Reused with modification from MetPy under the terms of the BSD 3-Clause License.
|
|
61
61
|
# Copyright (c) 2015,2017,2019 MetPy Developers.
|
|
62
62
|
# Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs
|
|
63
|
-
units = pint.UnitRegistry(
|
|
63
|
+
units: pint.UnitRegistry = pint.UnitRegistry(
|
|
64
64
|
autoconvert_offset_to_baseunit=True,
|
|
65
65
|
preprocessors=[
|
|
66
66
|
functools.partial(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.10.9"
|
|
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
|