1
- import glob
2
1
import logging
3
2
import os
4
- from pathlib import Path
5
- from typing import Any , Dict , Iterable , List , Optional
3
+ from pathlib import Path , PurePath
4
+ from typing import Iterable , Optional
6
5
import warnings
7
6
import zipfile
8
7
9
- from dask .distributed import Client
10
- from fsspec .implementations .local import LocalFileSystem
11
8
from fsspec .spec import AbstractFileSystem
12
9
import numpy as np
13
10
import xarray as xr
14
11
15
- # import cartopy.crs as ccrs
16
- # import matplotlib.pyplot as plt
17
- import glob
18
12
from scipy .optimize import curve_fit
19
13
20
- from hazard .indicator_model import IndicatorModel
21
14
from hazard .inventory import Colormap , HazardResource , MapInfo , Scenario
22
- from hazard .protocols import OpenDataset , ReadWriteDataArray
15
+ from hazard .onboarder import Onboarder
16
+ from hazard .protocols import WriteDataArray
23
17
from hazard .sources .osc_zarr import OscZarr
24
18
from hazard .utilities .tiles import create_tiles_for_resource
25
19
from hazard .utilities .xarray_utilities import data_array
26
20
27
21
logger = logging .getLogger (__name__ )
28
22
29
23
30
- class WISCWinterStormEventSource (OpenDataset ):
31
- def __init__ (self , source_dir : str , fs : Optional [AbstractFileSystem ] = None ):
32
- """Source that can create WISC return period wind speed maps from the event set:
24
+ class WISCEuropeanWinterStorm (Onboarder ):
25
+ def __init__ (
26
+ self ,
27
+ source_dir_base : PurePath = PurePath (),
28
+ fs : Optional [AbstractFileSystem ] = None ,
29
+ ):
30
+ super ().__init__ (source_dir_base , fs )
31
+ """
32
+ Peak 3s gust wind speed for different return periods inferred from the WISC event set.
33
+
34
+ METADATA:
35
+ Link: https://cds.climate.copernicus.eu/datasets/sis-european-wind-storm-synthetic-events?tab=overview
36
+ Data type: Synthetic European winter storm events.
37
+ Hazard indicator: Wind
38
+ Region: Europe
39
+ Resolution: 2.4 arcmin
40
+ Scenarios: Historical
41
+ Time range: Not applicable
42
+ File type: NetCDF
43
+
44
+ DATA DESCRIPTION:
45
+ The WISC dataset contains a set of synthetic windstorm events consisting of 22,980 individual
46
+ storm footprints over Europe. These are a physically realistic set of plausible windstorm
47
+ events based on the modelled climatic conditions, calculated using the Met Office HadGEM3 model
48
+ (Global Atmosphere 3 and Global Land 3 configurations).
49
+ Return period maps of peak gust wind speed are inferred from this data.
50
+
51
+ Special thanks to Annabel Hall; her work on investigating the fitting of the WISC data set is adapted hereunder.
33
52
https://cds.climate.copernicus.eu/datasets/sis-european-wind-storm-synthetic-events?tab=overview
34
53
https://cds.climate.copernicus.eu/how-to-api
35
54
@@ -39,8 +58,6 @@ def __init__(self, source_dir: str, fs: Optional[AbstractFileSystem] = None):
39
58
fs (Optional[AbstractFileSystem], optional): AbstractFileSystem instance.
40
59
If None, a LocalFileSystem is used.
41
60
"""
42
- self .fs = fs if fs else LocalFileSystem ()
43
- self .source_dir = source_dir
44
61
self .years = [
45
62
1986 ,
46
63
1987 ,
@@ -72,10 +89,41 @@ def __init__(self, source_dir: str, fs: Optional[AbstractFileSystem] = None):
72
89
# synth_sets = [1.2, 2.0, 3.0]
73
90
self .synth_set = 1.2
74
91
75
- def prepare_source_files (self , working_dir : Path ):
76
- self ._download_all (working_dir )
92
+ def prepare (self , working_dir : Path , force_download : bool = False ):
93
+ # self._download_all(working_dir)
77
94
self ._extract_all (working_dir )
78
95
96
+ def onboard (self , target : WriteDataArray ):
97
+ logger .info ("Creating data set from events" )
98
+ resource = self ._resource ()
99
+ for scenario in resource .scenarios :
100
+ for year in scenario .years :
101
+ ds = self ._peak_annual_gust_speed_fit ()
102
+ # note that the co-ordinates will be written into the parent of resource.path
103
+ target .write (
104
+ resource .path .format (scenario = scenario .id , year = year ),
105
+ ds ["wind_speed" ].compute (),
106
+ spatial_coords = resource .store_netcdf_coords ,
107
+ )
108
+
109
+ def create_maps (self , source : OscZarr , target : OscZarr ):
110
+ """Create map images."""
111
+ for resource in self .inventory ():
112
+ create_tiles_for_resource (
113
+ source ,
114
+ target ,
115
+ resource ,
116
+ nodata_as_zero = True ,
117
+ nodata_as_zero_coarsening = True ,
118
+ )
119
+
120
+ def inventory (self ) -> Iterable [HazardResource ]:
121
+ """Get the inventory item(s)."""
122
+ return [self ._resource ()]
123
+
124
+ def source_dir_from_base (self , source_dir_base ):
125
+ return source_dir_base / "wisc" / "v1"
126
+
79
127
def _download_all (self , working_dir : Path ):
80
128
try :
81
129
import cdsapi
@@ -109,11 +157,17 @@ def _extract_all(self, working_dir: Path):
109
157
with zipfile .ZipFile (
110
158
str (working_dir / (set_name + ".zip" )), "r"
111
159
) as zip_ref :
112
- zip_ref .extractall (
113
- str (working_dir / str (self .synth_set ).replace ("." , "_" ) / str (year ))
160
+ synth_set_year = PurePath (
161
+ str (self .synth_set ).replace ("." , "_" ), str (year )
162
+ )
163
+ zip_ref .extractall (working_dir / synth_set_year )
164
+ self .fs .upload (
165
+ str (working_dir / synth_set_year ),
166
+ str (self .source_dir / synth_set_year ),
167
+ recursive = True ,
114
168
)
115
169
116
- def occurrence_exceedance_count (self ):
170
+ def _occurrence_exceedance_count (self ):
117
171
"""Calculate occurrence exceedance count (i.e. number of events). Only used for diagnostic purposes.
118
172
119
173
Returns:
@@ -155,7 +209,7 @@ def occurrence_exceedance_count(self):
155
209
exceedance_count [i , :, :] += count
156
210
return exceedance_count
157
211
158
- def peak_annual_gust_speed (self ):
212
+ def _peak_annual_gust_speed (self ):
159
213
first_file = self ._file_list (self .years [0 ])[0 ]
160
214
all_files = [f for y in self .years for f in self ._file_list (y )]
161
215
first = xr .open_dataset (first_file )
@@ -203,7 +257,7 @@ def _gev_icdf(self, p, mu, xi, sigma):
203
257
x_p = mu - (sigma / xi ) * (1 - (- np .log (1 - p )) ** (- xi ))
204
258
return x_p
205
259
206
- def fit_gumbel (self , annual_max_gust_speed : xr .DataArray ):
260
+ def _fit_gumbel (self , annual_max_gust_speed : xr .DataArray ):
207
261
"""See for example "A review of methods to calculate extreme wind speeds", Palutikof et al. (1999).
208
262
https://rmets.onlinelibrary.wiley.com/doi/pdf/10.1017/S1350482799001103
209
263
we fit to a Type I extreme distribution, where $X_T$ is the 1-in-T year 3s peak gust wind speed:
@@ -260,7 +314,7 @@ def fit_gumbel(self, annual_max_gust_speed: xr.DataArray):
260
314
fitted_speeds [:, j , i ] = alpha * - np .log (- np .log (cum_prob )) + beta
261
315
return fitted_speeds
262
316
263
- def fit_gev (self , exceedance_count : xr .DataArray , p_tail : float = 1 / 5 ):
317
+ def _fit_gev (self , exceedance_count : xr .DataArray , p_tail : float = 1 / 5 ):
264
318
"""For reference, but fit_gumbel is preferred."""
265
319
return_periods = np .array ([5 , 10 , 20 , 50 , 100 , 200 , 500 ])
266
320
data = np .zeros (
@@ -307,7 +361,7 @@ def fit_gev(self, exceedance_count: xr.DataArray, p_tail: float = 1 / 5):
307
361
return fitted_speeds
308
362
309
363
def _file_list (self , year : int ):
310
- return glob .glob (
364
+ return self . fs .glob (
311
365
str (
312
366
Path (self .source_dir )
313
367
/ str (self .synth_set ).replace ("." , "_" )
@@ -316,76 +370,13 @@ def _file_list(self, year: int):
316
370
)
317
371
)
318
372
319
- def open_dataset_year (
320
- self , gcm : str , scenario : str , quantity : str , year : int , chunks = None
321
- ) -> xr .Dataset :
373
+ def _peak_annual_gust_speed_fit (self ) -> xr .Dataset :
322
374
logger .info ("Computing peak annual 3s gust wind speeds" )
323
- peak_annual_gust_speed = self .peak_annual_gust_speed ()
375
+ peak_annual_gust_speed = self ._peak_annual_gust_speed ()
324
376
# any deferred behaviour undesirable here
325
377
peak_annual_gust_speed = peak_annual_gust_speed .compute ()
326
378
logger .info ("Fitting peak wind speeds" )
327
- return self .fit_gumbel (peak_annual_gust_speed ).to_dataset (name = "wind_speed" )
328
-
329
-
330
- class WISCEuropeanWinterStorm (IndicatorModel [str ]):
331
- def __init__ (self ):
332
- """
333
- Peak 3s gust wind speed for different return periods inferred from the WISC event set.
334
-
335
- METADATA:
336
- Link: https://cds.climate.copernicus.eu/datasets/sis-european-wind-storm-synthetic-events?tab=overview
337
- Data type: Synthetic European winter storm events.
338
- Hazard indicator: Wind
339
- Region: Europe
340
- Resolution: 2.4 arcmin
341
- Scenarios: Historical
342
- Time range: Not applicable
343
- File type: NetCDF
344
-
345
- DATA DESCRIPTION:
346
- The WISC dataset contains a set of synthetic windstorm events consisting of 22,980 individual
347
- storm footprints over Europe. These are a physically realistic set of plausible windstorm
348
- events based on the modelled climatic conditions, calculated using the Met Office HadGEM3 model
349
- (Global Atmosphere 3 and Global Land 3 configurations).
350
- Return period maps of peak gust wind speed are inferred from this data.
351
-
352
- Special thanks to Annabel Hall; her work on investigating the fitting of the WISC data set is adapted hereunder.
353
- """
354
-
355
- def batch_items (self ):
356
- """Get a list of all batch items."""
357
- return ["historical" ]
358
-
359
- def run_single (
360
- self , item : str , source : Any , target : ReadWriteDataArray , client : Client
361
- ):
362
- assert isinstance (source , WISCWinterStormEventSource )
363
- logger .info ("Creating data set from events" )
364
- resource = self ._resource ()
365
- for scenario in resource .scenarios :
366
- for year in scenario .years :
367
- ds = source .open_dataset_year ("" , scenario .id , "" , year )
368
- # note that the co-ordinates will be written into the parent of resource.path
369
- target .write (
370
- resource .path .format (scenario = scenario .id , year = year ),
371
- ds ["wind_speed" ].compute (),
372
- spatial_coords = resource .store_netcdf_coords ,
373
- )
374
-
375
- def create_maps (self , source : OscZarr , target : OscZarr ):
376
- """Create map images."""
377
- for resource in self .inventory ():
378
- create_tiles_for_resource (
379
- source ,
380
- target ,
381
- resource ,
382
- nodata_as_zero = True ,
383
- nodata_as_zero_coarsening = True ,
384
- )
385
-
386
- def inventory (self ) -> Iterable [HazardResource ]:
387
- """Get the inventory item(s)."""
388
- return [self ._resource ()]
379
+ return self ._fit_gumbel (peak_annual_gust_speed ).to_dataset (name = "wind_speed" )
389
380
390
381
def _resource (self ) -> HazardResource :
391
382
"""Create resource."""
0 commit comments