Skip to content

Commit ddbba8d

Browse files
HMET ASCII works
1 parent 908c1e8 commit ddbba8d

2 files changed

Lines changed: 97 additions & 66 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ examples/example_data/*.tif.ovr
1313
testing/test_data/*.csv
1414
testing/test_data/*.parquet
1515
testing/test_data/*.xlsx
16+
*.asc
1617
dev_sandbox/**
1718
lake_erie_data/elements_data/**
1819
lake_erie_data/nodes_data/**

src/xarray_data_accessor/data_converters/to_gssha.py

Lines changed: 96 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,46 @@
11
import warnings
2+
import logging
23
import pandas as pd
34
import numpy as np
45
import xarray as xr
56
from xarray_data_accessor import utility_functions
67
from xarray_data_accessor.shared_types import (
78
BoundingBoxDict,
9+
TimeInput,
810
)
911
from xarray_data_accessor.data_converters.base import (
1012
DataConverterBase,
1113
)
1214
from xarray_data_accessor.data_converters.factory import (
1315
DataConversionFactory,
1416
)
17+
from xarray_data_accessor.info.gssha import (
18+
PrecipitationType,
19+
HMETVariables,
20+
)
1521
from datetime import datetime
1622
from pathlib import Path
1723
from 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

4533
class 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
5440
class 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

Comments
 (0)