@@ -156,6 +156,7 @@ class RasPlanHdf(RasGeomHdf):
156
156
PLAN_INFO_PATH = "Plan Data/Plan Information"
157
157
PLAN_PARAMS_PATH = "Plan Data/Plan Parameters"
158
158
PRECIP_PATH = "Event Conditions/Meteorology/Precipitation"
159
+ OBS_DATA_PATH = "Event Conditions/Observed Data"
159
160
RESULTS_UNSTEADY_PATH = "Results/Unsteady"
160
161
RESULTS_UNSTEADY_SUMMARY_PATH = f"{ RESULTS_UNSTEADY_PATH } /Summary"
161
162
VOLUME_ACCOUNTING_PATH = f"{ RESULTS_UNSTEADY_PATH } /Volume Accounting"
@@ -166,6 +167,8 @@ class RasPlanHdf(RasGeomHdf):
166
167
UNSTEADY_TIME_SERIES_PATH = f"{ BASE_OUTPUT_PATH } /Unsteady Time Series"
167
168
REFERENCE_LINES_OUTPUT_PATH = f"{ UNSTEADY_TIME_SERIES_PATH } /Reference Lines"
168
169
REFERENCE_POINTS_OUTPUT_PATH = f"{ UNSTEADY_TIME_SERIES_PATH } /Reference Points"
170
+ OBS_FLOW_OUTPUT_PATH = f"{ OBS_DATA_PATH } /Flow"
171
+ OBS_STAGE_OUTPUT_PATH = f"{ OBS_DATA_PATH } /Stage"
169
172
170
173
RESULTS_STEADY_PATH = "Results/Steady"
171
174
BASE_STEADY_PATH = f"{ RESULTS_STEADY_PATH } /Output/Output Blocks/Base Output"
@@ -1117,6 +1120,106 @@ def reference_lines_timeseries_output(self) -> xr.Dataset:
1117
1120
"""
1118
1121
return self .reference_timeseries_output (reftype = "lines" )
1119
1122
1123
+ def observed_timeseries_output (self , vartype : str = "Flow" ) -> xr .Dataset :
1124
+ """Return observed timeseries output data for reference lines and points from a HEC-RAS HDF plan file.
1125
+
1126
+ Parameters
1127
+ ----------
1128
+
1129
+ Returns
1130
+ -------
1131
+ xr.Dataset
1132
+ An xarray Dataset with reference line timeseries data.
1133
+ """
1134
+
1135
+ # Decode the contents of the DataFrame from utf-8
1136
+ def decode_bytes (val ):
1137
+ if isinstance (val , bytes ):
1138
+ return val .decode ('utf-8' )
1139
+ return val
1140
+
1141
+ # Function to adjust invalid hour values
1142
+ def adjust_invalid_hour (date_str ):
1143
+ if '24:00:00' in date_str :
1144
+ # Split the date and time parts
1145
+ date_part , time_part = date_str .split ()
1146
+ # Replace '24:00:00' with '00:00:00'
1147
+ new_time_part = '00:00:00'
1148
+ # Convert the date part to a datetime object and add one day
1149
+ new_date_part = (pd .to_datetime (date_part , format = '%d%b%Y' ) + pd .Timedelta (days = 1 )).strftime ('%d%b%Y' )
1150
+ # Combine the new date and time parts
1151
+ return f'{ new_date_part } { new_time_part } '
1152
+ return date_str
1153
+
1154
+ if vartype == "Flow" :
1155
+ output_path = self .OBS_FLOW_OUTPUT_PATH
1156
+ elif vartype == "Stage" :
1157
+ output_path = self .OBS_STAGE_OUTPUT_PATH
1158
+ else :
1159
+ raise ValueError ('vartype must be either "Flow" or "Stage".' )
1160
+
1161
+ observed_group = self .get (output_path )
1162
+ if observed_group is None :
1163
+ raise RasPlanHdfError (
1164
+ f"Could not find HDF group at path '{ output_path } '."
1165
+ f" Does the Plan HDF file contain reference { vartype [:- 1 ]} output data?"
1166
+ )
1167
+
1168
+ for var in observed_group .keys ():
1169
+ var_path = observed_group [var ]
1170
+ var_keys = var_path .keys ()
1171
+ if 'Attributes' in var_keys :
1172
+ attrs_df = pd .DataFrame (var_path ['Attributes' ][:])
1173
+ # Apply the decoding function to each element in the DataFrame
1174
+ attrs_df = attrs_df .map (decode_bytes )
1175
+ if var == 'Flow' :
1176
+ attrs_df ['Units' ] = 'cfs'
1177
+ elif var == 'Stage' :
1178
+ attrs_df ['Units' ] = 'ft'
1179
+ else :
1180
+ attrs_df ['Units' ] = 'Unknown'
1181
+ for site in var_keys :
1182
+ if site != 'Attributes' :
1183
+ # Site Ex: 'Ref Point: Grapevine_Lake_RP'
1184
+ prefix = site .split (":" )[0 ]
1185
+ suffix = site .split (":" )[1 ][1 :]
1186
+ data_df = pd .DataFrame (var_path [site ][:])
1187
+ # Apply the decoding function to each element in the DataFrame
1188
+ data_df = data_df .map (decode_bytes )
1189
+ # Assign data types to the columns
1190
+ data_df ['Date' ] = data_df ['Date' ].apply (adjust_invalid_hour )
1191
+ data_df ['Date' ] = pd .to_datetime (data_df ['Date' ], format = '%d%b%Y %H:%M:%S' )
1192
+ data_df ['Value' ] = data_df ['Value' ].astype (float )
1193
+ # Determine the site type
1194
+ site_type = 'reference_line' if 'Ref Line' in site else 'reference_point'
1195
+ # Package into an xarray DataArray
1196
+ da = xr .DataArray (
1197
+ data_df ['Value' ].values ,
1198
+ name = suffix ,
1199
+ dims = ["time" ],
1200
+ coords = {
1201
+ "time" : data_df ['Date' ].values ,
1202
+ },
1203
+ attrs = {"units" : attrs_df ['Units' ][0 ], "hdf_path" : f"{ output_path } /{ var } /{ site } " }
1204
+ )
1205
+ da_list .append (da .to_dataset (name = var ))
1206
+ site_list .append (suffix )
1207
+
1208
+ # Combine into an xarray dataset with 'site' as a dimension
1209
+ ds = xr .concat (da_list , dim = pd .Index (site_list , name = 'site' ))
1210
+
1211
+ return ds
1212
+
1213
+ def observed_data_timeseries_output (self ) -> xr .Dataset :
1214
+ """Return observed data timeseries output data for reference lines or points from a HEC-RAS HDF plan file.
1215
+
1216
+ Returns
1217
+ -------
1218
+ xr.Dataset
1219
+ An xarray Dataset with observed timeseries output data for reference lines or points.
1220
+ """
1221
+ return self .observed_timeseries_output (vartype = "Flow" )
1222
+
1120
1223
def reference_points_timeseries_output (self ) -> xr .Dataset :
1121
1224
"""Return timeseries output data for reference points from a HEC-RAS HDF plan file.
1122
1225
@@ -1279,6 +1382,16 @@ def get_meteorology_precip_attrs(self) -> Dict:
1279
1382
"""
1280
1383
return self .get_attrs (self .PRECIP_PATH )
1281
1384
1385
+ def get_obs_data_attrs (self ) -> Dict :
1386
+ """Return observed data attributes from a HEC-RAS HDF plan file.
1387
+
1388
+ Returns
1389
+ -------
1390
+ dict
1391
+ Dictionary of observed data attributes.
1392
+ """
1393
+ return self .get_attrs (self .OBS_DATA_PATH )
1394
+
1282
1395
def get_results_unsteady_attrs (self ) -> Dict :
1283
1396
"""Return unsteady attributes from a HEC-RAS HDF plan file.
1284
1397
0 commit comments