meteoraster 2.5__py3-none-any.whl

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.
@@ -0,0 +1,269 @@
1
+
2
+ import math
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ class Resample(object):
7
+ '''
8
+ Resamples meteorological data for use in hydrological and forecasting models
9
+ handles pandas dataframes [production timestamps x leadtime timedeltas]
10
+ Does not need initialization as the main method (resample) is a class method.
11
+
12
+
13
+ v1.01:
14
+ imported to meteoraster from tethys
15
+ '''
16
+
17
+ VERSION = '1.01'
18
+
19
+ @classmethod
20
+ def resample(cls, data, timestep_frequency, resamplingType, date_from=None, date_to=None, print_func=None):
21
+ '''
22
+ Main method of the class.
23
+ Takes the following inputs:
24
+ data: pandas dataframe [production timestamps x leadtime timedeltas]
25
+ timestep_frequency: pandas pd.Timedelta or equivalent string (e.g., '1D', '3H', '30min', '30T', etc...).
26
+ resamplingType: 'sum', 'mean', or 'linear. 'sum' and 'mean' are very similar. There is a difference only when the timestep_frequency is larger than the production frequency (1D case) or the leadtime frequency (2D case).
27
+ date_from: first datetime that is displayed (rows)
28
+ date_to: last datetime that is displayed (rows) - when upsampling additional values may be added.
29
+ print_func: a function handle to print the evolution of the calculations
30
+
31
+
32
+ ### THE VALIDITY CHECK SHOULD BE REVIEWED
33
+ '''
34
+
35
+ headers = list(data.columns.names)
36
+ headers.remove('leadtime')
37
+
38
+ # Default values for the date
39
+ if isinstance(date_from, type(None)):
40
+ date_from = data.index[0]
41
+ if isinstance(date_to, type(None)):
42
+ date_to = data.index[-1]
43
+
44
+ if isinstance(timestep_frequency, dict):
45
+ timestep_frequency = pd.Timedelta(**timestep_frequency)
46
+ elif isinstance(timestep_frequency, str):
47
+ timestep_frequency = pd.Timedelta(timestep_frequency)
48
+
49
+ idx_map = None
50
+ resampled = []
51
+ for g0_, mis0_ in data.groupby(headers, axis=1):
52
+ # Loop - separate handling of each header combination (indexes are calculated on the first pass)
53
+ g0_ = [mis0_.columns.get_level_values(h)[0] for h in headers]
54
+ mis0_ = mis0_.droplevel(headers, axis=1)
55
+
56
+ # Progress message
57
+ if not isinstance(print_func, type(None)):
58
+ print_func(g0_[-1])
59
+
60
+ # Infer frequencies
61
+ # Production (rows)
62
+ prod_frequency = pd.infer_freq(mis0_.index.values[0:3])
63
+ if not prod_frequency:
64
+ prod_frequency = pd.infer_freq(mis0_.index.values[3:6])
65
+ if not prod_frequency[0].isdigit():
66
+ prod_frequency = '1' + prod_frequency
67
+ prod_frequency = pd.Timedelta(prod_frequency)
68
+
69
+ date_from_ = date_from - timestep_frequency * math.ceil(prod_frequency / timestep_frequency)
70
+
71
+ # Leadtime (columns)
72
+ if mis0_.shape[1]>2:
73
+ lead_frequency = pd.infer_freq(mis0_.columns.get_level_values('leadtime'))
74
+ if not lead_frequency[0].isdigit():
75
+ lead_frequency = '1' + lead_frequency
76
+ lead_frequency = pd.Timedelta(lead_frequency)
77
+ elif mis0_.shape[1]==2:
78
+ tmp = mis0_.columns.get_level_values('leadtime')
79
+ lead_frequency = tmp[1]-tmp[0]
80
+ else:
81
+ lead_frequency = prod_frequency
82
+
83
+ # Check frequency ratio
84
+ fr_ratio = prod_frequency/lead_frequency
85
+ if mis0_.shape[1]!=1 and mis0_.shape[1]<fr_ratio:
86
+ raise Exception('Problem with the leadtime [%u (multiple of %u) "%s"] . Does it agree with the production step ["%s"]?' % (mis0_.shape[1], fr_ratio, lead_frequency, prod_frequency))
87
+
88
+ # Check timestep ratio
89
+ ts_ratio = timestep_frequency/lead_frequency
90
+ if mis0_.shape[1]!=1 and mis0_.shape[1]/ts_ratio!=int(mis0_.shape[1]/ts_ratio):
91
+ additional_mis0_steps = int((int(mis0_.shape[1]/ts_ratio)+1) * ts_ratio)-mis0_.shape[1]
92
+ additional_cols = pd.timedelta_range(start=mis0_.columns[-1], periods=additional_mis0_steps+1, freq=lead_frequency, closed='right')
93
+ additional_mis0 = pd.DataFrame(np.nan, index=mis0_.index, columns=additional_cols)
94
+ mis0_ = pd.concat((mis0_, additional_mis0), axis=1)
95
+ #===============================================================
96
+ # valid_mis0_steps = int(int(mis0_.shape[1]/ts_ratio) * ts_ratio)
97
+ # mis0_ = mis0_.iloc[:, :valid_mis0_steps]
98
+ #===============================================================
99
+
100
+ print(' Issue with the leadtime [%u (multiple of %u) "%s"]. Does it agree with the time step ["%s"]? Augmented %u steps.' % (mis0_.shape[1], ts_ratio, lead_frequency, prod_frequency, additional_mis0_steps))
101
+ #===============================================================
102
+ # raise Exception('Problem with the leadtime [%u (multiple of %u) "%s"]. Does it agree with the time step ["%s"]?' % (mis0_.shape[1], ts_ratio, lead_frequency, prod_frequency))
103
+ #===============================================================
104
+
105
+
106
+ calculation_frequency = min((prod_frequency, pd.Timedelta('1H'), timestep_frequency, lead_frequency))
107
+ if mis0_.shape[1]==1:
108
+ resampled_ = cls._resample_aux_1D(mis0_, prod_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from_, date_to)
109
+ else:
110
+ resampled_, idx_map = cls._resample_aux_2D(mis0_, prod_frequency, lead_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from_, date_to, idx_map)
111
+
112
+ #===================================================================
113
+ # mis0_.to_clipboard(excel=True)
114
+ # resampled_.reindex(production_range, axis=0).to_clipboard(excel=True)
115
+ #===================================================================
116
+ resampled_.columns = pd.MultiIndex.from_product([[i] for i in g0_] + [resampled_.columns.tolist()], names=headers + ['delay'])
117
+ resampled.append(resampled_)
118
+
119
+ # Aggregate to the desired time step
120
+ resampled = pd.concat(resampled, axis=1)
121
+ production_range = pd.date_range(start=date_from, end=date_to, freq=timestep_frequency)
122
+
123
+ return resampled.reindex(production_range, axis=0)
124
+
125
+ @classmethod
126
+ def _resample_aux_1D(cls, data, prod_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from, date_to):
127
+ '''
128
+
129
+ prod_frequency assumed to be compatible with timestep_frequency
130
+ calculation frequency is at least hourly to account for timezone shifts
131
+ '''
132
+
133
+ # Calculate reference step windows
134
+ production_calculation_window = int(prod_frequency/calculation_frequency)
135
+ timestep_calculation_window = int(timestep_frequency/calculation_frequency)
136
+
137
+ calculation_range = pd.date_range(start=date_from, end=date_to + prod_frequency, freq=calculation_frequency, inclusive='left')
138
+ resampled_ = data.reindex(calculation_range, axis=0)
139
+
140
+ if resamplingType=='sum' or resamplingType=='mean':
141
+ if production_calculation_window>1:
142
+ resampled_ = resampled_.ffill(limit=production_calculation_window-1) / production_calculation_window # value per calculation step
143
+ resampled_ = resampled_.resample(rule=timestep_frequency, axis=0).sum(min_count=timestep_calculation_window) # sum per time step
144
+
145
+ production_ratio = int(production_calculation_window / timestep_calculation_window)
146
+ if production_ratio > 1:
147
+ if production_ratio != production_calculation_window / timestep_calculation_window:
148
+ raise Exception('Production step not multiple of calculation time step.')
149
+
150
+ tmp = []
151
+ for i0 in range(production_ratio):
152
+ tmp.append(resampled_.shift(-i0))
153
+ tmp = pd.concat(tmp, axis=1)
154
+ tmp.columns = pd.timedelta_range('0D', end=prod_frequency, freq=timestep_frequency, name='leadtimes', closed='left')
155
+ resampled_ = tmp.sum(axis=1, min_count=production_ratio).to_frame()
156
+ if resamplingType=='mean':
157
+ resampled_ /= production_ratio
158
+ resampled_.columns = [pd.Timedelta('0D')]
159
+
160
+ else:
161
+ if resamplingType=='mean':
162
+ resampled_ *= production_calculation_window / timestep_calculation_window
163
+ elif resamplingType=='linear':
164
+ if production_calculation_window>1:
165
+ resampled_ = resampled_.interpolate(axis=0, limit=production_calculation_window-1, method='linear', limit_area='inside')
166
+ elif resamplingType=='max':
167
+ if production_calculation_window>1:
168
+ resampled_ = resampled_.ffill(limit=production_calculation_window-1) # value per calculation step
169
+ resampled_ = resampled_.resample(rule=timestep_frequency, axis=0).max(min_count=timestep_calculation_window) # sum per time step
170
+ else:
171
+ raise Exception('Resampling type not implemented: %s.' % resamplingType)
172
+
173
+ return resampled_
174
+
175
+ @classmethod
176
+ def _resample_aux_2D(cls, data, prod_frequency, leadtime_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from, date_to, idx_map):
177
+ '''
178
+
179
+ '''
180
+
181
+ # Reindex to calculation delta when required
182
+ if isinstance(idx_map, type(None)):
183
+ idx_map = cls._resample_aux_2D_idx(data, prod_frequency, leadtime_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from, date_to, idx_map)
184
+
185
+ # Calculate reference step windows
186
+ leadtime_calculation_window = int(leadtime_frequency/calculation_frequency)
187
+ timestep_calculation_window = int(timestep_frequency/calculation_frequency)
188
+
189
+ resampled_ = idx_map.copy()*np.nan
190
+ tmp0 = idx_map.dropna().astype(int).values.ravel()
191
+ tmp1 = np.isfinite(idx_map).astype(bool).values.ravel()
192
+ resampled_.loc[tmp1,:] = data.values.ravel()[tmp0]
193
+ resampled_ = resampled_.unstack('leadtime')
194
+ resampled_ = resampled_.droplevel(0, axis=1)
195
+
196
+ # Aggregate to desired time step (columns)
197
+ if timestep_calculation_window>1 or leadtime_calculation_window>1:
198
+ if resamplingType=='sum' or resamplingType=='mean':
199
+ resampled_ = resampled_.resample(rule=timestep_frequency, axis=1).sum(min_count=timestep_calculation_window) / timestep_calculation_window
200
+ if resamplingType=='sum':
201
+ resampled_ *= timestep_calculation_window / leadtime_calculation_window
202
+ elif resamplingType=='linear':
203
+ if leadtime_calculation_window>1:
204
+ resampled_ = resampled_.interpolate(axis=1, limit=leadtime_calculation_window-1, method='linear')
205
+ leadtimes_ = pd.timedelta_range(start=max((pd.Timedelta(hours=0), resampled_.columns[0])), end=resampled_.columns[-1], freq=timestep_frequency)
206
+ resampled_ = resampled_.reindex(leadtimes_, axis=1)
207
+ elif resamplingType=='max':
208
+ resampled_ = resampled_.resample(rule=timestep_frequency, axis=1).max(min_count=timestep_calculation_window)
209
+ else:
210
+ raise Exception('Resampling type not implemented: %s.' % resamplingType)
211
+
212
+ return (resampled_, idx_map)
213
+
214
+ @classmethod
215
+ def _resample_aux_2D_idx(cls, data, prod_frequency, leadtime_frequency, calculation_frequency, timestep_frequency, resamplingType, date_from, date_to, idx_map):
216
+ '''
217
+
218
+ '''
219
+ # Calculate reference step windows
220
+ production_calculation_window = int(prod_frequency/calculation_frequency)
221
+ leadtime_calculation_window = int(leadtime_frequency/calculation_frequency)
222
+
223
+ # Reindex to calculation delta when required
224
+ if isinstance(idx_map, type(None)):
225
+ idx_map = data.copy()
226
+ idx_map.loc[:,:] = np.arange(idx_map.size).reshape(idx_map.shape, order='C')
227
+ idx_map = idx_map
228
+ leadtimes = data.columns
229
+
230
+ # Handle columns (leadtimes)
231
+ if leadtime_calculation_window>1:
232
+ if resamplingType=='linear':
233
+ leadtime_index = pd.timedelta_range(start=leadtimes[0] - leadtime_frequency + calculation_frequency, end=leadtimes[-1] + leadtime_frequency, freq=calculation_frequency, closed='left')
234
+ else:
235
+ leadtime_index = pd.timedelta_range(start=leadtimes[0], end=leadtimes[-1]+leadtime_frequency, freq=calculation_frequency, closed='left')
236
+ if resamplingType=='sum' or resamplingType=='mean' or resamplingType=='max':
237
+ idx_map = idx_map.reindex(leadtime_index, axis=1).ffill(limit=leadtime_calculation_window-1, axis=1)
238
+ elif resamplingType=='linear':
239
+ idx_map = idx_map.reindex(leadtime_index, axis=1)
240
+ else:
241
+ raise Exception('Resampling type not implemented: %s.' % resamplingType)
242
+ idx_map.columns.name = 'leadtime'
243
+
244
+ # Handle rows (production times)
245
+ if production_calculation_window>1:
246
+ calculation_index = pd.date_range(start=date_from, end=date_to+leadtime_frequency, freq=calculation_frequency, inclusive='left')
247
+
248
+ idx_map = idx_map.reindex(calculation_index, axis=0)
249
+ valid = idx_map.iloc[0, :]*np.nan
250
+ shift = 1
251
+ for r0 in range(0, idx_map.shape[0]):
252
+ if np.isfinite(idx_map.iloc[r0, :]).any():
253
+ valid = idx_map.iloc[r0, :]
254
+ shift = 1
255
+ else:
256
+ idx_map.iloc[r0, :] = valid.shift(-shift)
257
+ shift += 1
258
+
259
+ idx_map = idx_map.stack('leadtime', dropna=False).to_frame()
260
+
261
+ return idx_map
262
+
263
+ @classmethod
264
+ def production_to_event(cls, data, prod_frequency):
265
+ '''
266
+ '''
267
+
268
+ raise(Exception('Not implemented'))
269
+
meteoraster/utils.py ADDED
@@ -0,0 +1,359 @@
1
+ '''
2
+ Created on 10/02/2023
3
+
4
+ @author: Jose Pedro Matos
5
+ '''
6
+
7
+ import warnings
8
+ import numpy as np
9
+ import xarray as xr
10
+ import pandas as pd
11
+ from pathlib import Path
12
+ from collections.abc import Iterable
13
+ from meteoraster import MeteoRaster
14
+
15
+ def readERA5_monthly(file, variable, **kwargs):
16
+ '''
17
+ Return a dict:
18
+ data: 5-D numpy array [productionDate, ensembleMember, leadtime, latitude, longitude]
19
+ production dates: 1-D numpy array
20
+ leadtimes: 1-D numpy array
21
+ latitudes: 1-D numpy array
22
+ longitudes: 1-D numpy array
23
+ '''
24
+
25
+ data = {}
26
+ with xr.open_dataset(file, engine='cfgrib', indexpath='', **kwargs) as ds:
27
+ data['latitudes'] = ds.latitude.data
28
+ data['longitudes'] = ds.longitude.data
29
+ data['productionDates'] = pd.to_datetime(ds.time.data)
30
+ tmp = ds[variable][:, :, :].data
31
+ data['data'] = np.expand_dims(tmp, [1, 2])
32
+ data['leadtimes'] = np.array([pd.DateOffset(days=0)])
33
+
34
+
35
+ if variable == 'tp':
36
+ data['data'] *= 1000 #to mm/day
37
+ days_in_month = data['productionDates'].days_in_month
38
+ days_in_month = np.expand_dims(days_in_month, (1, 2, 3, 4))
39
+ days_in_month = np.tile(days_in_month, [1] + list(data['data'].shape[1:]))
40
+ data['data'] *= days_in_month
41
+ units = 'mm/month'
42
+ elif variable == 't2m':
43
+ data['data'] -= 273.15
44
+ units = 'C'
45
+ elif variable == 'ro':
46
+ data['data'] *= 1000
47
+ units = 'mm/month'
48
+ elif variable == 'e':
49
+ data['data'] *= 1000
50
+ units = 'mm/month'
51
+
52
+ tmp = MeteoRaster(data, variable=variable, units=units)
53
+ return tmp
54
+
55
+ def readERA5Land_monthly(file, variable, **kwargs):
56
+ '''
57
+ Return a dict:
58
+ data: 5-D numpy array [productionDate, ensembleMember, leadtime, latitude, longitude]
59
+ production dates: 1-D numpy array
60
+ leadtimes: 1-D numpy array
61
+ latitudes: 1-D numpy array
62
+ longitudes: 1-D numpy array
63
+
64
+ https://cds.climate.copernicus.eu/datasets/reanalysis-era5-land-monthly-means?tab=overview
65
+
66
+ t2m: temperature
67
+ tp: total precipitation
68
+ ...
69
+ '''
70
+
71
+ if variable=='tp':
72
+ if 'backend_kwargs' in kwargs.keys():
73
+ kwargs['backend_kwargs'].update({'filter_by_keys': {'shortName': 'tp', 'stepType': 'avgad'}})
74
+ else:
75
+ kwargs['backend_kwargs'] = {'filter_by_keys': {'shortName': 'tp', 'stepType': 'avgad'}}
76
+
77
+ data = {}
78
+ with xr.open_dataset(file, engine='cfgrib', indexpath='', **kwargs) as ds:
79
+ data['latitudes'] = ds.latitude.data
80
+ data['longitudes'] = ds.longitude.data
81
+ data['productionDates'] = pd.to_datetime(ds.time.data)
82
+ tmp = ds[variable][:, :, :].data
83
+ data['data'] = np.expand_dims(tmp, [1, 2])
84
+ data['leadtimes'] = np.array([pd.DateOffset(days=0)])
85
+
86
+
87
+ if variable == 'tp':
88
+ data['data'] *= 1000 #to mm/day
89
+ days_in_month = data['productionDates'].days_in_month
90
+ days_in_month = np.expand_dims(days_in_month, (1, 2, 3, 4))
91
+ days_in_month = np.tile(days_in_month, [1] + list(data['data'].shape[1:]))
92
+ data['data'] *= days_in_month
93
+ units = 'mm/month'
94
+ elif variable == 't2m':
95
+ data['data'] -= 273.15
96
+ units = 'C'
97
+ elif variable == 'ro':
98
+ data['data'] *= 1000
99
+ units = 'mm/month'
100
+ elif variable == 'e':
101
+ data['data'] *= 1000
102
+ units = 'mm/month'
103
+ else:
104
+ raise(Exception(f'How to handle variable "{variable}" must be defined.'))
105
+
106
+ tmp = MeteoRaster(data, variable=variable, units=units)
107
+ return tmp
108
+
109
+ def read_ERA5Land_hourly(file, variable, file_next_year=None):
110
+ '''
111
+ Returns a MeteoRaster object with the ERA5 Land data
112
+ '''
113
+
114
+ cumulative = {'tp': True, 'ssr': True, 'u10': False, 'v10': False, 'sd': False, 't2m': False}
115
+
116
+ def _read_ERA5Land_helper(file, variable):
117
+ '''
118
+ Just reads the grib file
119
+ '''
120
+
121
+ data = {}
122
+ with xr.open_dataset(file, engine='cfgrib', indexpath='') as ds:
123
+ data['latitudes'] = ds.latitude.data
124
+ data['longitudes'] = ds.longitude.data
125
+ data['productionDates'] = ds.time.data
126
+ if isinstance(data['productionDates'], np.datetime64):
127
+ data['productionDates'] = np.array([data['productionDates']])
128
+ data['data'] = ds[variable][:, :, :].data
129
+ data['steps'] = ds.step.data
130
+
131
+ return data
132
+
133
+ data = _read_ERA5Land_helper(file, variable)
134
+
135
+ if cumulative[variable]:
136
+ first_timestamp = pd.Timestamp(data['productionDates'][0])
137
+ last_timestamp = pd.Timestamp(data['productionDates'][-1])
138
+ if first_timestamp==pd.Timestamp(first_timestamp.year, 12, 31) and np.isnan(data['data'][0, -2, :, :]).min():
139
+ # Last day of the previous year with data on the last hour only... To remove.
140
+ data['data'] = data['data'][1:, :, :, :]
141
+ data['productionDates'] = data['productionDates'][1:]
142
+ warnings.warn('Last hour of the previous year dropped...')
143
+
144
+ if last_timestamp==pd.Timestamp(last_timestamp.year, 12, 31) and np.isnan(data['data'][-1, -1, :, :]).max():
145
+ if file_next_year is not None:
146
+ # Last day of the previous year with data missing on the last hour... To lead next year and take that value
147
+ data_last = _read_ERA5Land_helper(file_next_year, variable)
148
+ data['data'][-1, -1, :, :] = data_last['data'][0, -1, :, :]
149
+ warnings.warn('First hour of the following year added...')
150
+
151
+ if len(data['data'].shape)==4:
152
+ # (date, hour, ...) > (timestamp, ---)
153
+ data['data'] = np.diff(data['data'], n=1, axis=1, prepend=0)
154
+ else:
155
+ data['data'] = np.diff(data['data'], n=1, axis=0, prepend=0)
156
+
157
+ if variable == 'tp':
158
+ data['data'] *= 1000
159
+ units = 'mm/hr'
160
+ elif variable == 't2m':
161
+ data['data'] -= 273.15
162
+ units = 'C'
163
+ elif variable == 'sd':
164
+ #=======================================================================
165
+ # raise Exception('Cumulative?')
166
+ #=======================================================================
167
+ data['data'] *= 1000
168
+ units = 'mm'
169
+ elif variable == 'ssr':
170
+ data['data'] /= 3600
171
+ units = 'W/m2'
172
+ elif variable == 'u10' or variable == 'v10':
173
+ units = 'm/s'
174
+
175
+ #=======================================================================
176
+ # if variable == 'tp' or variable == 't2m':
177
+ # tmp = np.reshape(tmp, (tmp.shape[0]*tmp.shape[1], tmp.shape[2], tmp.shape[3]))
178
+ # times = np.tile(data['productionDates'], (24, 1)).transpose() + np.tile(ds.step.data, (data['productionDates'].shape[0], 1))
179
+ # data['productionDates'] = times.ravel()
180
+ #=======================================================================
181
+
182
+ if not isinstance(data['steps'], Iterable):
183
+ data['steps'] = [data['steps']]
184
+
185
+ if variable !='sd':
186
+ data['data'] = np.reshape(data['data'], (data['data'].shape[0]*data['data'].shape[1], data['data'].shape[-2], data['data'].shape[-1]))
187
+
188
+ times = np.tile(data['productionDates'], (24, 1)).transpose() + np.tile(data['steps'] - data['steps'][0], (data['productionDates'].shape[0], 1))
189
+ data['productionDates'] = times.ravel()
190
+
191
+ data['data'] = np.expand_dims(data['data'], [1, 2])
192
+ data['leadtimes'] = np.array([pd.DateOffset(days=0)])
193
+
194
+ tmp = MeteoRaster(data, units=units, variable=variable)
195
+ tmp.trim()
196
+
197
+ return tmp
198
+
199
+ def read_GFS(path, timestamp, leadtime, variable, crop=None):
200
+ '''
201
+ TMP
202
+ PRATE
203
+ WEASD
204
+ DPT
205
+ RH
206
+ SNOD
207
+ SUNSD
208
+ UGRD
209
+ VGRD
210
+ '''
211
+
212
+ GFS_info = dict(TMP=dict(units='C', variable='t2m', transform=lambda x: x),
213
+ PRATE=dict(units='mm/3h', variable='tp', transform=lambda x: x*3600*3), # kg/(m^2 s) > mm/3h
214
+ )
215
+
216
+ lead_hours = leadtime // np.timedelta64(1, 'h')
217
+
218
+ path_ = Path(path) / timestamp.strftime('%Y/%m') / '00' / ('%03u' % lead_hours)
219
+ file = path_ / 'gfs_4_{date:s}_00_{lead:03d}.nc'.format(date=timestamp.strftime('%Y.%m.%d'), lead=lead_hours)
220
+
221
+ data = {}
222
+ with xr.open_dataset(file) as ds:
223
+ data['latitudes'] = ds.lat.data
224
+ data['longitudes'] = ds.lon.data
225
+ data['productionDates'] = ds.productionDatetimes.data
226
+ data['leadtimes'] = np.array([pd.Timedelta(hours=int(ds.leadtimes.data/1000000000/3600))])
227
+ tmp = ds[variable][:, :, :, :].data
228
+ data['data'] = np.expand_dims(tmp, [1])
229
+
230
+ if GFS_info[variable]['transform']:
231
+ data['data'] = GFS_info[variable]['transform'](data['data'])
232
+
233
+ meteoraster = MeteoRaster(data, units=GFS_info[variable]['units'], variable=GFS_info[variable]['variable'])
234
+ if crop:
235
+ meteoraster = meteoraster.getCropped(**crop)
236
+
237
+ return meteoraster
238
+
239
+ def read_C3S(file, variable, engine='cfgrib', **kwargs):
240
+ '''
241
+ Reads c3s forecasts (seasonal forecast monthly statistics on single levels)
242
+ https://cds.climate.copernicus.eu/datasets/seasonal-monthly-single-levels?tab=overview
243
+
244
+ v0.1
245
+ 2024.10.04
246
+
247
+ Return a dict:
248
+ data: 5-D numpy array [productionDate, ensembleMember, leadtime, latitude, longitude]
249
+ production dates: 1-D numpy array
250
+ leadtimes: 1-D numpy array
251
+ latitudes: 1-D numpy array
252
+ longitudes: 1-D numpy array
253
+ '''
254
+
255
+ data = {}
256
+ with xr.open_dataset(file, engine=engine, indexpath='', **kwargs) as ds:
257
+ data['latitudes'] = ds.latitude.data
258
+ data['longitudes'] = ds.longitude.data
259
+ data['productionDates'] = ds.time.data
260
+ data_ = ds[variable][:, :, :, :].data
261
+ steps_ = pd.to_timedelta([pd.Timedelta(f'{d}d') for d in (ds.step.data/86400/1000000000).astype(int)])
262
+
263
+ data_ = np.rollaxis(data_, 1, 0)
264
+
265
+ months_ = np.round(steps_.days/30).astype(int)
266
+ months_unique = np.unique(months_)
267
+ data['leadtimes'] = [pd.DateOffset(months=m-1) for m in months_unique]
268
+
269
+ data_shape = list(data_.shape)
270
+ data_shape[2] = len(months_unique)
271
+ data['data'] = np.empty(data_shape)*np.nan
272
+ for i0, m0 in enumerate(months_unique):
273
+ idxs = months_==m0
274
+ data['data'][:, :, i0, :, :] = np.nanmean(data_[:, :, idxs, :, :], axis=2)
275
+
276
+ if variable == 'tprate':
277
+ data['data'] *= 1000 # to mm/s
278
+
279
+ idx = pd.MultiIndex.from_product([data['productionDates'], data['leadtimes']],
280
+ names=['productionDates', 'leadtimes']).to_frame()
281
+ days_in_month = idx.sum(axis=1).dt.days_in_month.unstack('leadtimes')
282
+ seconds_in_month = days_in_month*86400
283
+
284
+ multiplier = np.expand_dims(seconds_in_month.values, (1, -2, -1))
285
+ tmp = data_shape.copy()
286
+ tmp[0] = 1
287
+ tmp[2] = 1
288
+ multiplier = np.tile(multiplier, tmp)
289
+ data['data'] *= multiplier
290
+
291
+ units = 'mm/month'
292
+ variable='tp'
293
+ elif variable == 't2m':
294
+ data['data'] -= 273.15
295
+ units = 'C'
296
+
297
+ tmp = MeteoRaster(data, units=units, variable=variable)
298
+ return(tmp)
299
+
300
+ def readCORDEX_monthly(file, variable, convention=pd.offsets.MonthBegin(), remove_time=True):
301
+ '''
302
+ Reads CORDEX CMIP 5 monthly data.
303
+
304
+ variable:
305
+ pr - precipitation
306
+ tas - air temperature near surface
307
+
308
+ convention (pandas offset):
309
+ pd.offsets.MonthBegin()
310
+ None - file default
311
+ '''
312
+
313
+ data = {}
314
+ with xr.open_dataset(file) as ds:
315
+ data['latitudes'] = ds.lat.data
316
+ data['longitudes'] = ds.lon.data
317
+ data['productionDates'] = ds.time.data
318
+ data['data'] = ds[variable][:, :, :].data
319
+ data['leadtimes'] = np.array([pd.DateOffset(days=0)])
320
+
321
+ if not convention is None:
322
+ data['productionDates'] = [d + convention for d in data['productionDates']]
323
+ if remove_time:
324
+ data['productionDates'] = [d.normalize() for d in data['productionDates']]
325
+
326
+ if variable=='tas':
327
+ units = 'C'
328
+ variable = 'Air temperature near surface'
329
+ elif variable=='pr':
330
+ units = 'mm/month'
331
+ variable = 'Precipitation'
332
+ else:
333
+ raise(Exception(f'How to handle variable "{variable}" must be defined.'))
334
+
335
+ data['data'] = np.expand_dims(data['data'], [1, 2])
336
+
337
+ tmp = MeteoRaster(data, units=units, variable=variable)
338
+ tmp.trim()
339
+
340
+ return tmp
341
+
342
+
343
+ if __name__ == '__main__':
344
+
345
+ import matplotlib.pyplot as plt
346
+
347
+ #===========================================================================
348
+ # file = Path(r'C:\Users\zepedro\Documents\GitHub\Climate-change\data\ERA5 land') / 'pr_MOROCCO_ERA5Land.grib'
349
+ # era5 = readERA5Land_monthly(file, 'tp')
350
+ # data = era5.plot_mean(coastline=True, borders=False, colorbar=True,
351
+ # colorbar_label=f'[{era5.units}]', cmap='viridis', central_longitude=20, central_latitude=30)
352
+ #===========================================================================
353
+
354
+ #===========================================================================
355
+ # file = Path(r'C:\Users\zepedro\Documents\GitHub\Climate-change\data\ERA5 land') / 'tas_MOROCCO_ERA5Land.grib'
356
+ # era5 = readERA5Land_monthly(file, 't2m')
357
+ # data = era5.plot_mean(coastline=True, borders=False, colorbar=True,
358
+ # colorbar_label=f'[{era5.units}]', cmap='magma', central_longitude=20, central_latitude=30)
359
+ #===========================================================================