@@ -156,6 +156,7 @@ class RasPlanHdf(RasGeomHdf):
156156 PLAN_INFO_PATH = "Plan Data/Plan Information"
157157 PLAN_PARAMS_PATH = "Plan Data/Plan Parameters"
158158 PRECIP_PATH = "Event Conditions/Meteorology/Precipitation"
159+ OBS_DATA_PATH = "Event Conditions/Observed Data"
159160 RESULTS_UNSTEADY_PATH = "Results/Unsteady"
160161 RESULTS_UNSTEADY_SUMMARY_PATH = f"{ RESULTS_UNSTEADY_PATH } /Summary"
161162 VOLUME_ACCOUNTING_PATH = f"{ RESULTS_UNSTEADY_PATH } /Volume Accounting"
@@ -166,6 +167,8 @@ class RasPlanHdf(RasGeomHdf):
166167 UNSTEADY_TIME_SERIES_PATH = f"{ BASE_OUTPUT_PATH } /Unsteady Time Series"
167168 REFERENCE_LINES_OUTPUT_PATH = f"{ UNSTEADY_TIME_SERIES_PATH } /Reference Lines"
168169 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"
169172
170173 RESULTS_STEADY_PATH = "Results/Steady"
171174 BASE_STEADY_PATH = f"{ RESULTS_STEADY_PATH } /Output/Output Blocks/Base Output"
@@ -1117,6 +1120,106 @@ def reference_lines_timeseries_output(self) -> xr.Dataset:
11171120 """
11181121 return self .reference_timeseries_output (reftype = "lines" )
11191122
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+
11201223 def reference_points_timeseries_output (self ) -> xr .Dataset :
11211224 """Return timeseries output data for reference points from a HEC-RAS HDF plan file.
11221225
@@ -1279,6 +1382,16 @@ def get_meteorology_precip_attrs(self) -> Dict:
12791382 """
12801383 return self .get_attrs (self .PRECIP_PATH )
12811384
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+
12821395 def get_results_unsteady_attrs (self ) -> Dict :
12831396 """Return unsteady attributes from a HEC-RAS HDF plan file.
12841397
0 commit comments