ras-commander 0.44.0__tar.gz → 0.46.0__tar.gz
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-0.44.0/ras_commander.egg-info → ras_commander-0.46.0}/PKG-INFO +1 -1
- ras_commander-0.46.0/ras_commander/HdfFluvialPluvial.py +317 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfMesh.py +62 -15
- ras_commander-0.46.0/ras_commander/HdfPipe.py +771 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfPlan.py +5 -0
- ras_commander-0.46.0/ras_commander/HdfPump.py +269 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfResultsMesh.py +135 -62
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfResultsPlan.py +3 -0
- ras_commander-0.46.0/ras_commander/HdfResultsXsec.py +272 -0
- ras_commander-0.46.0/ras_commander/HdfStruc.py +245 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfUtils.py +51 -0
- ras_commander-0.46.0/ras_commander/HdfXsec.py +613 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasPlan.py +298 -45
- ras_commander-0.46.0/ras_commander/RasToGo.py +21 -0
- ras_commander-0.46.0/ras_commander/RasUnsteady.py +710 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/__init__.py +7 -1
- {ras_commander-0.44.0 → ras_commander-0.46.0/ras_commander.egg-info}/PKG-INFO +1 -1
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander.egg-info/SOURCES.txt +4 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/setup.py +1 -1
- ras_commander-0.44.0/ras_commander/HdfResultsXsec.py +0 -237
- ras_commander-0.44.0/ras_commander/HdfStruc.py +0 -147
- ras_commander-0.44.0/ras_commander/HdfXsec.py +0 -282
- ras_commander-0.44.0/ras_commander/RasUnsteady.py +0 -109
- {ras_commander-0.44.0 → ras_commander-0.46.0}/LICENSE +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/README.md +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/pyproject.toml +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/Decorators.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfBase.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/HdfBndry.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/LoggingConfig.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasCmdr.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasExamples.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasGeo.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasGpt.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasPrj.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander/RasUtils.py +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander.egg-info/dependency_links.txt +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/ras_commander.egg-info/top_level.txt +0 -0
- {ras_commander-0.44.0 → ras_commander-0.46.0}/setup.cfg +0 -0
@@ -0,0 +1,317 @@
|
|
1
|
+
from typing import Dict, List, Tuple
|
2
|
+
import pandas as pd
|
3
|
+
import geopandas as gpd
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
from collections import defaultdict
|
6
|
+
from rtree import index
|
7
|
+
from shapely.geometry import LineString, MultiLineString
|
8
|
+
from tqdm import tqdm
|
9
|
+
|
10
|
+
from .HdfMesh import HdfMesh
|
11
|
+
from .HdfUtils import HdfUtils
|
12
|
+
|
13
|
+
class HdfFluvialPluvial:
|
14
|
+
"""
|
15
|
+
A class for analyzing and visualizing fluvial-pluvial boundaries in HEC-RAS 2D model results.
|
16
|
+
|
17
|
+
This class provides methods to process and visualize HEC-RAS 2D model outputs,
|
18
|
+
specifically focusing on the delineation of fluvial and pluvial flood areas.
|
19
|
+
It includes functionality for plotting maximum water surface elevations,
|
20
|
+
extracting cell polygons, and calculating fluvial-pluvial boundaries based on
|
21
|
+
the timing of maximum water surface elevations.
|
22
|
+
|
23
|
+
Key Features:
|
24
|
+
- Plotting maximum water surface elevations and their timing
|
25
|
+
- Extracting and visualizing 2D flow area cell polygons
|
26
|
+
- Calculating and visualizing fluvial-pluvial boundaries
|
27
|
+
|
28
|
+
Data Requirements:
|
29
|
+
1. For plotting maximum water surface:
|
30
|
+
- Use HdfResultsMesh.mesh_max_ws(hdf_path) to get max_ws_df
|
31
|
+
- Use HdfResultsMesh.mesh_timeseries_output(hdf_path, mesh_name, 'water_surface')
|
32
|
+
to get time series data
|
33
|
+
|
34
|
+
2. For extracting cell polygons:
|
35
|
+
- Use HdfMesh.mesh_cell_polygons(geom_hdf_path) to get cell_polygons_df
|
36
|
+
- Use HdfUtils.projection(hdf_path) to get the projection
|
37
|
+
|
38
|
+
3. For calculating fluvial-pluvial boundary:
|
39
|
+
- Requires cell_polygons_gdf (from step 2)
|
40
|
+
- Requires max_ws_df with 'cell_id' and 'max_wsel_time' columns
|
41
|
+
(can be derived from HdfResultsMesh.mesh_max_ws(hdf_path))
|
42
|
+
|
43
|
+
Usage:
|
44
|
+
To use this class effectively, first initialize a RasPrj object and load the
|
45
|
+
necessary HDF files. Then, use the methods provided to analyze and visualize
|
46
|
+
the fluvial-pluvial characteristics of your 2D model results.
|
47
|
+
|
48
|
+
Example:
|
49
|
+
ras = RasPrj()
|
50
|
+
ras = init_ras_project(project_path, ras_version)
|
51
|
+
hdf_path = ras.get_plan_value(plan_number, 'Results_Output')
|
52
|
+
|
53
|
+
# Get maximum water surface data
|
54
|
+
max_ws_df = HdfResultsMesh.mesh_max_ws(hdf_path)
|
55
|
+
|
56
|
+
# Plot maximum water surface
|
57
|
+
HdfFluvialPluvial.plot_max_water_surface(max_ws_df)
|
58
|
+
|
59
|
+
# Extract cell polygons
|
60
|
+
cell_polygons_df = HdfMesh.mesh_cell_polygons(hdf_path)
|
61
|
+
projection = HdfUtils.projection(hdf_path)
|
62
|
+
cell_polygons_gdf = HdfFluvialPluvial.plot_cell_polygons(cell_polygons_df, projection)
|
63
|
+
|
64
|
+
# Calculate fluvial-pluvial boundary
|
65
|
+
boundary_gdf = HdfFluvialPluvial.calculate_fluvial_pluvial_boundary(cell_polygons_gdf, max_ws_df)
|
66
|
+
|
67
|
+
Note: Ensure that you have the necessary permissions and have initialized
|
68
|
+
the RAS project correctly before attempting to access HDF files.
|
69
|
+
"""
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def plot_max_water_surface(max_ws_df):
|
73
|
+
"""
|
74
|
+
Plots the maximum water surface elevation per cell.
|
75
|
+
|
76
|
+
Parameters:
|
77
|
+
- max_ws_df: DataFrame containing merged data with coordinates and max water surface.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
- None
|
81
|
+
"""
|
82
|
+
# Extract x and y coordinates from the geometry column
|
83
|
+
max_ws_df['x'] = max_ws_df['geometry'].apply(lambda geom: geom.x if geom is not None else None)
|
84
|
+
max_ws_df['y'] = max_ws_df['geometry'].apply(lambda geom: geom.y if geom is not None else None)
|
85
|
+
|
86
|
+
# Check if 'x' and 'y' columns exist in max_ws_df
|
87
|
+
if 'x' not in max_ws_df.columns or 'y' not in max_ws_df.columns:
|
88
|
+
print("Error: 'x' or 'y' columns not found in the merged dataframe.")
|
89
|
+
print("Available columns:", max_ws_df.columns.tolist())
|
90
|
+
return
|
91
|
+
|
92
|
+
# Create the plot
|
93
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
94
|
+
scatter = ax.scatter(max_ws_df['x'], max_ws_df['y'], c=max_ws_df['maximum_water_surface'], cmap='viridis', s=10)
|
95
|
+
|
96
|
+
# Customize the plot
|
97
|
+
ax.set_title('Max Water Surface per Cell')
|
98
|
+
ax.set_xlabel('X Coordinate')
|
99
|
+
ax.set_ylabel('Y Coordinate')
|
100
|
+
plt.colorbar(scatter, label='Max Water Surface (ft)')
|
101
|
+
|
102
|
+
# Add grid lines
|
103
|
+
ax.grid(True, linestyle='--', alpha=0.7)
|
104
|
+
|
105
|
+
# Increase font size for better readability
|
106
|
+
plt.rcParams.update({'font.size': 12})
|
107
|
+
|
108
|
+
# Adjust layout to prevent cutting off labels
|
109
|
+
plt.tight_layout()
|
110
|
+
|
111
|
+
# Show the plot
|
112
|
+
plt.show()
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
@staticmethod
|
118
|
+
def plot_max_wsel_time(max_ws_df: pd.DataFrame) -> None:
|
119
|
+
"""
|
120
|
+
Plots the time of the maximum water surface elevation (WSEL) per cell.
|
121
|
+
|
122
|
+
Parameters:
|
123
|
+
- max_ws_df: DataFrame containing merged data with coordinates and max water surface.
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
- None
|
127
|
+
"""
|
128
|
+
max_ws_df['max_wsel_time'] = pd.to_datetime(max_ws_df['maximum_water_surface_time'])
|
129
|
+
HdfFluvialPluvial._extract_coordinates(max_ws_df)
|
130
|
+
|
131
|
+
if 'x' not in max_ws_df.columns or 'y' not in max_ws_df.columns:
|
132
|
+
raise ValueError("x and y coordinates are missing from the DataFrame. Make sure the 'face_point' column exists and contains valid coordinate data.")
|
133
|
+
|
134
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
135
|
+
|
136
|
+
min_time = max_ws_df['max_wsel_time'].min()
|
137
|
+
color_values = (max_ws_df['max_wsel_time'] - min_time).dt.total_seconds() / 3600
|
138
|
+
|
139
|
+
scatter = ax.scatter(max_ws_df['x'], max_ws_df['y'], c=color_values, cmap='viridis', s=10)
|
140
|
+
|
141
|
+
ax.set_title('Time of Maximum Water Surface Elevation per Cell')
|
142
|
+
ax.set_xlabel('X Coordinate')
|
143
|
+
ax.set_ylabel('Y Coordinate')
|
144
|
+
|
145
|
+
cbar = plt.colorbar(scatter)
|
146
|
+
cbar.set_label('Hours since simulation start')
|
147
|
+
cbar.set_ticks(range(0, int(color_values.max()) + 1, 6))
|
148
|
+
cbar.set_ticklabels([f'{h}h' for h in range(0, int(color_values.max()) + 1, 6)])
|
149
|
+
|
150
|
+
ax.grid(True, linestyle='--', alpha=0.7)
|
151
|
+
plt.rcParams.update({'font.size': 12})
|
152
|
+
plt.tight_layout()
|
153
|
+
plt.show()
|
154
|
+
|
155
|
+
HdfFluvialPluvial._print_max_wsel_info(max_ws_df, min_time)
|
156
|
+
|
157
|
+
@staticmethod
|
158
|
+
def plot_cell_polygons(cell_polygons_df: pd.DataFrame, projection: str) -> gpd.GeoDataFrame:
|
159
|
+
"""
|
160
|
+
Plots the cell polygons from the provided DataFrame and returns the GeoDataFrame.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
cell_polygons_df (pd.DataFrame): DataFrame containing cell polygons.
|
164
|
+
projection (str): The coordinate reference system to assign to the GeoDataFrame.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
gpd.GeoDataFrame: GeoDataFrame containing the cell polygons.
|
168
|
+
"""
|
169
|
+
if cell_polygons_df.empty:
|
170
|
+
print("No Cell Polygons found.")
|
171
|
+
return None
|
172
|
+
|
173
|
+
cell_polygons_gdf = HdfFluvialPluvial._convert_to_geodataframe(cell_polygons_df, projection)
|
174
|
+
|
175
|
+
print("Cell Polygons CRS:", cell_polygons_gdf.crs)
|
176
|
+
display(cell_polygons_gdf.head())
|
177
|
+
|
178
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
179
|
+
cell_polygons_gdf.plot(ax=ax, edgecolor='blue', facecolor='none')
|
180
|
+
ax.set_xlabel('X Coordinate')
|
181
|
+
ax.set_ylabel('Y Coordinate')
|
182
|
+
ax.set_title('2D Flow Area Cell Polygons')
|
183
|
+
ax.grid(True)
|
184
|
+
plt.tight_layout()
|
185
|
+
plt.show()
|
186
|
+
|
187
|
+
return cell_polygons_gdf
|
188
|
+
|
189
|
+
@staticmethod
|
190
|
+
def calculate_fluvial_pluvial_boundary(cell_polygons_gdf: gpd.GeoDataFrame, max_ws_df: pd.DataFrame, delta_t: float = 12) -> gpd.GeoDataFrame:
|
191
|
+
"""
|
192
|
+
Calculate the fluvial-pluvial boundary based on cell polygons and maximum water surface elevation times.
|
193
|
+
|
194
|
+
Args:
|
195
|
+
cell_polygons_gdf (gpd.GeoDataFrame): GeoDataFrame containing cell polygons with 'cell_id' and 'geometry' columns.
|
196
|
+
max_ws_df (pd.DataFrame): DataFrame containing 'cell_id' and 'max_wsel_time' columns.
|
197
|
+
delta_t (float): Threshold time difference in hours. Default is 12 hours.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
gpd.GeoDataFrame: GeoDataFrame containing the fluvial-pluvial boundary as simple LineStrings.
|
201
|
+
"""
|
202
|
+
cell_adjacency, common_edges = HdfFluvialPluvial._process_cell_adjacencies(cell_polygons_gdf)
|
203
|
+
cell_times = max_ws_df.set_index('cell_id')['max_wsel_time'].to_dict()
|
204
|
+
boundary_edges = HdfFluvialPluvial._identify_boundary_edges(cell_adjacency, common_edges, cell_times, delta_t)
|
205
|
+
|
206
|
+
# Join adjacent LineStrings into simple LineStrings
|
207
|
+
joined_lines = []
|
208
|
+
current_line = []
|
209
|
+
|
210
|
+
for edge in boundary_edges:
|
211
|
+
if not current_line:
|
212
|
+
current_line.append(edge)
|
213
|
+
else:
|
214
|
+
if current_line[-1].coords[-1] == edge.coords[0]: # Check if the last point of the current line matches the first point of the new edge
|
215
|
+
current_line.append(edge)
|
216
|
+
else:
|
217
|
+
# Create a simple LineString from the current line and reset
|
218
|
+
joined_lines.append(LineString([point for line in current_line for point in line.coords]))
|
219
|
+
current_line = [edge]
|
220
|
+
|
221
|
+
# Add the last collected line if exists
|
222
|
+
if current_line:
|
223
|
+
joined_lines.append(LineString([point for line in current_line for point in line.coords]))
|
224
|
+
|
225
|
+
boundary_gdf = gpd.GeoDataFrame(geometry=joined_lines, crs=cell_polygons_gdf.crs)
|
226
|
+
return boundary_gdf
|
227
|
+
|
228
|
+
@staticmethod
|
229
|
+
def _print_max_wsel_info(max_ws_df: pd.DataFrame, min_time: pd.Timestamp) -> None:
|
230
|
+
max_wsel_row = max_ws_df.loc[max_ws_df['maximum_water_surface'].idxmax()]
|
231
|
+
hours_since_start = (max_wsel_row['max_wsel_time'] - min_time).total_seconds() / 3600
|
232
|
+
print(f"\nOverall Maximum WSEL: {max_wsel_row['maximum_water_surface']:.2f} ft")
|
233
|
+
print(f"Time of Overall Maximum WSEL: {max_wsel_row['max_wsel_time']}")
|
234
|
+
print(f"Hours since simulation start: {hours_since_start:.2f} hours")
|
235
|
+
print(f"Location of Overall Maximum WSEL: X={max_wsel_row['x']}, Y={max_wsel_row['y']}")
|
236
|
+
|
237
|
+
@staticmethod
|
238
|
+
def _process_cell_adjacencies(cell_polygons_gdf: gpd.GeoDataFrame) -> Tuple[Dict[int, List[int]], Dict[int, Dict[int, LineString]]]:
|
239
|
+
cell_adjacency = defaultdict(list)
|
240
|
+
common_edges = defaultdict(dict)
|
241
|
+
idx = index.Index()
|
242
|
+
for i, geom in enumerate(cell_polygons_gdf.geometry):
|
243
|
+
idx.insert(i, geom.bounds)
|
244
|
+
|
245
|
+
with tqdm(total=len(cell_polygons_gdf), desc="Processing cell adjacencies") as pbar:
|
246
|
+
for idx1, row1 in cell_polygons_gdf.iterrows():
|
247
|
+
cell_id1 = row1['cell_id']
|
248
|
+
poly1 = row1['geometry']
|
249
|
+
potential_neighbors = list(idx.intersection(poly1.bounds))
|
250
|
+
|
251
|
+
for idx2 in potential_neighbors:
|
252
|
+
if idx1 >= idx2:
|
253
|
+
continue
|
254
|
+
|
255
|
+
row2 = cell_polygons_gdf.iloc[idx2]
|
256
|
+
cell_id2 = row2['cell_id']
|
257
|
+
poly2 = row2['geometry']
|
258
|
+
|
259
|
+
if poly1.touches(poly2):
|
260
|
+
intersection = poly1.intersection(poly2)
|
261
|
+
if isinstance(intersection, LineString):
|
262
|
+
cell_adjacency[cell_id1].append(cell_id2)
|
263
|
+
cell_adjacency[cell_id2].append(cell_id1)
|
264
|
+
common_edges[cell_id1][cell_id2] = intersection
|
265
|
+
common_edges[cell_id2][cell_id1] = intersection
|
266
|
+
|
267
|
+
pbar.update(1)
|
268
|
+
|
269
|
+
return cell_adjacency, common_edges
|
270
|
+
|
271
|
+
@staticmethod
|
272
|
+
def _identify_boundary_edges(cell_adjacency: Dict[int, List[int]], common_edges: Dict[int, Dict[int, LineString]], cell_times: Dict[int, pd.Timestamp], delta_t: float) -> List[LineString]:
|
273
|
+
boundary_edges = []
|
274
|
+
with tqdm(total=len(cell_adjacency), desc="Processing cell adjacencies") as pbar:
|
275
|
+
for cell_id, neighbors in cell_adjacency.items():
|
276
|
+
cell_time = cell_times[cell_id]
|
277
|
+
|
278
|
+
for neighbor_id in neighbors:
|
279
|
+
neighbor_time = cell_times[neighbor_id]
|
280
|
+
time_diff = abs((cell_time - neighbor_time).total_seconds() / 3600)
|
281
|
+
|
282
|
+
if time_diff >= delta_t:
|
283
|
+
boundary_edges.append(common_edges[cell_id][neighbor_id])
|
284
|
+
|
285
|
+
pbar.update(1)
|
286
|
+
return boundary_edges
|
287
|
+
|
288
|
+
@staticmethod
|
289
|
+
def _extract_coordinates(df: pd.DataFrame) -> None:
|
290
|
+
"""
|
291
|
+
Extract x and y coordinates from the 'face_point' column.
|
292
|
+
|
293
|
+
Parameters:
|
294
|
+
- df: DataFrame containing the 'face_point' column.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
- None (modifies the DataFrame in-place)
|
298
|
+
"""
|
299
|
+
if 'face_point' in df.columns:
|
300
|
+
df[['x', 'y']] = df['face_point'].str.strip('()').str.split(',', expand=True).astype(float)
|
301
|
+
else:
|
302
|
+
print("Warning: 'face_point' column not found in the DataFrame.")
|
303
|
+
|
304
|
+
@staticmethod
|
305
|
+
def _convert_to_geodataframe(df: pd.DataFrame, projection: str) -> gpd.GeoDataFrame:
|
306
|
+
"""
|
307
|
+
Convert a DataFrame to a GeoDataFrame.
|
308
|
+
|
309
|
+
Parameters:
|
310
|
+
- df: DataFrame containing 'geometry' column.
|
311
|
+
- projection: The coordinate reference system to assign to the GeoDataFrame.
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
- GeoDataFrame with the specified projection.
|
315
|
+
"""
|
316
|
+
gdf = gpd.GeoDataFrame(df, geometry='geometry', crs=projection)
|
317
|
+
return gdf
|
@@ -39,8 +39,6 @@ class HdfMesh:
|
|
39
39
|
Note: This class relies on HdfBase and HdfUtils for some underlying operations.
|
40
40
|
"""
|
41
41
|
|
42
|
-
FLOW_AREA_2D_PATH = "Geometry/2D Flow Areas"
|
43
|
-
|
44
42
|
def __init__(self):
|
45
43
|
self.logger = logging.getLogger(__name__)
|
46
44
|
|
@@ -62,12 +60,12 @@ class HdfMesh:
|
|
62
60
|
"""
|
63
61
|
try:
|
64
62
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
65
|
-
if
|
63
|
+
if "Geometry/2D Flow Areas" not in hdf_file:
|
66
64
|
return list()
|
67
65
|
return list(
|
68
66
|
[
|
69
67
|
HdfUtils.convert_ras_hdf_string(n)
|
70
|
-
for n in hdf_file[
|
68
|
+
for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
|
71
69
|
]
|
72
70
|
)
|
73
71
|
except Exception as e:
|
@@ -96,7 +94,7 @@ class HdfMesh:
|
|
96
94
|
if not mesh_area_names:
|
97
95
|
return GeoDataFrame()
|
98
96
|
mesh_area_polygons = [
|
99
|
-
Polygon(hdf_file[
|
97
|
+
Polygon(hdf_file["Geometry/2D Flow Areas/{}/Perimeter".format(n)][()])
|
100
98
|
for n in mesh_area_names
|
101
99
|
]
|
102
100
|
return GeoDataFrame(
|
@@ -134,13 +132,13 @@ class HdfMesh:
|
|
134
132
|
|
135
133
|
cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
|
136
134
|
for i, mesh_name in enumerate(mesh_area_names):
|
137
|
-
cell_cnt = hdf_file[
|
135
|
+
cell_cnt = hdf_file["Geometry/2D Flow Areas/Cell Info"][()][i][1]
|
138
136
|
cell_ids = list(range(cell_cnt))
|
139
137
|
cell_face_info = hdf_file[
|
140
|
-
|
138
|
+
"Geometry/2D Flow Areas/{}/Cells Face and Orientation Info".format(mesh_name)
|
141
139
|
][()]
|
142
140
|
cell_face_values = hdf_file[
|
143
|
-
|
141
|
+
"Geometry/2D Flow Areas/{}/Cells Face and Orientation Values".format(mesh_name)
|
144
142
|
][()][:, 0]
|
145
143
|
face_id_lists = list(
|
146
144
|
np.vectorize(
|
@@ -200,8 +198,8 @@ class HdfMesh:
|
|
200
198
|
return GeoDataFrame()
|
201
199
|
pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
|
202
200
|
for i, mesh_name in enumerate(mesh_area_names):
|
203
|
-
starting_row, count = hdf_file[
|
204
|
-
cell_pnt_coords = hdf_file[
|
201
|
+
starting_row, count = hdf_file["Geometry/2D Flow Areas/Cell Info"][()][i]
|
202
|
+
cell_pnt_coords = hdf_file["Geometry/2D Flow Areas/Cell Points"][()][
|
205
203
|
starting_row : starting_row + count
|
206
204
|
]
|
207
205
|
pnt_dict["mesh_name"] += [mesh_name] * cell_pnt_coords.shape[0]
|
@@ -240,16 +238,16 @@ class HdfMesh:
|
|
240
238
|
face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
|
241
239
|
for mesh_name in mesh_area_names:
|
242
240
|
facepoints_index = hdf_file[
|
243
|
-
|
241
|
+
"Geometry/2D Flow Areas/{}/Faces FacePoint Indexes".format(mesh_name)
|
244
242
|
][()]
|
245
243
|
facepoints_coordinates = hdf_file[
|
246
|
-
|
244
|
+
"Geometry/2D Flow Areas/{}/FacePoints Coordinate".format(mesh_name)
|
247
245
|
][()]
|
248
246
|
faces_perimeter_info = hdf_file[
|
249
|
-
|
247
|
+
"Geometry/2D Flow Areas/{}/Faces Perimeter Info".format(mesh_name)
|
250
248
|
][()]
|
251
249
|
faces_perimeter_values = hdf_file[
|
252
|
-
|
250
|
+
"Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
|
253
251
|
][()]
|
254
252
|
face_id = -1
|
255
253
|
for pnt_a_index, pnt_b_index in facepoints_index:
|
@@ -288,7 +286,7 @@ class HdfMesh:
|
|
288
286
|
"""
|
289
287
|
try:
|
290
288
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
291
|
-
d2_flow_area = hdf_file.get(
|
289
|
+
d2_flow_area = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
292
290
|
if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
|
293
291
|
result = {}
|
294
292
|
for name in d2_flow_area.dtype.names:
|
@@ -306,3 +304,52 @@ class HdfMesh:
|
|
306
304
|
except Exception as e:
|
307
305
|
logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
|
308
306
|
return {}
|
307
|
+
|
308
|
+
|
309
|
+
@staticmethod
|
310
|
+
@standardize_input(file_type='geom_hdf')
|
311
|
+
def get_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
312
|
+
"""
|
313
|
+
Extract Face Property Tables for each Face in all 2D Flow Areas.
|
314
|
+
|
315
|
+
Parameters
|
316
|
+
----------
|
317
|
+
hdf_path : Path
|
318
|
+
Path to the HEC-RAS geometry HDF file.
|
319
|
+
|
320
|
+
Returns
|
321
|
+
-------
|
322
|
+
Dict[str, pd.DataFrame]
|
323
|
+
A dictionary where keys are mesh names and values are DataFrames
|
324
|
+
containing the Face Property Tables for all faces in that mesh.
|
325
|
+
"""
|
326
|
+
try:
|
327
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
328
|
+
mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
|
329
|
+
if not mesh_area_names:
|
330
|
+
return {}
|
331
|
+
|
332
|
+
result = {}
|
333
|
+
for mesh_name in mesh_area_names:
|
334
|
+
area_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Info"][()]
|
335
|
+
area_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Values"][()]
|
336
|
+
|
337
|
+
face_data = []
|
338
|
+
for face_id, (start_index, count) in enumerate(area_elevation_info):
|
339
|
+
face_values = area_elevation_values[start_index:start_index+count]
|
340
|
+
for z, area, wetted_perimeter, mannings_n in face_values:
|
341
|
+
face_data.append({
|
342
|
+
'Face ID': face_id,
|
343
|
+
'Z': z,
|
344
|
+
'Area': area,
|
345
|
+
'Wetted Perimeter': wetted_perimeter,
|
346
|
+
"Manning's n": mannings_n
|
347
|
+
})
|
348
|
+
|
349
|
+
result[mesh_name] = pd.DataFrame(face_data)
|
350
|
+
|
351
|
+
return result
|
352
|
+
|
353
|
+
except Exception as e:
|
354
|
+
logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
|
355
|
+
return {}
|