Skip to content

Commit 49e0e72

Browse files
authored
FIX: properly read CfRadial1 n_points files (#190)
* FIX: properly read CfRadial1 n_points files * add history.md entry * only strip dimensions which are available * restructure coordinate assignments * add test, bump open-radar-data
1 parent 2e8d6d3 commit 49e0e72

File tree

7 files changed

+97
-19
lines changed

7 files changed

+97
-19
lines changed

ci/notebooktests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies:
1616
- netCDF4
1717
- notebook
1818
- numpy
19-
- open-radar-data>=0.1.0
19+
- open-radar-data>=0.3.0
2020
- pip
2121
- pyproj
2222
- pytest

ci/unittests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies:
1313
- lat_lon_parser
1414
- netCDF4
1515
- numpy
16-
- open-radar-data>=0.1.0
16+
- open-radar-data>=0.3.0
1717
- pip
1818
- pyproj
1919
- pytest

docs/history.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Development Version
44

55
* MNT: minimize CI ({pull}`192`) by [@kmuehlbauer](https://github.com/kmuehlbauer).
6+
* FIX: properly read CfRadial1 n_points files ({issue}`188`) by [@aladinor](https://github.com/aladinor), ({pull}`190`) by [@kmuehlbauer](https://github.com/kmuehlbauer).
67

78
## 0.6.0 (2024-08-05)
89

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ def cfradial1_file(tmp_path_factory):
1515
return DATASETS.fetch("cfrad.20080604_002217_000_SPOL_v36_SUR.nc")
1616

1717

18+
@pytest.fixture(scope="session")
19+
def cfradial1n_file(tmp_path_factory):
20+
return DATASETS.fetch("DES_VOL_RAW_20240522_1600.nc")
21+
22+
1823
@pytest.fixture(scope="session")
1924
def odim_file():
2025
return DATASETS.fetch("71_20181220_060628.pvol.h5")

tests/io/test_io.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,59 @@ def test_cfradfial2_roundtrip(cfradial1_file, first_dim):
911911
xr.testing.assert_equal(dtree1[d1].ds, dtree2[d2].ds)
912912

913913

914+
def test_cfradial_n_points_file(cfradial1n_file):
915+
dtree = open_cfradial1_datatree(
916+
cfradial1n_file, first_dim="auto", site_coords=False
917+
)
918+
attrs = dtree.attrs
919+
920+
# root_attrs
921+
assert attrs["Conventions"] == "CF-1.7"
922+
assert attrs["version"] == "CF-Radial-1.4"
923+
assert attrs["title"] == "VOL_A"
924+
assert attrs["instrument_name"] == "Desio_Radar"
925+
assert attrs["platform_is_mobile"] == "false"
926+
927+
# root vars
928+
rvars = dtree.data_vars
929+
assert rvars["volume_number"] == 1
930+
assert rvars["platform_type"] == b"fixed"
931+
assert rvars["instrument_type"] == b"radar"
932+
assert rvars["time_coverage_start"] == b"2024-05-22T16:00:47Z"
933+
assert rvars["time_coverage_end"] == b"2024-05-22T16:03:20Z"
934+
np.testing.assert_almost_equal(rvars["latitude"].values, np.array(45.6272661))
935+
np.testing.assert_almost_equal(rvars["longitude"].values, np.array(9.1963181))
936+
np.testing.assert_almost_equal(rvars["altitude"].values, np.array(241.0))
937+
938+
# iterate over subgroups and check some values
939+
moments = ["ZDR", "RHOHV", "KDP", "DBZ", "VEL", "PHIDP"]
940+
elevations = [0.7, 1.3, 3.0, 5.0, 7.0, 10.0, 15.0, 25.0]
941+
azimuths = [360] * 8
942+
ranges = [416] * 5 + [383, 257, 157]
943+
for grp in dtree.groups:
944+
# only iterate sweep groups
945+
if "sweep" not in grp:
946+
continue
947+
ds = dtree[grp].ds
948+
i = int(ds.sweep_number.values)
949+
assert i == int(grp[7:])
950+
assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]}
951+
assert set(ds.data_vars) & (
952+
sweep_dataset_vars | non_standard_sweep_dataset_vars
953+
) == set(moments)
954+
assert set(ds.data_vars) & (required_sweep_metadata_vars) == set(
955+
required_sweep_metadata_vars ^ {"azimuth", "elevation"}
956+
)
957+
assert set(ds.coords) == {
958+
"azimuth",
959+
"elevation",
960+
"time",
961+
"range",
962+
}
963+
assert np.round(ds.sweep_fixed_angle.values.item(), 1) == elevations[i]
964+
assert ds.sweep_mode == "azimuth_surveillance"
965+
966+
914967
@pytest.mark.parametrize("sweep", ["sweep_0", 0, [0, 1], ["sweep_0", "sweep_1"]])
915968
@pytest.mark.parametrize(
916969
"nexradlevel2_files", ["nexradlevel2_gzfile", "nexradlevel2_bzfile"], indirect=True

xradar/io/backends/cfradial1.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
import numpy as np
3636
from datatree import DataTree
37-
from xarray import open_dataset
37+
from xarray import merge, open_dataset
3838
from xarray.backends import NetCDF4DataStore
3939
from xarray.backends.common import BackendEntrypoint
4040
from xarray.backends.store import StoreBackendEntrypoint
@@ -128,13 +128,20 @@ def _get_sweep_groups(
128128
ray_start_index = root.get("ray_start_index", False)
129129

130130
# strip variables and attributes
131-
anc_dims = list(set(root.dims) ^ {"time", "range", "sweep"})
131+
anc_dims = set(root.dims) ^ {"time", "range", "sweep", "n_points"}
132+
anc_dims &= set(root.dims)
133+
132134
root = root.drop_dims(anc_dims)
133135

134136
root = root.rename({"fixed_angle": "sweep_fixed_angle"})
135137

136138
# conform to cfradial2 standard
137139
data = conform_cfradial2_sweep_group(root, optional, "time")
140+
data_vars = {
141+
k
142+
for k, v in data.data_vars.items()
143+
if any(d in v.dims for d in ["range", "n_points"])
144+
}
138145

139146
# which sweeps to load
140147
# sweep is assumed a list of strings with elements like "sweep_0"
@@ -172,23 +179,16 @@ def _get_sweep_groups(
172179
rslice = slice(0, current_ray_n_gates[0].values.astype(int))
173180
ds = ds.isel(range=rslice)
174181
ds = ds.isel(n_points=nslice)
175-
ds = ds.stack(n_points=[dim0, "range"])
176-
ds = ds.unstack("n_points")
177-
# fix elevation/time additional range dimension in coordinate
178-
ds = ds.assign_coords({"elevation": ds.elevation.isel(range=0, drop=True)})
179-
180-
# handling first dimension
181-
# for CfRadial1 first dimension is time
182-
if first_dim == "auto":
183-
ds = ds.swap_dims({"time": dim0})
184-
ds = ds.sortby(dim0)
185-
186-
# reassign azimuth/elevation coordinates
187-
ds = ds.assign_coords({"azimuth": ds.azimuth})
188-
ds = ds.assign_coords({"elevation": ds.elevation})
182+
ds_vars = ds[data_vars]
183+
ds_vars = merge([ds_vars, ds[[dim0, "range"]]])
184+
ds_vars = ds_vars.stack(n_points=[dim0, "range"])
185+
ds_vars = ds_vars.unstack("n_points")
186+
ds = ds.drop_vars(ds_vars.data_vars)
187+
ds = merge([ds, ds_vars])
189188

190189
# assign site_coords
191190
if site_coords:
191+
192192
ds = ds.assign_coords(
193193
{
194194
"latitude": root.latitude,
@@ -197,6 +197,20 @@ def _get_sweep_groups(
197197
}
198198
)
199199

200+
# handling first dimension
201+
# for CfRadial1 first dimension is time
202+
if first_dim == "auto":
203+
if "time" in ds.dims:
204+
ds = ds.swap_dims({"time": dim0})
205+
ds = ds.sortby(dim0)
206+
else:
207+
if "time" not in ds.dims:
208+
ds = ds.swap_dims({dim0: "time"})
209+
ds = ds.sortby("time")
210+
211+
# reassign azimuth/elevation coordinates
212+
ds = ds.set_coords(["azimuth", "elevation"])
213+
200214
sweep_groups[sw] = ds
201215

202216
return sweep_groups

xradar/model.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,12 @@ def determine_cfradial2_sweep_variables(obj, optional, dim0):
995995
keep_vars |= required_sweep_metadata_vars
996996
# all moment fields
997997
# todo: strip off non-conforming
998-
keep_vars |= {k for k, v in obj.data_vars.items() if "range" in v.dims}
998+
# this also handles cfradial1 n_points layout
999+
keep_vars |= {
1000+
k
1001+
for k, v in obj.data_vars.items()
1002+
if any(d in v.dims for d in ["range", "n_points"])
1003+
}
9991004
# optional variables
10001005
if optional:
10011006
keep_vars |= {k for k, v in obj.data_vars.items() if dim0 in v.dims}

0 commit comments

Comments
 (0)