11import warnings
2+ import logging
23import pandas as pd
34import numpy as np
45import xarray as xr
56from xarray_data_accessor import utility_functions
67from xarray_data_accessor .shared_types import (
78 BoundingBoxDict ,
9+ TimeInput ,
810)
911from xarray_data_accessor .data_converters .base import (
1012 DataConverterBase ,
1113)
1214from xarray_data_accessor .data_converters .factory import (
1315 DataConversionFactory ,
1416)
17+ from xarray_data_accessor .info .gssha import (
18+ PrecipitationType ,
19+ HMETVariables ,
20+ )
1521from datetime import datetime
1622from pathlib import Path
1723from typing import (
1824 List ,
19- Dict ,
2025 Tuple ,
26+ Dict ,
2127 Union ,
22- Literal ,
2328 Optional ,
2429 TypedDict ,
2530)
2631
27- PrecipitationType = Literal [
28- 'GAGES' ,
29- 'RADAR' ,
30- 'RATES' ,
31- 'ACCUM' ,
32- ]
33-
34- HMETVariables = Literal [
35- 'Cloud Cover' ,
36- 'Atmospheric Pressure' ,
37- 'Relative Humidity' ,
38- 'Temperature' ,
39- 'Wind Speed' ,
40- 'Direct Radiation' ,
41- 'Global Radiation' ,
42- ]
43-
4432
4533class EventIntervals (TypedDict ):
46- """Stores information about a single event interval."""
47-
4834 name : str
4935 start : datetime
5036 end : datetime
5137
5238
53- @DataConversionFactory .register
39+ @ DataConversionFactory .register
5440class ConvertToGSSHA (DataConverterBase ):
5541 """Converts xarray datasets to GSSHA input files."""
5642
57- @staticmethod
43+ @ staticmethod
5844 def _get_file_path (
5945 file_dir : Optional [Union [str , Path ]] = None ,
6046 file_name : Optional [str ] = None ,
@@ -98,7 +84,7 @@ def _get_file_path(
9884 # return the file path
9985 return Path (file_dir / f'{ file_name } { file_suffix } ' )
10086
101- @staticmethod
87+ @ staticmethod
10288 def _write_ascii_file (
10389 text_content : str ,
10490 file_path : Path ,
@@ -129,14 +115,14 @@ def _write_ascii_file(
129115 f'Something went wrong - File { file_path } is not a valid ASCII file.'
130116 )
131117
132- @staticmethod
118+ @ staticmethod
133119 def _write_precip_coords (
134120 easting : pd .Series ,
135121 northing : pd .Series ,
136122 ) -> str :
137123 """Writes the coordinate lines of a precipitation ASCII file.
138124
139- NOTE: This function assumes that the easting and northing coordinates
125+ NOTE: This function assumes that the easting and northing coordinates
140126 are in the same projection and correspond to the same time step!
141127
142128 Arguments:
@@ -146,7 +132,7 @@ def _write_precip_coords(
146132 Returns: A string of the precipitation coordinates in ASCII format.
147133 Output format:
148134 NRGAG 2
149- COORD 204555.0 4751268.0 "Center of precipitation pixel #1"
135+ COORD 204555.0 4751268.0 "Center of precipitation pixel #1"
150136 COORD 205642.0 4750491.0 "Center of precipitation pixel #2"
151137 """
152138 # zip the coordinates
@@ -180,7 +166,7 @@ def make_gssha_precipitation_input(
180166 xarray_dataset: The xarray dataset to convert.
181167 precipitation_variable: The name of the precipitation variable.
182168 precipitation_type: The type of precipitation (i.e., GAGE, RADAR,...).
183- event_intervals: A list of event intervals. Each interval must be
169+ event_intervals: A list of event intervals. Each interval must be
184170 a dict of form {event_name: str, start: datetime, end: datetime}.
185171 file_dir: The directory to save the file to.
186172 file_name: The name of the file to save.
@@ -269,15 +255,17 @@ def make_gssha_precipitation_input(
269255 text_content = ascii_text ,
270256 file_path = file_path ,
271257 )
258+ logging .info (f'Precipitation ASCII file saved @ { file_path } .' )
272259 return file_path
273260
274- @classmethod
261+ @ classmethod
275262 def make_grass_ascii_input (
276263 cls ,
277264 xarray_dataset : xr .Dataset ,
278265 variable : str ,
279- hmet_variable : Optional [HMETVariables ] = None ,
280- file_name_code : Optional [str ] = None ,
266+ hmet_variable : Optional [str ] = None ,
267+ start_time : Optional [TimeInput ] = None ,
268+ end_time : Optional [TimeInput ] = None ,
281269 file_dir : Optional [Union [str , Path ]] = None ,
282270 file_suffix : Optional [str ] = None ,
283271 ) -> Path :
@@ -289,57 +277,99 @@ def make_grass_ascii_input(
289277 Arguments:
290278 xarray_dataset: The xarray dataset to convert.
291279 variable: The name of the variable to convert.
280+ hmet_variable: The name of the GSSHA HMET variable.
281+ See: https://www.gsshawiki.com/Continuous:Hydrometeorological_Data
282+ start_time: The start time to trim the data to.
283+ end_time: The end time to trim the data to.
292284 file_dir: The directory to save the file to.
293- file_name: The name of the file to save .
285+ NOTE: The file name is automatically generated .
294286 file_suffix: The file suffix to use.
295287
296288 Returns:
297- The path of the output GRASS ASCII input file .
289+ The path of the directory containing all GRASS ASCII input files .
298290 """
299- # get a file path
300- file_path : Path = cls ._get_file_path (
301- file_dir = file_dir ,
302- file_name = file_name ,
303- file_suffix = file_suffix ,
304- )
305291
306- # TODO: make this work over time https://www.gsshawiki.com/Continuous:Hydrometeorological_Data
307- # make header
308- ascii_text : str = ''
309- bbox : BoundingBoxDict = utility_functions ._bbox_from_raster (
310- raster = xarray_dataset ,
292+ # make sure we are using an appropriate variable name
293+ if variable not in xarray_dataset :
294+ raise KeyError (
295+ f'Variable { variable } not found in xarray dataset!' ,
296+ )
297+ if hmet_variable not in HMETVariables .keys ():
298+ raise KeyError (
299+ f'Variable { hmet_variable } not found in HMETVariables! '
300+ f'Please specify a valid HMET variable from list: { HMETVariables .keys ()} ' ,
301+ )
302+
303+ file_name : str = HMETVariables [hmet_variable ].ascii_file_name
304+
305+ # trim to time range if necessary
306+ if start_time or end_time :
307+ start_dt = utility_functions ._get_datetime (start_time )
308+ end_dt = utility_functions ._get_datetime (end_time )
309+ xarray_dataset = xarray_dataset .time .where (
310+ (xarray_dataset .time .dt >=
311+ start_dt and xarray_dataset .time .dt <= end_dt ),
312+ drop = True ,
313+ ).copy ()
314+
315+ # make GRASS ASCII header with bounds
316+ grass_header : str = ''
317+
318+ x_dim = xarray_dataset .attrs ['x_dim' ]
319+ y_dim = xarray_dataset .attrs ['y_dim' ]
320+
321+ bbox = BoundingBoxDict (
322+ north = np .max (xarray_dataset [y_dim ].values ),
323+ south = np .min (xarray_dataset [y_dim ].values ),
324+ east = np .max (xarray_dataset [x_dim ].values ),
325+ west = np .min (xarray_dataset [x_dim ].values ),
311326 )
327+
312328 for direction in ['north' , 'south' , 'east' , 'west' ]:
313- ascii_text += f'{ direction } : { bbox [direction ]} \n '
314- ascii_text += f'rows: { len (xarray_dataset .dims [xarray_dataset .attrs ["y_dim" ]].values )} \n '
315- ascii_text += f'rows: { len (xarray_dataset .dims [xarray_dataset .attrs ["x_dim" ]].values )} \n '
316-
317- # get the data as a string
318- data_str = (
319- np .array2string (
320- xarray_dataset [variable ].values ,
321- formatter = {'float' : lambda x : str (x )},
322- separator = ' ' ,
329+ grass_header += f'{ direction } : { bbox [direction ]} \n '
330+
331+ grass_header += f'rows: { len (xarray_dataset [y_dim ].values )} \n '
332+ grass_header += f'cols: { len (xarray_dataset [x_dim ].values )} \n '
333+
334+ # iterate over time steps
335+ for time in xarray_dataset .time .values :
336+ data_str = (
337+ np .array2string (
338+ xarray_dataset [variable ].sel (time = time ).values ,
339+ max_line_width = 100000000 ,
340+ formatter = {'float' : lambda x : str (x )},
341+ separator = ' ' ,
342+ )
343+ .replace (' ' , '' )
344+ .replace ('[' , '' )
345+ .replace (']' , '' )
346+ .replace ('\n ' , '\n ' )
323347 )
324- .replace (' ' , '' )
325- .replace ('[' , '' )
326- .replace (']' , '' )
327- )
328348
329- ascii_text += data_str
349+ ascii_text = grass_header + data_str
330350
331- # write the ASCII file
332- cls ._write_ascii_file (
333- text_content = ascii_text ,
334- file_path = file_path ,
335- )
336- return file_path
351+ # get a file path (YYYYMMDDHH_TYPE.asc format)
352+ timestamp : str = pd .to_datetime (time ).strftime ('%Y%m%d%H' )
353+ file_path : Path = cls ._get_file_path (
354+ file_dir = file_dir ,
355+ file_name = f'{ timestamp } _{ file_name } ' ,
356+ file_suffix = file_suffix ,
357+ )
337358
338- @classmethod
359+ # write the ASCII file
360+ cls ._write_ascii_file (
361+ text_content = ascii_text ,
362+ file_path = file_path ,
363+ )
364+ logging .info (f'GRASS ASCII files saved to { file_dir } .' )
365+ return file_path .parent
366+
367+ @ classmethod
339368 def get_conversion_functions (
340369 cls ,
341370 ) -> Dict [str , DataConverterBase .ConversionFunctionType ]:
342371 """Returns a dictionary of conversion functions."""
343372 return {
344373 cls .make_gssha_precipitation_input .__name__ : cls .make_gssha_precipitation_input ,
374+ cls .make_grass_ascii_input .__name__ : cls .make_grass_ascii_input ,
345375 }
0 commit comments