5454 'type' : 'string' ,
5555 },
5656 'minOccurs' : 1 ,
57- 'maxOccurs' : 1
57+ 'maxOccurs' : 1 ,
5858 },
5959 'model_run' : {
6060 'title' : 'Model run' ,
6363 'minOccurs' : 1 ,
6464 'maxOccurs' : 1 ,
6565 },
66- 'forecast_hour' : {
67- 'title' : 'Forecast hour' ,
68- 'description' : 'Forecast hour in XXX format.' ,
66+ 'forecast_start_hour' : {
67+ 'title' : 'Forecast start hour' ,
68+ 'description' : 'Forecast start hour in XXX format.' ,
69+ 'schema' : {'type' : 'string' },
70+ 'minOccurs' : 1 ,
71+ 'maxOccurs' : 1 ,
72+ },
73+ 'forecast_step' : {
74+ 'title' : 'Forecast time step' ,
75+ 'description' : 'Forecast time step, usually a multiple of 3' ,
76+ 'schema' : {'type' : 'number' },
77+ 'minOccurs' : 1 ,
78+ 'maxOccurs' : 1 ,
79+ },
80+ 'forecast_end_hour' : {
81+ 'title' : 'Forecast end hour' ,
82+ 'description' : 'Forecast end hour in XXX format.' ,
6983 'schema' : {'type' : 'string' },
7084 'minOccurs' : 1 ,
7185 'maxOccurs' : 1 ,
7791 'type' : 'number' ,
7892 },
7993 'minOccurs' : 1 ,
80- 'maxOccurs' : 1
94+ 'maxOccurs' : 1 ,
8195 },
8296 'lat' : {
8397 'title' : 'lat coordinate' ,
86100 'type' : 'number' ,
87101 },
88102 'minOccurs' : 1 ,
89- 'maxOccurs' : 1
103+ 'maxOccurs' : 1 ,
90104 },
91105 },
92106 "outputs" : {
99113 'inputs' : {
100114 'model' : 'GDWPS' ,
101115 'model_run' : f'{ (datetime .date .today () - datetime .timedelta (days = 1 )).strftime ("%Y-%m-%d" )} T12:00:00Z' , # noqa
102- 'forecast_hour ' : '003' ,
116+ 'forecast_start_hour ' : '003' ,
103117 'lon' : - 28.75 ,
104118 'lat' : 39.25 ,
105119 }
@@ -150,103 +164,158 @@ def geo2xy(ds, x, y):
150164 return (x , y )
151165
152166
153- def extract_wind_data (
154- model ,
155- model_run ,
156- forecast_hour ,
157- lon ,
158- lat ,
159- ):
160- from msc_pygeoapi .env import GEOMET_HPFX_BASEPATH
161-
162- data_basepath = GEOMET_HPFX_BASEPATH
163-
164- date = datetime .datetime .strptime (model_run , DATE_FORMAT )
165- run_hour = f"{ date .hour :02} "
166- date_formatted = date .strftime ("%Y%m%d" )
167-
167+ def get_single_wind_data (model , date_formatted , run_hour , forecast_hour , data_basepath , lon , lat ):
168168 match (model ):
169169 case "GDWPS" :
170170 inter_path = f"/model_gdwps/25km/{ run_hour } /"
171- file_name_x = f"{ date_formatted } T{ run_hour } Z_MSC_GDWPS_UGRD_AGL-10m_LatLon0.25_PT{ forecast_hour } H.grib2"
172- file_name_y = f"{ date_formatted } T{ run_hour } Z_MSC_GDWPS_VGRD_AGL-10m_LatLon0.25_PT{ forecast_hour } H.grib2"
171+ file_name_u = f"{ date_formatted } T{ run_hour } Z_MSC_GDWPS_UGRD_AGL-10m_LatLon0.25_PT{ forecast_hour } H.grib2"
172+ file_name_v = f"{ date_formatted } T{ run_hour } Z_MSC_GDWPS_VGRD_AGL-10m_LatLon0.25_PT{ forecast_hour } H.grib2"
173173
174174 case "GDPS" :
175175 inter_path = f"/model_gem_global/15km/grib2/lat_lon/{ run_hour } /{ forecast_hour } /"
176- file_name_x = f"CMC_glb_UGRD_TGL_10_latlon.15x.15_{ date_formatted } { run_hour } _P{ forecast_hour } .grib2"
177- file_name_y = f"CMC_glb_VGRD_TGL_10_latlon.15x.15_{ date_formatted } { run_hour } _P{ forecast_hour } .grib2"
176+ file_name_u = f"CMC_glb_UGRD_TGL_10_latlon.15x.15_{ date_formatted } { run_hour } _P{ forecast_hour } .grib2"
177+ file_name_v = f"CMC_glb_VGRD_TGL_10_latlon.15x.15_{ date_formatted } { run_hour } _P{ forecast_hour } .grib2"
178178
179179 case "GEPS" :
180180 raise NotImplementedError ("GEPS raw data not yet in Geomet HPFX" )
181181
182182 case "RDWPS" :
183- inter_path = f"/model_rdwps/national/2.5km/{ run_hour } /"
184- file_name_x = f"{ date_formatted } T{ run_hour } Z_MSC_RDWPS_UGRD_AGL-10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
185- file_name_y = f"{ date_formatted } T{ run_hour } Z_MSC_RDWPS_VGRD_AGL-10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
183+ # RDWPS grid is rotated so we're using HRDPS instead
184+ inter_path = f"/model_hrdps/continental/2.5km/{ run_hour } /{ forecast_hour } /"
185+ file_name_wind = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_WIND_AGL-10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
186+ file_name_wdir = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_WDIR_AGL-10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
186187
187188 case "HRDPS" :
188189 inter_path = f"/model_hrdps/continental/2.5km/{ run_hour } /{ forecast_hour } /"
189- file_name_x = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_UGRD_AGL -10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
190- file_name_y = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_VGRD_AGL -10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
190+ file_name_wind = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_WIND_AGL -10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
191+ file_name_wdir = f"{ date_formatted } T{ run_hour } Z_MSC_HRDPS_WDIR_AGL -10m_RLatLon0.0225_PT{ forecast_hour } H.grib2"
191192
192193 case "REPS" :
193194 inter_path = f"/ensemble/reps/10km/grib2/{ run_hour } /{ forecast_hour } /"
194- file_name_x = f"{ date_formatted } T{ run_hour } Z_MSC_REPS_UGRD_AGL-10m_RLatLon0.09x0.09_PT{ forecast_hour } H.grib2"
195- file_name_y = f"{ date_formatted } T{ run_hour } Z_MSC_REPS_VGRD_AGL-10m_RLatLon0.09x0.09_PT{ forecast_hour } H.grib2"
195+ file_name_u = f"{ date_formatted } T{ run_hour } Z_MSC_REPS_UGRD_AGL-10m_RLatLon0.09x0.09_PT{ forecast_hour } H.grib2"
196+ file_name_v = f"{ date_formatted } T{ run_hour } Z_MSC_REPS_VGRD_AGL-10m_RLatLon0.09x0.09_PT{ forecast_hour } H.grib2"
196197
197198 case _:
198199 raise ValueError (f"Unknown model: { model } " )
199200
200- output = {
201- 'type' : "Feature" ,
202- 'geometry' : {"type" : "Point" , "coordinates" : [lon , lat ]},
203- 'properties' : {
204- 'wind_speed_unit' : "knots" ,
205- 'wind_direction_unit' : "°" ,
206- },
207- }
201+ if model in ("GDWPS" , "GDPS" , "GEPS" , "REPS" ):
202+ full_path_u = f"{ data_basepath } { inter_path } { file_name_u } "
203+ full_path_v = f"{ data_basepath } { inter_path } { file_name_v } "
204+
205+ ds_u = gdal .Open (full_path_u , gdal .GA_ReadOnly )
206+ ds_v = gdal .Open (full_path_v , gdal .GA_ReadOnly )
207+
208+ if ds_u is None :
209+ raise NameError (f"Couldn't open { full_path_u } , check if file exists" )
210+
211+ if ds_v is None :
212+ raise NameError (f"Couldn't open { full_path_v } , check if file exists" )
208213
209- full_path_x = f"{ data_basepath } { inter_path } { file_name_x } "
210- full_path_y = f"{ data_basepath } { inter_path } { file_name_y } "
214+ out_proj = ds_u .GetProjection ()
215+ transformer = Transformer .from_crs ("EPSG:4326" , out_proj , always_xy = True )
216+ _x , _y = transformer .transform (lon , lat )
217+ x , y = geo2xy (ds_u , _x , _y )
211218
212- ds_x = gdal . Open ( full_path_x , gdal . GA_ReadOnly )
213- ds_y = gdal . Open ( full_path_y , gdal . GA_ReadOnly )
219+ band_u = ds_u . GetRasterBand ( 1 )
220+ bitmap_u = band_u . GetMaskBand (). ReadAsArray ( )
214221
215- if ds_x is None :
216- raise NameError (f"Couldn't open { full_path_x } , check if file exists" )
222+ try :
223+ _ = bitmap_u [y , x ]
224+ except IndexError :
225+ raise IndexError ("ERROR: no data at requested latitude and longitude - point outside of model grid" )
217226
218- if ds_y is None :
219- raise NameError ( f"Couldn't open { full_path_y } , check if file exists " )
227+ if not bitmap_u [ y , x ] :
228+ raise IndexError ( "ERROR: no data at requested latitude and longitude - data at point is masked " )
220229
221- out_proj = ds_x .GetProjection ()
222- transformer = Transformer .from_crs ("EPSG:4326" , out_proj , always_xy = True )
223- _x , _y = transformer .transform (lon , lat )
224- x , y = geo2xy (ds_x , _x , _y )
230+ band_y = ds_v .GetRasterBand (1 )
225231
226- band_x = ds_x . GetRasterBand ( 1 )
227- bitmap_x = band_x . GetMaskBand () .ReadAsArray ()
232+ data_array_u = band_u . ReadAsArray ( )
233+ data_array_v = band_y .ReadAsArray ()
228234
229- try :
230- _ = bitmap_x [y , x ]
231- except IndexError :
232- raise IndexError ("ERROR: no data at requested latitude and longitude - point outside of model grid" )
235+ wind_speed_u = data_array_u [y , x ]
236+ wind_speed_v = data_array_v [y , x ]
233237
234- if not bitmap_x [ y , x ]:
235- raise IndexError ( "ERROR: no data at requested latitude and longitude - data at point is masked" )
238+ norm = get_norm ( wind_speed_u , wind_speed_v ) * MS_TO_KNOTS
239+ dir = get_dir ( wind_speed_u , wind_speed_v )
236240
237- band_y = ds_y .GetRasterBand (1 )
241+ else :
242+ full_path_wind = f"{ data_basepath } { inter_path } { file_name_wind } "
243+ full_path_wdir = f"{ data_basepath } { inter_path } { file_name_wdir } "
238244
239- data_array_x = band_x . ReadAsArray ( )
240- data_array_y = band_y . ReadAsArray ( )
245+ ds_wind = gdal . Open ( full_path_wind , gdal . GA_ReadOnly )
246+ ds_wdir = gdal . Open ( full_path_wdir , gdal . GA_ReadOnly )
241247
242- wind_speed_x = data_array_x [y , x ]
243- wind_speed_y = data_array_y [y , x ]
248+ if ds_wind is None :
249+ raise NameError (f"Couldn't open { full_path_wind } , check if file exists" )
250+
251+ if ds_wdir is None :
252+ raise NameError (f"Couldn't open { full_path_wdir } , check if file exists" )
253+
254+ out_proj = ds_wind .GetProjection ()
255+ transformer = Transformer .from_crs ("EPSG:4326" , out_proj , always_xy = True )
256+ _x , _y = transformer .transform (lon , lat )
257+ x , y = geo2xy (ds_wind , _x , _y )
258+
259+ band_wind = ds_wind .GetRasterBand (1 )
260+ bitmap_wind = band_wind .GetMaskBand ().ReadAsArray ()
261+
262+ try :
263+ _ = bitmap_wind [y , x ]
264+ except IndexError :
265+ raise IndexError ("ERROR: no data at requested latitude and longitude - point outside of model grid" )
266+
267+ if not bitmap_wind [y , x ]:
268+ raise IndexError ("ERROR: no data at requested latitude and longitude - data at point is masked" )
269+
270+ band_wdir = ds_wdir .GetRasterBand (1 )
271+
272+ data_array_wind = band_wind .ReadAsArray ()
273+ data_array_wdir = band_wdir .ReadAsArray ()
274+
275+ norm = data_array_wind [y , x ] * MS_TO_KNOTS
276+ dir = data_array_wdir [y , x ]
277+
278+ output = {}
279+
280+ output ['wind_speed' ] = norm
281+ output ['wind_dir' ] = dir
282+
283+ return output
284+
285+
286+ def extract_wind_data (
287+ model ,
288+ model_run ,
289+ forecast_start_hour ,
290+ forecast_step ,
291+ forecast_end_hour ,
292+ lon ,
293+ lat ,
294+ ):
295+ from msc_pygeoapi .env import GEOMET_HPFX_BASEPATH
296+
297+ data_basepath = GEOMET_HPFX_BASEPATH
298+
299+ date = datetime .datetime .strptime (model_run , DATE_FORMAT )
300+ run_hour = f"{ date .hour :02} "
301+ date_formatted = date .strftime ("%Y%m%d" )
302+
303+ output = {
304+ 'type' : "Feature" ,
305+ 'geometry' : {"type" : "Point" , "coordinates" : [lon , lat ]},
306+ 'properties' : {
307+ 'wind_speed_unit' : "knots" ,
308+ 'wind_direction_unit' : "°" ,
309+ },
310+ }
244311
245- norm = get_norm ( wind_speed_x , wind_speed_y ) * MS_TO_KNOTS
246- dir = get_dir ( wind_speed_x , wind_speed_y )
312+ n = ( int ( forecast_end_hour ) - int ( forecast_start_hour )) / forecast_step
313+ n = int ( n ) + 1
247314
248- output ['properties' ]['wind_speed' ] = norm
249- output ['properties' ]['wind_dir' ] = dir
315+ for i in range (n ):
316+ forecast_hour = int (forecast_start_hour ) + (i * forecast_step )
317+ forecast_hour = f'{ forecast_hour } ' .zfill (3 )
318+ output ['properties' ][forecast_hour ] = get_single_wind_data (model , date_formatted , run_hour , forecast_hour , data_basepath , lon , lat )
250319
251320 return output
252321
@@ -271,7 +340,7 @@ def extract_wind_data_execute():
271340)
272341@click .option (
273342 "-fh" ,
274- "--forecast_hour " ,
343+ "--forecast_start_hour " ,
275344 help = "forecast hour 3 digits format" ,
276345 required = True
277346)
@@ -289,7 +358,7 @@ def extract_wind_data_cli(
289358 ctx ,
290359 model ,
291360 model_run ,
292- forecast_hour ,
361+ forecast_start_hour ,
293362 lon ,
294363 lat ,
295364):
@@ -301,7 +370,7 @@ def extract_wind_data_cli(
301370 output = extract_wind_data (
302371 model ,
303372 model_run ,
304- forecast_hour ,
373+ forecast_start_hour ,
305374 float (lon ),
306375 float (lat ),
307376 )
@@ -332,23 +401,27 @@ def __init__(self, provider_def):
332401 def execute (self , data , outputs = None ):
333402 mimetype = "application/json"
334403
335- required = ["model" , "model_run" , "forecast_hour " , "lon" , "lat" ]
404+ required = ["model" , "model_run" , "forecast_start_hour" , "forecast_step" , "forecast_end_hour " , "lon" , "lat" ]
336405 if not all ([param in data for param in required ]):
337406 msg = "Missing required parameters."
338407 LOGGER .error (msg )
339408 raise ProcessorExecuteError (msg )
340409
341410 model = data .get ("model" )
342411 mr = data .get ("model_run" )
343- fh = data .get ("forecast_hour" )
412+ fsh = data .get ("forecast_start_hour" )
413+ fs = data .get ("forecast_step" )
414+ fse = data .get ("forecast_end_hour" )
344415 lon = float (data .get ("lon" ))
345416 lat = float (data .get ("lat" ))
346417
347418 try :
348419 output = extract_wind_data (
349420 model ,
350421 mr ,
351- fh ,
422+ fsh ,
423+ fs ,
424+ fse ,
352425 lon ,
353426 lat ,
354427 )
0 commit comments