ras-commander 0.44.0__py3-none-any.whl → 0.46.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ras_commander/HdfFluvialPluvial.py +317 -0
- ras_commander/HdfMesh.py +62 -15
- ras_commander/HdfPipe.py +771 -0
- ras_commander/HdfPlan.py +5 -0
- ras_commander/HdfPump.py +269 -0
- ras_commander/HdfResultsMesh.py +135 -62
- ras_commander/HdfResultsPlan.py +3 -0
- ras_commander/HdfResultsXsec.py +192 -157
- ras_commander/HdfStruc.py +148 -50
- ras_commander/HdfUtils.py +51 -0
- ras_commander/HdfXsec.py +467 -136
- ras_commander/RasPlan.py +298 -45
- ras_commander/RasToGo.py +21 -0
- ras_commander/RasUnsteady.py +615 -14
- ras_commander/__init__.py +7 -1
- {ras_commander-0.44.0.dist-info → ras_commander-0.46.0.dist-info}/METADATA +1 -1
- ras_commander-0.46.0.dist-info/RECORD +30 -0
- {ras_commander-0.44.0.dist-info → ras_commander-0.46.0.dist-info}/WHEEL +1 -1
- ras_commander-0.44.0.dist-info/RECORD +0 -26
- {ras_commander-0.44.0.dist-info → ras_commander-0.46.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.44.0.dist-info → ras_commander-0.46.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfResultsXsec.py
CHANGED
@@ -8,20 +8,21 @@ released under MIT license and Copyright (c) 2024 fema-ffrd
|
|
8
8
|
The file has been forked and modified for use in RAS Commander.
|
9
9
|
"""
|
10
10
|
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import Union, Optional, List, Dict, Tuple
|
13
|
+
|
11
14
|
import h5py
|
12
15
|
import numpy as np
|
13
16
|
import pandas as pd
|
14
|
-
|
15
|
-
|
17
|
+
import xarray as xr
|
18
|
+
|
16
19
|
from .HdfBase import HdfBase
|
17
20
|
from .HdfUtils import HdfUtils
|
18
21
|
from .Decorators import standardize_input, log_call
|
19
|
-
from .LoggingConfig import
|
20
|
-
import xarray as xr
|
22
|
+
from .LoggingConfig import get_logger
|
21
23
|
|
22
24
|
logger = get_logger(__name__)
|
23
25
|
|
24
|
-
|
25
26
|
class HdfResultsXsec:
|
26
27
|
"""
|
27
28
|
A class for handling cross-section results from HEC-RAS HDF files.
|
@@ -36,202 +37,236 @@ class HdfResultsXsec:
|
|
36
37
|
Attributes:
|
37
38
|
None
|
38
39
|
|
39
|
-
|
40
|
-
steady_profile_xs_output: Extract steady profile cross-section output for a specified variable.
|
41
|
-
cross_sections_wsel: Get water surface elevation data for cross-sections.
|
42
|
-
cross_sections_flow: Get flow data for cross-sections.
|
43
|
-
cross_sections_energy_grade: Get energy grade data for cross-sections.
|
44
|
-
cross_sections_additional_enc_station_left: Get left encroachment station data for cross-sections.
|
45
|
-
cross_sections_additional_enc_station_right: Get right encroachment station data for cross-sections.
|
46
|
-
cross_sections_additional_area_total: Get total ineffective area data for cross-sections.
|
47
|
-
cross_sections_additional_velocity_total: Get total velocity data for cross-sections.
|
40
|
+
|
48
41
|
"""
|
49
42
|
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
50
54
|
@staticmethod
|
55
|
+
@log_call
|
51
56
|
@standardize_input(file_type='plan_hdf')
|
52
|
-
def
|
57
|
+
def get_pump_station_profile_output(hdf_path: Path) -> pd.DataFrame:
|
53
58
|
"""
|
54
|
-
|
59
|
+
Extract pump station profile output data from the HDF file.
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
hdf_path : Path
|
59
|
-
Path to the HEC-RAS plan HDF file.
|
60
|
-
var : str
|
61
|
-
The variable to extract from the steady cross section results.
|
62
|
-
round_to : int, optional
|
63
|
-
Number of decimal places to round the results to (default is 2).
|
61
|
+
Args:
|
62
|
+
hdf_path (Path): Path to the HDF file.
|
64
63
|
|
65
64
|
Returns:
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
pd.DataFrame: DataFrame containing pump station profile output data.
|
66
|
+
|
67
|
+
Raises:
|
68
|
+
KeyError: If the required datasets are not found in the HDF file.
|
69
69
|
"""
|
70
|
-
XS_STEADY_OUTPUT_ADDITIONAL = [
|
71
|
-
"Additional Encroachment Station Left",
|
72
|
-
"Additional Encroachment Station Right",
|
73
|
-
"Additional Area Ineffective Total",
|
74
|
-
"Additional Velocity Total",
|
75
|
-
]
|
76
|
-
|
77
70
|
try:
|
78
|
-
with h5py.File(hdf_path, 'r') as
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
path = f"/Results/Steady/Cross Sections/{var}"
|
84
|
-
|
85
|
-
# Check if the path exists in the HDF file
|
86
|
-
if path not in hdf_file:
|
71
|
+
with h5py.File(hdf_path, 'r') as hdf:
|
72
|
+
# Extract profile output data
|
73
|
+
profile_path = "/Results/Unsteady/Output/Output Blocks/DSS Profile Output/Unsteady Time Series/Pumping Stations"
|
74
|
+
if profile_path not in hdf:
|
75
|
+
logger.warning("Pump Station profile output data not found in HDF file")
|
87
76
|
return pd.DataFrame()
|
88
77
|
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
78
|
+
# Initialize an empty list to store data from all pump stations
|
79
|
+
all_data = []
|
80
|
+
|
81
|
+
# Iterate through all pump stations
|
82
|
+
for station in hdf[profile_path].keys():
|
83
|
+
station_path = f"{profile_path}/{station}/Structure Variables"
|
84
|
+
|
85
|
+
data = hdf[station_path][()]
|
86
|
+
|
87
|
+
# Create a DataFrame for this pump station
|
88
|
+
df = pd.DataFrame(data, columns=['Flow', 'Stage HW', 'Stage TW', 'Pump Station', 'Pumps on'])
|
89
|
+
df['Station'] = station
|
90
|
+
|
91
|
+
all_data.append(df)
|
92
|
+
|
93
|
+
# Concatenate all DataFrames
|
94
|
+
result_df = pd.concat(all_data, ignore_index=True)
|
95
|
+
|
96
|
+
# Add time information
|
97
|
+
time = HdfBase._get_unsteady_datetimes(hdf)
|
98
|
+
result_df['Time'] = [time[i] for i in result_df.index]
|
99
|
+
|
100
|
+
return result_df
|
101
|
+
|
102
|
+
except KeyError as e:
|
103
|
+
logger.error(f"Required dataset not found in HDF file: {e}")
|
104
|
+
raise
|
104
105
|
except Exception as e:
|
105
|
-
|
106
|
-
|
106
|
+
logger.error(f"Error extracting pump station profile output data: {e}")
|
107
|
+
raise
|
108
|
+
|
107
109
|
|
108
|
-
@staticmethod
|
109
|
-
@standardize_input(file_type='plan_hdf')
|
110
|
-
def cross_sections_wsel(hdf_path: Path) -> pd.DataFrame:
|
111
|
-
"""
|
112
|
-
Return the water surface elevation information for each 1D Cross Section.
|
113
110
|
|
114
|
-
Parameters:
|
115
|
-
----------
|
116
|
-
hdf_path : Path
|
117
|
-
Path to the HEC-RAS plan HDF file.
|
118
111
|
|
119
|
-
Returns:
|
120
|
-
-------
|
121
|
-
pd.DataFrame
|
122
|
-
A DataFrame containing the water surface elevations for each cross section and event.
|
123
|
-
"""
|
124
|
-
return HdfResultsXsec.steady_profile_xs_output(hdf_path, "Water Surface")
|
125
112
|
|
126
|
-
@staticmethod
|
127
|
-
@standardize_input(file_type='plan_hdf')
|
128
|
-
def cross_sections_flow(hdf_path: Path) -> pd.DataFrame:
|
129
|
-
"""
|
130
|
-
Return the Flow information for each 1D Cross Section.
|
131
113
|
|
132
|
-
Parameters:
|
133
|
-
----------
|
134
|
-
hdf_path : Path
|
135
|
-
Path to the HEC-RAS plan HDF file.
|
136
114
|
|
137
|
-
Returns:
|
138
|
-
-------
|
139
|
-
pd.DataFrame
|
140
|
-
A DataFrame containing the flow for each cross section and event.
|
141
|
-
"""
|
142
|
-
return HdfResultsXsec.steady_profile_xs_output(hdf_path, "Flow")
|
143
115
|
|
144
|
-
@staticmethod
|
145
|
-
@standardize_input(file_type='plan_hdf')
|
146
|
-
def cross_sections_energy_grade(hdf_path: Path) -> pd.DataFrame:
|
147
|
-
"""
|
148
|
-
Return the energy grade information for each 1D Cross Section.
|
149
116
|
|
150
|
-
Parameters:
|
151
|
-
----------
|
152
|
-
hdf_path : Path
|
153
|
-
Path to the HEC-RAS plan HDF file.
|
154
117
|
|
155
|
-
Returns:
|
156
|
-
-------
|
157
|
-
pd.DataFrame
|
158
|
-
A DataFrame containing the energy grade for each cross section and event.
|
159
|
-
"""
|
160
|
-
return HdfResultsXsec.steady_profile_xs_output(hdf_path, "Energy Grade")
|
161
118
|
|
162
|
-
@staticmethod
|
163
|
-
@standardize_input(file_type='plan_hdf')
|
164
|
-
def cross_sections_additional_enc_station_left(hdf_path: Path) -> pd.DataFrame:
|
165
|
-
"""
|
166
|
-
Return the left side encroachment information for a floodway plan hdf.
|
167
119
|
|
168
|
-
Parameters:
|
169
|
-
----------
|
170
|
-
hdf_path : Path
|
171
|
-
Path to the HEC-RAS plan HDF file.
|
172
120
|
|
173
|
-
Returns:
|
174
|
-
-------
|
175
|
-
pd.DataFrame
|
176
|
-
A DataFrame containing the cross sections left side encroachment stations.
|
177
|
-
"""
|
178
|
-
return HdfResultsXsec.steady_profile_xs_output(
|
179
|
-
hdf_path, "Encroachment Station Left"
|
180
|
-
)
|
181
121
|
|
182
|
-
@staticmethod
|
183
|
-
@standardize_input(file_type='plan_hdf')
|
184
|
-
def cross_sections_additional_enc_station_right(hdf_path: Path) -> pd.DataFrame:
|
185
|
-
"""
|
186
|
-
Return the right side encroachment information for a floodway plan hdf.
|
187
122
|
|
188
|
-
Parameters:
|
189
|
-
----------
|
190
|
-
hdf_path : Path
|
191
|
-
Path to the HEC-RAS plan HDF file.
|
192
123
|
|
193
|
-
Returns:
|
194
|
-
-------
|
195
|
-
pd.DataFrame
|
196
|
-
A DataFrame containing the cross sections right side encroachment stations.
|
197
|
-
"""
|
198
|
-
return HdfResultsXsec.steady_profile_xs_output(
|
199
|
-
hdf_path, "Encroachment Station Right"
|
200
|
-
)
|
201
124
|
|
202
125
|
@staticmethod
|
126
|
+
@log_call
|
203
127
|
@standardize_input(file_type='plan_hdf')
|
204
|
-
def
|
128
|
+
def get_pump_station_summary(hdf_path: Path) -> pd.DataFrame:
|
205
129
|
"""
|
206
|
-
|
130
|
+
Extract summary data for pump stations from the HDF file.
|
207
131
|
|
208
|
-
|
209
|
-
|
210
|
-
hdf_path : Path
|
211
|
-
Path to the HEC-RAS plan HDF file.
|
132
|
+
Args:
|
133
|
+
hdf_path (Path): Path to the HDF file.
|
212
134
|
|
213
135
|
Returns:
|
214
|
-
|
215
|
-
|
216
|
-
|
136
|
+
pd.DataFrame: DataFrame containing pump station summary data.
|
137
|
+
|
138
|
+
Raises:
|
139
|
+
KeyError: If the required datasets are not found in the HDF file.
|
217
140
|
"""
|
218
|
-
|
141
|
+
try:
|
142
|
+
with h5py.File(hdf_path, 'r') as hdf:
|
143
|
+
# Extract summary data
|
144
|
+
summary_path = "/Results/Unsteady/Summary/Pump Station"
|
145
|
+
if summary_path not in hdf:
|
146
|
+
logger.warning("Pump Station summary data not found in HDF file")
|
147
|
+
return pd.DataFrame()
|
148
|
+
|
149
|
+
summary_data = hdf[summary_path][()]
|
150
|
+
|
151
|
+
# Create DataFrame
|
152
|
+
df = pd.DataFrame(summary_data)
|
153
|
+
|
154
|
+
# Convert column names
|
155
|
+
df.columns = [col.decode('utf-8') if isinstance(col, bytes) else col for col in df.columns]
|
156
|
+
|
157
|
+
# Convert byte string values to regular strings
|
158
|
+
for col in df.columns:
|
159
|
+
if df[col].dtype == object:
|
160
|
+
df[col] = df[col].apply(lambda x: x.decode('utf-8') if isinstance(x, bytes) else x)
|
161
|
+
|
162
|
+
return df
|
163
|
+
|
164
|
+
except KeyError as e:
|
165
|
+
logger.error(f"Required dataset not found in HDF file: {e}")
|
166
|
+
raise
|
167
|
+
except Exception as e:
|
168
|
+
logger.error(f"Error extracting pump station summary data: {e}")
|
169
|
+
raise
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
# Tested functions from AWS webinar where the code was developed
|
175
|
+
# Need to add examples
|
176
|
+
|
219
177
|
|
220
178
|
@staticmethod
|
179
|
+
@log_call
|
221
180
|
@standardize_input(file_type='plan_hdf')
|
222
|
-
def
|
181
|
+
def extract_cross_section_results(hdf_path: Path) -> xr.Dataset:
|
223
182
|
"""
|
224
|
-
|
183
|
+
Extract Water Surface, Velocity Total, Velocity Channel, Flow Lateral, and Flow data from HEC-RAS HDF file.
|
184
|
+
Includes Cross Section Only and Cross Section Attributes as coordinates in the xarray.Dataset.
|
185
|
+
Also calculates maximum values for key parameters.
|
225
186
|
|
226
187
|
Parameters:
|
227
|
-
|
188
|
+
-----------
|
228
189
|
hdf_path : Path
|
229
|
-
Path to the HEC-RAS
|
190
|
+
Path to the HEC-RAS results HDF file
|
230
191
|
|
231
192
|
Returns:
|
232
|
-
|
233
|
-
|
234
|
-
|
193
|
+
--------
|
194
|
+
xr.Dataset
|
195
|
+
Xarray Dataset containing the extracted cross-section results with appropriate coordinates and attributes.
|
196
|
+
Includes maximum values for Water Surface, Flow, Channel Velocity, Total Velocity, and Lateral Flow.
|
235
197
|
"""
|
236
|
-
|
198
|
+
try:
|
199
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
200
|
+
# Define base paths
|
201
|
+
base_output_path = "/Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/Cross Sections/"
|
202
|
+
time_stamp_path = "/Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/Time Date Stamp (ms)"
|
203
|
+
|
204
|
+
# Extract Cross Section Attributes
|
205
|
+
attrs_dataset = hdf_file[f"{base_output_path}Cross Section Attributes"][:]
|
206
|
+
rivers = [attr['River'].decode('utf-8').strip() for attr in attrs_dataset]
|
207
|
+
reaches = [attr['Reach'].decode('utf-8').strip() for attr in attrs_dataset]
|
208
|
+
stations = [attr['Station'].decode('utf-8').strip() for attr in attrs_dataset]
|
209
|
+
names = [attr['Name'].decode('utf-8').strip() for attr in attrs_dataset]
|
210
|
+
|
211
|
+
# Extract Cross Section Only (Unique Names)
|
212
|
+
cross_section_only_dataset = hdf_file[f"{base_output_path}Cross Section Only"][:]
|
213
|
+
cross_section_names = [cs.decode('utf-8').strip() for cs in cross_section_only_dataset]
|
214
|
+
|
215
|
+
# Extract Time Stamps and convert to datetime
|
216
|
+
time_stamps = hdf_file[time_stamp_path][:]
|
217
|
+
if any(isinstance(ts, bytes) for ts in time_stamps):
|
218
|
+
time_stamps = [ts.decode('utf-8') for ts in time_stamps]
|
219
|
+
# Convert RAS format timestamps to datetime
|
220
|
+
times = pd.to_datetime(time_stamps, format='%d%b%Y %H:%M:%S:%f')
|
221
|
+
|
222
|
+
# Extract Required Datasets
|
223
|
+
water_surface = hdf_file[f"{base_output_path}Water Surface"][:]
|
224
|
+
velocity_total = hdf_file[f"{base_output_path}Velocity Total"][:]
|
225
|
+
velocity_channel = hdf_file[f"{base_output_path}Velocity Channel"][:]
|
226
|
+
flow_lateral = hdf_file[f"{base_output_path}Flow Lateral"][:]
|
227
|
+
flow = hdf_file[f"{base_output_path}Flow"][:]
|
228
|
+
|
229
|
+
# Calculate maximum values along time axis
|
230
|
+
max_water_surface = np.max(water_surface, axis=0)
|
231
|
+
max_flow = np.max(flow, axis=0)
|
232
|
+
max_velocity_channel = np.max(velocity_channel, axis=0)
|
233
|
+
max_velocity_total = np.max(velocity_total, axis=0)
|
234
|
+
max_flow_lateral = np.max(flow_lateral, axis=0)
|
235
|
+
|
236
|
+
# Create Xarray Dataset
|
237
|
+
ds = xr.Dataset(
|
238
|
+
{
|
239
|
+
'Water_Surface': (['time', 'cross_section'], water_surface),
|
240
|
+
'Velocity_Total': (['time', 'cross_section'], velocity_total),
|
241
|
+
'Velocity_Channel': (['time', 'cross_section'], velocity_channel),
|
242
|
+
'Flow_Lateral': (['time', 'cross_section'], flow_lateral),
|
243
|
+
'Flow': (['time', 'cross_section'], flow),
|
244
|
+
},
|
245
|
+
coords={
|
246
|
+
'time': times,
|
247
|
+
'cross_section': cross_section_names,
|
248
|
+
'River': ('cross_section', rivers),
|
249
|
+
'Reach': ('cross_section', reaches),
|
250
|
+
'Station': ('cross_section', stations),
|
251
|
+
'Name': ('cross_section', names),
|
252
|
+
'Maximum_Water_Surface': ('cross_section', max_water_surface),
|
253
|
+
'Maximum_Flow': ('cross_section', max_flow),
|
254
|
+
'Maximum_Channel_Velocity': ('cross_section', max_velocity_channel),
|
255
|
+
'Maximum_Velocity_Total': ('cross_section', max_velocity_total),
|
256
|
+
'Maximum_Flow_Lateral': ('cross_section', max_flow_lateral)
|
257
|
+
},
|
258
|
+
attrs={
|
259
|
+
'description': 'Cross-section results extracted from HEC-RAS HDF file',
|
260
|
+
'source_file': str(hdf_path)
|
261
|
+
}
|
262
|
+
)
|
263
|
+
|
264
|
+
return ds
|
265
|
+
|
266
|
+
except KeyError as e:
|
267
|
+
logger.error(f"Required dataset not found in HDF file: {e}")
|
268
|
+
raise
|
269
|
+
except Exception as e:
|
270
|
+
logger.error(f"Error extracting cross section results: {e}")
|
271
|
+
raise
|
237
272
|
|
ras_commander/HdfStruc.py
CHANGED
@@ -38,79 +38,177 @@ class HdfStruc:
|
|
38
38
|
|
39
39
|
Note: This class contains static methods and does not require instantiation.
|
40
40
|
"""
|
41
|
-
|
42
|
-
GEOM_STRUCTURES_PATH = "Geometry/Structures"
|
43
|
-
|
41
|
+
|
44
42
|
@staticmethod
|
45
43
|
@log_call
|
46
44
|
@standardize_input(file_type='geom_hdf')
|
47
45
|
def structures(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
48
46
|
"""
|
49
|
-
|
47
|
+
Extracts structure data from a HEC-RAS geometry HDF5 file and returns it as a GeoDataFrame.
|
50
48
|
|
51
|
-
This
|
52
|
-
|
49
|
+
This function excludes Property Tables, Pier and Abutment Data/Attributes, and Gate Groups.
|
50
|
+
It includes Table Info, Centerlines as LineStrings, Structures Attributes, Bridge Coefficient Attributes,
|
51
|
+
and Profile Data (as a list of station and elevation values for each structure).
|
53
52
|
|
54
53
|
Parameters
|
55
54
|
----------
|
56
55
|
hdf_path : Path
|
57
|
-
Path to the HEC-RAS geometry
|
56
|
+
Path to the HEC-RAS geometry HDF5 file.
|
58
57
|
datetime_to_str : bool, optional
|
59
|
-
|
58
|
+
Convert datetime objects to strings, by default False.
|
60
59
|
|
61
60
|
Returns
|
62
61
|
-------
|
63
62
|
GeoDataFrame
|
64
|
-
A GeoDataFrame containing
|
65
|
-
and geometry.
|
66
|
-
|
67
|
-
Raises
|
68
|
-
------
|
69
|
-
Exception
|
70
|
-
If there's an error reading the structures data from the HDF file.
|
63
|
+
A GeoDataFrame containing all relevant structure data with geometries and attributes.
|
71
64
|
"""
|
72
65
|
try:
|
73
|
-
with h5py.File(hdf_path, 'r') as
|
74
|
-
|
75
|
-
|
76
|
-
logger.info(f"No structures found in the geometry file: {hdf_path}")
|
66
|
+
with h5py.File(hdf_path, 'r') as hdf:
|
67
|
+
if "Geometry/Structures" not in hdf:
|
68
|
+
logger.info(f"No structures found in: {hdf_path}")
|
77
69
|
return GeoDataFrame()
|
70
|
+
|
71
|
+
def get_dataset_df(path: str) -> pd.DataFrame:
|
72
|
+
"""
|
73
|
+
Helper function to convert an HDF5 dataset to a pandas DataFrame.
|
74
|
+
|
75
|
+
Parameters
|
76
|
+
----------
|
77
|
+
path : str
|
78
|
+
The path to the dataset within the HDF5 file.
|
79
|
+
|
80
|
+
Returns
|
81
|
+
-------
|
82
|
+
pd.DataFrame
|
83
|
+
DataFrame representation of the dataset.
|
84
|
+
"""
|
85
|
+
if path not in hdf:
|
86
|
+
logger.warning(f"Dataset not found: {path}")
|
87
|
+
return pd.DataFrame()
|
88
|
+
|
89
|
+
data = hdf[path][()]
|
90
|
+
|
91
|
+
if data.dtype.names:
|
92
|
+
df = pd.DataFrame(data)
|
93
|
+
# Decode byte strings to UTF-8
|
94
|
+
for col in df.columns:
|
95
|
+
if df[col].dtype.kind in {'S', 'a'}: # Byte strings
|
96
|
+
df[col] = df[col].str.decode('utf-8', errors='ignore')
|
97
|
+
return df
|
98
|
+
else:
|
99
|
+
# If no named fields, assign generic column names
|
100
|
+
return pd.DataFrame(data, columns=[f'Value_{i}' for i in range(data.shape[1])])
|
101
|
+
|
102
|
+
# Extract relevant datasets
|
103
|
+
struct_attrs = get_dataset_df("Geometry/Structures/Attributes")
|
104
|
+
bridge_coef = get_dataset_df("Geometry/Structures/Bridge Coefficient Attributes")
|
105
|
+
table_info = get_dataset_df("Geometry/Structures/Table Info")
|
106
|
+
profile_data = get_dataset_df("Geometry/Structures/Profile Data")
|
107
|
+
|
108
|
+
# Assign 'Structure ID' based on index (starting from 1)
|
109
|
+
struct_attrs.reset_index(drop=True, inplace=True)
|
110
|
+
struct_attrs['Structure ID'] = range(1, len(struct_attrs) + 1)
|
111
|
+
logger.debug(f"Assigned Structure IDs: {struct_attrs['Structure ID'].tolist()}")
|
112
|
+
|
113
|
+
# Check if 'Structure ID' was successfully assigned
|
114
|
+
if 'Structure ID' not in struct_attrs.columns:
|
115
|
+
logger.error("'Structure ID' column could not be assigned to Structures/Attributes.")
|
116
|
+
return GeoDataFrame()
|
117
|
+
|
118
|
+
# Get centerline geometry
|
119
|
+
centerline_info = hdf["Geometry/Structures/Centerline Info"][()]
|
120
|
+
centerline_points = hdf["Geometry/Structures/Centerline Points"][()]
|
78
121
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
HdfStruc.GEOM_STRUCTURES_PATH,
|
93
|
-
info_name="Centerline Info",
|
94
|
-
parts_name="Centerline Parts",
|
95
|
-
points_name="Centerline Points"
|
96
|
-
)
|
97
|
-
|
98
|
-
# Create GeoDataFrame
|
122
|
+
# Create LineString geometries for each structure
|
123
|
+
geoms = []
|
124
|
+
for i in range(len(centerline_info)):
|
125
|
+
start_idx = centerline_info[i][0] # Point Starting Index
|
126
|
+
point_count = centerline_info[i][1] # Point Count
|
127
|
+
points = centerline_points[start_idx:start_idx + point_count]
|
128
|
+
if len(points) >= 2:
|
129
|
+
geoms.append(LineString(points))
|
130
|
+
else:
|
131
|
+
logger.warning(f"Insufficient points for LineString in structure index {i}.")
|
132
|
+
geoms.append(None)
|
133
|
+
|
134
|
+
# Create base GeoDataFrame with Structures Attributes and geometries
|
99
135
|
struct_gdf = GeoDataFrame(
|
100
|
-
|
136
|
+
struct_attrs,
|
101
137
|
geometry=geoms,
|
102
|
-
crs=HdfUtils.projection(hdf_path)
|
138
|
+
crs=HdfUtils.projection(hdf_path)
|
103
139
|
)
|
104
|
-
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
140
|
+
|
141
|
+
# Drop entries with invalid geometries
|
142
|
+
initial_count = len(struct_gdf)
|
143
|
+
struct_gdf = struct_gdf.dropna(subset=['geometry']).reset_index(drop=True)
|
144
|
+
final_count = len(struct_gdf)
|
145
|
+
if final_count < initial_count:
|
146
|
+
logger.warning(f"Dropped {initial_count - final_count} structures due to invalid geometries.")
|
147
|
+
|
148
|
+
# Merge Bridge Coefficient Attributes on 'Structure ID'
|
149
|
+
if not bridge_coef.empty and 'Structure ID' in bridge_coef.columns:
|
150
|
+
struct_gdf = struct_gdf.merge(
|
151
|
+
bridge_coef,
|
152
|
+
on='Structure ID',
|
153
|
+
how='left',
|
154
|
+
suffixes=('', '_bridge_coef')
|
109
155
|
)
|
110
|
-
|
156
|
+
logger.debug("Merged Bridge Coefficient Attributes successfully.")
|
157
|
+
else:
|
158
|
+
logger.warning("Bridge Coefficient Attributes missing or 'Structure ID' not present.")
|
159
|
+
|
160
|
+
# Merge Table Info based on the DataFrame index (one-to-one correspondence)
|
161
|
+
if not table_info.empty:
|
162
|
+
if len(table_info) != len(struct_gdf):
|
163
|
+
logger.warning("Table Info count does not match Structures count. Skipping merge.")
|
164
|
+
else:
|
165
|
+
struct_gdf = pd.concat([struct_gdf, table_info.reset_index(drop=True)], axis=1)
|
166
|
+
logger.debug("Merged Table Info successfully.")
|
167
|
+
else:
|
168
|
+
logger.warning("Table Info dataset is empty or missing.")
|
169
|
+
|
170
|
+
# Process Profile Data based on Table Info
|
171
|
+
if not profile_data.empty and not table_info.empty:
|
172
|
+
# Assuming 'Centerline Profile (Index)' and 'Centerline Profile (Count)' are in 'Table Info'
|
173
|
+
if ('Centerline Profile (Index)' in table_info.columns and
|
174
|
+
'Centerline Profile (Count)' in table_info.columns):
|
175
|
+
struct_gdf['Profile_Data'] = struct_gdf.apply(
|
176
|
+
lambda row: [
|
177
|
+
{'Station': float(profile_data.iloc[i, 0]),
|
178
|
+
'Elevation': float(profile_data.iloc[i, 1])}
|
179
|
+
for i in range(
|
180
|
+
int(row['Centerline Profile (Index)']),
|
181
|
+
int(row['Centerline Profile (Index)']) + int(row['Centerline Profile (Count)'])
|
182
|
+
)
|
183
|
+
],
|
184
|
+
axis=1
|
185
|
+
)
|
186
|
+
logger.debug("Processed Profile Data successfully.")
|
187
|
+
else:
|
188
|
+
logger.warning("Required columns for Profile Data not found in Table Info.")
|
189
|
+
else:
|
190
|
+
logger.warning("Profile Data dataset is empty or Table Info is missing.")
|
191
|
+
|
192
|
+
# Convert datetime columns to string if requested
|
193
|
+
if datetime_to_str:
|
194
|
+
datetime_cols = struct_gdf.select_dtypes(include=['datetime64']).columns
|
195
|
+
for col in datetime_cols:
|
196
|
+
struct_gdf[col] = struct_gdf[col].dt.isoformat()
|
197
|
+
logger.debug(f"Converted datetime column '{col}' to string.")
|
198
|
+
|
199
|
+
# Ensure all byte strings are decoded (if any remain)
|
200
|
+
for col in struct_gdf.columns:
|
201
|
+
if struct_gdf[col].dtype == object:
|
202
|
+
struct_gdf[col] = struct_gdf[col].apply(
|
203
|
+
lambda x: x.decode('utf-8', errors='ignore') if isinstance(x, bytes) else x
|
204
|
+
)
|
205
|
+
|
206
|
+
# Final GeoDataFrame
|
207
|
+
logger.info("Successfully extracted structures GeoDataFrame.")
|
111
208
|
return struct_gdf
|
209
|
+
|
112
210
|
except Exception as e:
|
113
|
-
logger.error(f"Error reading structures: {str(e)}")
|
211
|
+
logger.error(f"Error reading structures from {hdf_path}: {str(e)}")
|
114
212
|
raise
|
115
213
|
|
116
214
|
@staticmethod
|
@@ -138,10 +236,10 @@ class HdfStruc:
|
|
138
236
|
"""
|
139
237
|
try:
|
140
238
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
141
|
-
if
|
239
|
+
if "Geometry/Structures" not in hdf_file:
|
142
240
|
logger.info(f"No structures found in the geometry file: {hdf_path}")
|
143
241
|
return {}
|
144
|
-
return HdfUtils.get_attrs(hdf_file,
|
242
|
+
return HdfUtils.get_attrs(hdf_file, "Geometry/Structures")
|
145
243
|
except Exception as e:
|
146
244
|
logger.error(f"Error reading geometry structures attributes: {str(e)}")
|
147
245
|
return {}
|