1
1
"""HEC-RAS Plan HDF class."""
2
2
3
3
from .geom import RasGeomHdf
4
- from .utils import df_datetimes_to_str , ras_timesteps_to_datetimes
4
+ from .utils import (
5
+ df_datetimes_to_str ,
6
+ ras_timesteps_to_datetimes ,
7
+ parse_ras_datetime_ms ,
8
+ )
5
9
6
10
from geopandas import GeoDataFrame
7
11
import h5py
8
12
import numpy as np
9
13
from pandas import DataFrame
10
14
import pandas as pd
15
+ import xarray as xr
11
16
12
17
from datetime import datetime
13
18
from enum import Enum
@@ -46,6 +51,84 @@ class SummaryOutputVar(Enum):
46
51
]
47
52
48
53
54
+ class TimeSeriesOutputVar (Enum ):
55
+ """Time series output variables."""
56
+
57
+ # Default Outputs
58
+ WATER_SURFACE = "Water Surface"
59
+ FACE_VELOCITY = "Face Velocity"
60
+
61
+ # Optional Outputs
62
+ CELL_COURANT = "Cell Courant"
63
+ CELL_CUMULATIVE_PRECIPITATION_DEPTH = "Cell Cumulative Precipitation Depth"
64
+ CELL_DIVERGENCE_TERM = "Cell Divergence Term"
65
+ CELL_EDDY_VISCOSITY_X = "Cell Eddy Viscosity - Eddy Viscosity X"
66
+ CELL_EDDY_VISCOSITY_Y = "Cell Eddy Viscosity - Eddy Viscosity Y"
67
+ CELL_FLOW_BALANCE = "Cell Flow Balance"
68
+ CELL_HYDRAULIC_DEPTH = "Cell Hydraulic Depth"
69
+ CELL_INVERT_DEPTH = "Cell Invert Depth"
70
+ CELL_STORAGE_TERM = "Cell Storage Term"
71
+ CELL_VELOCITY_X = "Cell Velocity - Velocity X"
72
+ CELL_VELOCITY_Y = "Cell Velocity - Velocity Y"
73
+ CELL_VOLUME = "Cell Volume"
74
+ CELL_VOLUME_ERROR = "Cell Volume Error"
75
+ CELL_WATER_SOURCE_TERM = "Cell Water Source Term"
76
+ CELL_WATER_SURFACE_ERROR = "Cell Water Surface Error"
77
+
78
+ FACE_COURANT = "Face Courant"
79
+ FACE_CUMULATIVE_VOLUME = "Face Cumulative Volume"
80
+ FACE_EDDY_VISCOSITY = "Face Eddy Viscosity"
81
+ FACE_FLOW = "Face Flow"
82
+ FACE_FLOW_PERIOD_AVERAGE = "Face Flow Period Average"
83
+ FACE_FRICTION_TERM = "Face Friction Term"
84
+ FACE_PRESSURE_GRADIENT_TERM = "Face Pressure Gradient Term"
85
+ FACE_SHEAR_STRESS = "Face Shear Stress"
86
+ FACE_TANGENTIAL_VELOCITY = "Face Tangential Velocity"
87
+ FACE_WATER_SURFACE = "Face Water Surface"
88
+ FACE_WIND_TERM = "Face Wind Term"
89
+
90
+
91
+ TIME_SERIES_OUTPUT_VARS_CELLS = [
92
+ TimeSeriesOutputVar .WATER_SURFACE ,
93
+ TimeSeriesOutputVar .CELL_COURANT ,
94
+ TimeSeriesOutputVar .CELL_CUMULATIVE_PRECIPITATION_DEPTH ,
95
+ TimeSeriesOutputVar .CELL_DIVERGENCE_TERM ,
96
+ TimeSeriesOutputVar .CELL_EDDY_VISCOSITY_X ,
97
+ TimeSeriesOutputVar .CELL_EDDY_VISCOSITY_Y ,
98
+ TimeSeriesOutputVar .CELL_FLOW_BALANCE ,
99
+ TimeSeriesOutputVar .CELL_HYDRAULIC_DEPTH ,
100
+ TimeSeriesOutputVar .CELL_INVERT_DEPTH ,
101
+ TimeSeriesOutputVar .CELL_STORAGE_TERM ,
102
+ TimeSeriesOutputVar .CELL_VELOCITY_X ,
103
+ TimeSeriesOutputVar .CELL_VELOCITY_Y ,
104
+ TimeSeriesOutputVar .CELL_VOLUME ,
105
+ TimeSeriesOutputVar .CELL_VOLUME_ERROR ,
106
+ TimeSeriesOutputVar .CELL_WATER_SOURCE_TERM ,
107
+ TimeSeriesOutputVar .CELL_WATER_SURFACE_ERROR ,
108
+ ]
109
+
110
+ TIME_SERIES_OUTPUT_VARS_FACES = [
111
+ TimeSeriesOutputVar .FACE_COURANT ,
112
+ TimeSeriesOutputVar .FACE_CUMULATIVE_VOLUME ,
113
+ TimeSeriesOutputVar .FACE_EDDY_VISCOSITY ,
114
+ TimeSeriesOutputVar .FACE_FLOW ,
115
+ TimeSeriesOutputVar .FACE_FLOW_PERIOD_AVERAGE ,
116
+ TimeSeriesOutputVar .FACE_FRICTION_TERM ,
117
+ TimeSeriesOutputVar .FACE_PRESSURE_GRADIENT_TERM ,
118
+ TimeSeriesOutputVar .FACE_SHEAR_STRESS ,
119
+ TimeSeriesOutputVar .FACE_TANGENTIAL_VELOCITY ,
120
+ TimeSeriesOutputVar .FACE_VELOCITY ,
121
+ TimeSeriesOutputVar .FACE_WATER_SURFACE ,
122
+ # TODO: investigate why "Face Wind Term" data gets written as a 1D array
123
+ # TimeSeriesOutputVar.FACE_WIND_TERM,
124
+ ]
125
+
126
+ TIME_SERIES_OUTPUT_VARS_DEFAULT = [
127
+ TimeSeriesOutputVar .WATER_SURFACE ,
128
+ TimeSeriesOutputVar .FACE_VELOCITY ,
129
+ ]
130
+
131
+
49
132
class RasPlanHdf (RasGeomHdf ):
50
133
"""HEC-RAS Plan HDF class."""
51
134
@@ -55,7 +138,11 @@ class RasPlanHdf(RasGeomHdf):
55
138
RESULTS_UNSTEADY_PATH = "Results/Unsteady"
56
139
RESULTS_UNSTEADY_SUMMARY_PATH = f"{ RESULTS_UNSTEADY_PATH } /Summary"
57
140
VOLUME_ACCOUNTING_PATH = f"{ RESULTS_UNSTEADY_PATH } /Volume Accounting"
58
- SUMMARY_OUTPUT_2D_FLOW_AREAS_PATH = f"{ RESULTS_UNSTEADY_PATH } /Output/Output Blocks/Base Output/Summary Output/2D Flow Areas"
141
+ BASE_OUTPUT_PATH = f"{ RESULTS_UNSTEADY_PATH } /Output/Output Blocks/Base Output"
142
+ SUMMARY_OUTPUT_2D_FLOW_AREAS_PATH = (
143
+ f"{ BASE_OUTPUT_PATH } /Summary Output/2D Flow Areas"
144
+ )
145
+ UNSTEADY_TIME_SERIES_PATH = f"{ BASE_OUTPUT_PATH } /Unsteady Time Series"
59
146
60
147
def __init__ (self , name : str , ** kwargs ):
61
148
"""Open a HEC-RAS Plan HDF file.
@@ -679,6 +766,135 @@ def mesh_cell_faces(
679
766
datetime_to_str = datetime_to_str ,
680
767
)
681
768
769
+ def unsteady_datetimes (self ) -> List [datetime ]:
770
+ """Return the unsteady timeseries datetimes from the plan file.
771
+
772
+ Returns
773
+ -------
774
+ List[datetime]
775
+ A list of datetimes for the unsteady timeseries data.
776
+ """
777
+ group_path = f"{ self .UNSTEADY_TIME_SERIES_PATH } /Time Date Stamp (ms)"
778
+ raw_datetimes = self [group_path ][:]
779
+ dt = [parse_ras_datetime_ms (x .decode ("utf-8" )) for x in raw_datetimes ]
780
+ return dt
781
+
782
+ def _mesh_timeseries_output_values_units (
783
+ self ,
784
+ mesh_name : str ,
785
+ var : TimeSeriesOutputVar ,
786
+ ) -> Tuple [np .ndarray , str ]:
787
+ path = f"{ self .UNSTEADY_TIME_SERIES_PATH } /2D Flow Areas/{ mesh_name } /{ var .value } "
788
+ group = self .get (path )
789
+ try :
790
+ import dask .array as da
791
+
792
+ # TODO: user-specified chunks?
793
+ values = da .from_array (group , chunks = group .chunks )
794
+ except ImportError :
795
+ values = group [:]
796
+ units = group .attrs .get ("Units" )
797
+ if units is not None :
798
+ units = units .decode ("utf-8" )
799
+ return values , units
800
+
801
+ def mesh_timeseries_output (
802
+ self ,
803
+ mesh_name : str ,
804
+ var : Union [str , TimeSeriesOutputVar ],
805
+ ) -> xr .DataArray :
806
+ """Return the time series output data for a given variable.
807
+
808
+ Parameters
809
+ ----------
810
+ mesh_name : str
811
+ The name of the 2D flow area mesh.
812
+ var : TimeSeriesOutputVar
813
+ The time series output variable to retrieve.
814
+
815
+ Returns
816
+ -------
817
+ xr.DataArray
818
+ An xarray DataArray with dimensions 'time' and 'cell_id'.
819
+ """
820
+ times = self .unsteady_datetimes ()
821
+ mesh_names_counts = {
822
+ name : count for name , count in self ._2d_flow_area_names_and_counts ()
823
+ }
824
+ if mesh_name not in mesh_names_counts :
825
+ raise ValueError (f"Mesh '{ mesh_name } ' not found in the Plan HDF file." )
826
+ if isinstance (var , str ):
827
+ var = TimeSeriesOutputVar (var )
828
+ values , units = self ._mesh_timeseries_output_values_units (mesh_name , var )
829
+ if var in TIME_SERIES_OUTPUT_VARS_CELLS :
830
+ cell_count = mesh_names_counts [mesh_name ]
831
+ values = values [:, :cell_count ]
832
+ id_coord = "cell_id"
833
+ elif var in TIME_SERIES_OUTPUT_VARS_FACES :
834
+ id_coord = "face_id"
835
+ else :
836
+ raise ValueError (f"Invalid time series output variable: { var .value } " )
837
+ da = xr .DataArray (
838
+ values ,
839
+ name = var .value ,
840
+ dims = ["time" , id_coord ],
841
+ coords = {
842
+ "time" : times ,
843
+ id_coord : range (values .shape [1 ]),
844
+ },
845
+ attrs = {
846
+ "mesh_name" : mesh_name ,
847
+ "variable" : var .value ,
848
+ "units" : units ,
849
+ },
850
+ )
851
+ return da
852
+
853
+ def _mesh_timeseries_outputs (
854
+ self , mesh_name : str , vars : List [TimeSeriesOutputVar ]
855
+ ) -> xr .Dataset :
856
+ datasets = {}
857
+ for var in vars :
858
+ var_path = f"{ self .UNSTEADY_TIME_SERIES_PATH } /2D Flow Areas/{ mesh_name } /{ var .value } "
859
+ if self .get (var_path ) is None :
860
+ continue
861
+ da = self .mesh_timeseries_output (mesh_name , var )
862
+ datasets [var .value ] = da
863
+ ds = xr .Dataset (datasets , attrs = {"mesh_name" : mesh_name })
864
+ return ds
865
+
866
+ def mesh_timeseries_output_cells (self , mesh_name : str ) -> xr .Dataset :
867
+ """Return the time series output data for cells in a 2D flow area mesh.
868
+
869
+ Parameters
870
+ ----------
871
+ mesh_name : str
872
+ The name of the 2D flow area mesh.
873
+
874
+ Returns
875
+ -------
876
+ xr.Dataset
877
+ An xarray Dataset with DataArrays for each time series output variable.
878
+ """
879
+ ds = self ._mesh_timeseries_outputs (mesh_name , TIME_SERIES_OUTPUT_VARS_CELLS )
880
+ return ds
881
+
882
+ def mesh_timeseries_output_faces (self , mesh_name : str ) -> xr .Dataset :
883
+ """Return the time series output data for faces in a 2D flow area mesh.
884
+
885
+ Parameters
886
+ ----------
887
+ mesh_name : str
888
+ The name of the 2D flow area mesh.
889
+
890
+ Returns
891
+ -------
892
+ xr.Dataset
893
+ An xarray Dataset with DataArrays for each time series output variable.
894
+ """
895
+ ds = self ._mesh_timeseries_outputs (mesh_name , TIME_SERIES_OUTPUT_VARS_FACES )
896
+ return ds
897
+
682
898
def get_plan_info_attrs (self ) -> Dict :
683
899
"""Return plan information attributes from a HEC-RAS HDF plan file.
684
900
0 commit comments