Skip to content

Commit c7db730

Browse files
committed
Deprecate Hazard.from_raster_xarray_file
* Make `Hazard.from_raster_xarray` handle files. * Update tests * Fix linter issues
1 parent fdf9853 commit c7db730

File tree

3 files changed

+238
-220
lines changed

3 files changed

+238
-220
lines changed

climada/hazard/io.py

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import pandas as pd
3333
import rasterio
3434
import xarray as xr
35+
from deprecation import deprecated
3536
from scipy import sparse
3637

3738
import climada.util.constants as u_const
@@ -274,6 +275,10 @@ def from_raster(
274275
)
275276

276277
@classmethod
278+
@deprecated(
279+
details="Hazard.from_xarray_raster now supports a filepath for the 'data' "
280+
"parameter"
281+
)
277282
def from_xarray_raster_file(
278283
cls, filepath: Union[pathlib.Path, str], *args, **kwargs
279284
):
@@ -301,26 +306,14 @@ def from_xarray_raster_file(
301306
Examples
302307
--------
303308
304-
>>> hazard = Hazard.from_xarray_raster_file("path/to/file.nc", "", "")
305-
306-
Notes
307-
-----
308-
309-
If you have specific requirements for opening a data file, prefer opening it
310-
yourself and using :py:meth:`~Hazard.from_xarray_raster`, following this pattern:
311-
312-
>>> open_kwargs = dict(engine="h5netcdf", chunks=dict(x=-1, y="auto"))
313-
>>> with xarray.open_dataset("path/to/file.nc", **open_kwargs) as dset:
314-
... hazard = Hazard.from_xarray_raster(dset, "", "")
315309
"""
316-
reader = HazardXarrayReader.from_file(filepath, *args, **kwargs)
317-
kwargs = reader.get_hazard_kwargs()
318-
return cls(**cls._check_and_cast_attrs(kwargs))
310+
args = (filepath,) + args
311+
return cls.from_xarray_raster(*args, **kwargs)
319312

320313
@classmethod
321314
def from_xarray_raster(
322315
cls,
323-
data: xr.Dataset,
316+
data: xr.Dataset | pathlib.Path | str,
324317
hazard_type: str,
325318
intensity_unit: str,
326319
*,
@@ -329,6 +322,7 @@ def from_xarray_raster(
329322
data_vars: Optional[Dict[str, str]] = None,
330323
crs: str = u_const.DEF_CRS,
331324
rechunk: bool = False,
325+
open_dataset_kws: dict[str, Any] | None = None,
332326
):
333327
"""Read raster-like data from an xarray Dataset
334328
@@ -352,13 +346,10 @@ def from_xarray_raster(
352346
meaning that the object can be used in all CLIMADA operations without throwing
353347
an error due to missing data or faulty data types.
354348
355-
Use :py:meth:`~Hazard.from_xarray_raster_file` to open a file on disk
356-
and load the resulting dataset with this method in one step.
357-
358349
Parameters
359350
----------
360-
data : xarray.Dataset
361-
The dataset to read from.
351+
data : xarray.Dataset or Path or str
352+
The filepath to read the data from or the already opened dataset
362353
hazard_type : str
363354
The type identifier of the hazard. Will be stored directly in the hazard
364355
object.
@@ -406,6 +397,9 @@ def from_xarray_raster(
406397
be forced by rechunking the data. Ideally, you would select the chunks in
407398
that manner when opening the dataset before passing it to this function.
408399
Defaults to ``False``.
400+
open_dataset_kws : dict(str, any)
401+
Keyword arguments passed to ``xarray.open_dataset`` if ``data`` is a file
402+
path. Ignored otherwise.
409403
410404
Returns
411405
-------
@@ -414,8 +408,8 @@ def from_xarray_raster(
414408
415409
See Also
416410
--------
417-
:py:meth:`~Hazard.from_xarray_raster_file`
418-
Use this method if you want CLIMADA to open and read a file on disk for you.
411+
:py:class:`HazardXarrayReader`
412+
The helper class used to read the data.
419413
420414
Notes
421415
-----
@@ -457,6 +451,21 @@ def from_xarray_raster(
457451
... )
458452
>>> hazard = Hazard.from_xarray_raster(dset, "", "")
459453
454+
Data can also be read from a file.
455+
456+
>>> dset.to_netcdf("path/to/file.nc")
457+
>>> hazard = Hazard.from_xarray_raster("path/to/file.nc", "", "")
458+
459+
If you have specific requirements for opening a data file, you can pass
460+
``open_dataset_kws``.
461+
462+
>>> hazard = Hazard.from_xarray_raster(
463+
... dset,
464+
... "",
465+
... "",
466+
... open_dataset_kws={"chunks": {"x": -1, "y": "auto"}, "engine": "netcdf4"}
467+
... )
468+
460469
For non-default coordinate names, use the ``coordinate_vars`` argument.
461470
462471
>>> dset = xr.Dataset(
@@ -568,26 +577,29 @@ def from_xarray_raster(
568577
>>> dset = dset.expand_dims(time=[numpy.datetime64("2000-01-01")])
569578
>>> hazard = Hazard.from_xarray_raster(dset, "", "")
570579
"""
580+
reader_kwargs = {
581+
"intensity": intensity,
582+
"coordinate_vars": coordinate_vars,
583+
"data_vars": data_vars,
584+
"crs": crs,
585+
"rechunk": rechunk,
586+
}
571587
if not isinstance(data, xr.Dataset):
572-
if isinstance(data, (pathlib.Path, str)):
588+
if isinstance(data, xr.DataArray):
573589
raise TypeError(
574-
"Passing a path to this classmethod is not supported. "
575-
"Use Hazard.from_xarray_raster_file instead."
590+
"This method only supports passing xr.Dataset. Consider promoting "
591+
"your xr.DataArray to a Dataset."
576592
)
593+
reader = HazardXarrayReader.from_file(
594+
filename=data, open_dataset_kws=open_dataset_kws, **reader_kwargs
595+
)
596+
else:
597+
reader = HazardXarrayReader(data=data, **reader_kwargs)
577598

578-
raise TypeError("This method only supports xarray.Dataset as input data")
579-
580-
reader = HazardXarrayReader(
581-
data=data,
582-
hazard_type=hazard_type,
583-
intensity_unit=intensity_unit,
584-
intensity=intensity,
585-
coordinate_vars=coordinate_vars,
586-
data_vars=data_vars,
587-
crs=crs,
588-
rechunk=rechunk,
589-
)
590-
kwargs = reader.get_hazard_kwargs()
599+
kwargs = reader.get_hazard_kwargs() | {
600+
"haz_type": hazard_type,
601+
"units": intensity_unit,
602+
}
591603
return cls(**cls._check_and_cast_attrs(kwargs))
592604

593605
@staticmethod

climada/hazard/test/test_xarray.py

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ def setUpClass(cls):
4646
cls.time = np.array([dt.datetime(1999, 1, 1), dt.datetime(2000, 1, 1)])
4747
cls.latitude = np.array([0, 1])
4848
cls.longitude = np.array([0, 1, 2])
49-
dset = xr.Dataset(
49+
cls.dset = xr.Dataset(
5050
{
5151
"intensity": (["time", "latitude", "longitude"], cls.intensity),
5252
},
5353
dict(time=cls.time, latitude=cls.latitude, longitude=cls.longitude),
5454
)
55-
dset.to_netcdf(cls.netcdf_path)
55+
cls.dset.to_netcdf(cls.netcdf_path)
5656

5757
@classmethod
5858
def tearDownClass(cls):
@@ -108,49 +108,49 @@ def _assert_default_types(self, hazard):
108108

109109
def test_load_path(self):
110110
"""Load the data with path as argument"""
111+
hazard = Hazard.from_xarray_raster(self.netcdf_path, "", "")
112+
self._assert_default(hazard)
113+
114+
# Check deprecated method
111115
hazard = Hazard.from_xarray_raster_file(self.netcdf_path, "", "")
112116
self._assert_default(hazard)
113117

114118
# Check wrong paths
115-
with self.assertRaises(FileNotFoundError) as cm:
116-
Hazard.from_xarray_raster_file("file-does-not-exist.nc", "", "")
117-
self.assertIn("file-does-not-exist.nc", str(cm.exception))
118-
with self.assertRaises(KeyError) as cm:
119-
Hazard.from_xarray_raster_file(
119+
with self.assertRaisesRegex(FileNotFoundError, "file-does-not-exist.nc"):
120+
Hazard.from_xarray_raster("file-does-not-exist.nc", "", "")
121+
with self.assertRaisesRegex(KeyError, "wrong-intensity-path"):
122+
Hazard.from_xarray_raster(
120123
self.netcdf_path, "", "", intensity="wrong-intensity-path"
121124
)
122-
self.assertIn("wrong-intensity-path", str(cm.exception))
123125

124-
def test_load_dataset(self):
126+
@patch("climada.hazard.xarray.xr.open_dataset")
127+
def test_load_dataset(self, open_dataset_mock):
125128
"""Load the data from an opened dataset as argument"""
129+
open_dataset_mock.return_value.__enter__.return_value = self.dset
126130

127-
def _load_and_assert(chunks):
128-
with xr.open_dataset(self.netcdf_path, chunks=chunks) as dataset:
129-
hazard = Hazard.from_xarray_raster(dataset, "", "")
130-
self._assert_default(hazard)
131+
def _load_and_assert(**kwargs):
132+
hazard = Hazard.from_xarray_raster(
133+
self.netcdf_path, "", "", open_dataset_kws=kwargs
134+
)
135+
self._assert_default(hazard)
131136

132-
_load_and_assert(chunks=None)
133-
_load_and_assert(chunks=dict(latitude=1, longitude=1, time=1))
137+
_load_and_assert()
138+
open_dataset_mock.assert_called_once_with(self.netcdf_path, chunks="auto")
139+
open_dataset_mock.reset_mock()
140+
_load_and_assert(chunks=dict(latitude=1, longitude=1, time=1), engine="netcdf4")
141+
open_dataset_mock.assert_called_once_with(
142+
self.netcdf_path,
143+
chunks=dict(latitude=1, longitude=1, time=1),
144+
engine="netcdf4",
145+
)
134146

135147
def test_type_error(self):
136148
"""Calling 'from_xarray_raster' with wrong data type should throw"""
137-
# Passing a path
138-
with self.assertRaises(TypeError) as cm:
139-
Hazard.from_xarray_raster(self.netcdf_path, "", "")
140-
self.assertIn(
141-
"Use Hazard.from_xarray_raster_file instead",
142-
str(cm.exception),
143-
)
144-
145149
# Passing a DataArray
146-
with xr.open_dataset(self.netcdf_path) as dset, self.assertRaises(
147-
TypeError
148-
) as cm:
150+
with xr.open_dataset(self.netcdf_path) as dset, self.assertRaisesRegex(
151+
TypeError, "This method only supports passing xr.Dataset"
152+
):
149153
Hazard.from_xarray_raster(dset["intensity"], "", "")
150-
self.assertIn(
151-
"This method only supports xarray.Dataset as input data",
152-
str(cm.exception),
153-
)
154154

155155
def test_type_and_unit(self):
156156
"""Test passing a custom type and unit"""
@@ -352,9 +352,7 @@ def test_crs(self):
352352

353353
def test_crs_from_input(crs_input):
354354
crs = CRS.from_user_input(crs_input)
355-
hazard = Hazard.from_xarray_raster_file(
356-
self.netcdf_path, "", "", crs=crs_input
357-
)
355+
hazard = Hazard.from_xarray_raster(self.netcdf_path, "", "", crs=crs_input)
358356
self.assertEqual(hazard.centroids.geometry.crs, crs)
359357

360358
test_crs_from_input("EPSG:3857")
@@ -366,6 +364,7 @@ def test_missing_dims(self):
366364
# Drop time as dimension, but not as coordinate!
367365
with xr.open_dataset(self.netcdf_path) as ds:
368366
ds = ds.isel(time=0).squeeze()
367+
print(ds)
369368
hazard = Hazard.from_xarray_raster(ds, "", "")
370369
self._assert_default_types(hazard)
371370
np.testing.assert_array_equal(
@@ -447,7 +446,7 @@ def _assert_intensity_fraction(self, hazard):
447446

448447
def test_dimension_naming(self):
449448
"""Test if dimensions with different names can be read"""
450-
hazard = Hazard.from_xarray_raster_file(
449+
hazard = Hazard.from_xarray_raster(
451450
self.netcdf_path,
452451
"",
453452
"",
@@ -462,7 +461,7 @@ def test_dimension_naming(self):
462461

463462
def test_coordinate_naming(self):
464463
"""Test if coordinates with different names than dimensions can be read"""
465-
hazard = Hazard.from_xarray_raster_file(
464+
hazard = Hazard.from_xarray_raster(
466465
self.netcdf_path,
467466
"",
468467
"",
@@ -477,7 +476,7 @@ def test_coordinate_naming(self):
477476

478477
def test_2D_coordinates(self):
479478
"""Test if read method correctly handles 2D coordinates"""
480-
hazard = Hazard.from_xarray_raster_file(
479+
hazard = Hazard.from_xarray_raster(
481480
self.netcdf_path,
482481
"",
483482
"",
@@ -579,18 +578,17 @@ def test_2D_time(self):
579578
def test_errors(self):
580579
"""Check if expected errors are thrown"""
581580
# Wrong coordinate key
582-
with self.assertRaises(ValueError) as cm:
583-
Hazard.from_xarray_raster_file(
581+
with self.assertRaisesRegex(ValueError, "Unknown coordinates passed"):
582+
Hazard.from_xarray_raster(
584583
self.netcdf_path,
585584
"",
586585
"",
587586
coordinate_vars=dict(bar="latitude", longitude="baz"),
588587
)
589-
self.assertIn("Unknown coordinates passed: '['bar']'.", str(cm.exception))
590588

591589
# Correctly specified, but the custom dimension does not exist
592590
with self.assertRaisesRegex(RuntimeError, "lalalatitude"):
593-
Hazard.from_xarray_raster_file(
591+
Hazard.from_xarray_raster(
594592
self.netcdf_path,
595593
"",
596594
"",

0 commit comments

Comments
 (0)