ras-commander 0.45.0__py3-none-any.whl → 0.47.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/HdfBndry.py +9 -9
- ras_commander/HdfFluvialPluvial.py +317 -0
- ras_commander/HdfMesh.py +73 -27
- ras_commander/HdfPipe.py +587 -78
- ras_commander/HdfPlan.py +5 -0
- ras_commander/HdfPump.py +25 -11
- ras_commander/HdfResultsMesh.py +135 -62
- ras_commander/HdfResultsXsec.py +126 -297
- 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/RasPrj.py +2 -7
- ras_commander/RasToGo.py +21 -0
- ras_commander/RasUnsteady.py +615 -14
- ras_commander/__init__.py +3 -1
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/METADATA +2 -2
- ras_commander-0.47.0.dist-info/RECORD +30 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/WHEEL +1 -1
- ras_commander-0.45.0.dist-info/RECORD +0 -28
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfBndry.py
CHANGED
@@ -70,9 +70,9 @@ class HdfBndry:
|
|
70
70
|
return gpd.GeoDataFrame(
|
71
71
|
{
|
72
72
|
"bc_line_id": bc_line_ids,
|
73
|
-
"
|
73
|
+
"Name": names,
|
74
74
|
"mesh_name": mesh_names,
|
75
|
-
"
|
75
|
+
"Type": types,
|
76
76
|
"geometry": geoms,
|
77
77
|
},
|
78
78
|
geometry="geometry",
|
@@ -110,7 +110,7 @@ class HdfBndry:
|
|
110
110
|
)
|
111
111
|
geoms = HdfBndry._get_polylines(hdf_file, breaklines_path)
|
112
112
|
return gpd.GeoDataFrame(
|
113
|
-
{"bl_id": bl_line_ids, "
|
113
|
+
{"bl_id": bl_line_ids, "Name": names, "geometry": geoms},
|
114
114
|
geometry="geometry",
|
115
115
|
crs=HdfUtils.projection(hdf_file),
|
116
116
|
)
|
@@ -158,7 +158,7 @@ class HdfBndry:
|
|
158
158
|
)
|
159
159
|
)
|
160
160
|
return gpd.GeoDataFrame(
|
161
|
-
{"rr_id": rr_ids, "
|
161
|
+
{"rr_id": rr_ids, "Name": names, "geometry": geoms},
|
162
162
|
geometry="geometry",
|
163
163
|
crs=HdfUtils.projection(hdf_file),
|
164
164
|
)
|
@@ -242,9 +242,9 @@ class HdfBndry:
|
|
242
242
|
return gpd.GeoDataFrame(
|
243
243
|
{
|
244
244
|
"refln_id": refline_ids,
|
245
|
-
"
|
246
|
-
"
|
247
|
-
"
|
245
|
+
"Name": names,
|
246
|
+
"mesh-name": mesh_names,
|
247
|
+
"Type": types,
|
248
248
|
"geometry": geoms,
|
249
249
|
},
|
250
250
|
geometry="geometry",
|
@@ -286,9 +286,9 @@ class HdfBndry:
|
|
286
286
|
return gpd.GeoDataFrame(
|
287
287
|
{
|
288
288
|
"refpt_id": range(attributes.shape[0]),
|
289
|
-
"
|
289
|
+
"Name": names,
|
290
290
|
"mesh_name": mesh_names,
|
291
|
-
"
|
291
|
+
"Cell Index": cell_id,
|
292
292
|
"geometry": list(map(Point, points)),
|
293
293
|
},
|
294
294
|
geometry="geometry",
|
@@ -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
|
ras_commander/HdfMesh.py
CHANGED
@@ -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
|
-
HdfUtils.convert_ras_hdf_string(n)
|
70
|
-
for n in hdf_file[
|
67
|
+
HdfUtils.convert_ras_hdf_string(n.decode('utf-8')) # Decode as UTF-8
|
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(
|
@@ -176,12 +174,11 @@ class HdfMesh:
|
|
176
174
|
except Exception as e:
|
177
175
|
logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
|
178
176
|
return GeoDataFrame()
|
179
|
-
|
180
177
|
@staticmethod
|
181
178
|
@standardize_input(file_type='plan_hdf')
|
182
179
|
def mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
|
183
180
|
"""
|
184
|
-
Return 2D flow mesh cell points.
|
181
|
+
Return 2D flow mesh cell center points.
|
185
182
|
|
186
183
|
Parameters
|
187
184
|
----------
|
@@ -191,29 +188,29 @@ class HdfMesh:
|
|
191
188
|
Returns
|
192
189
|
-------
|
193
190
|
GeoDataFrame
|
194
|
-
A GeoDataFrame containing the 2D flow mesh cell points.
|
191
|
+
A GeoDataFrame containing the 2D flow mesh cell center points.
|
195
192
|
"""
|
196
193
|
try:
|
197
194
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
198
195
|
mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
|
199
196
|
if not mesh_area_names:
|
200
197
|
return GeoDataFrame()
|
198
|
+
|
201
199
|
pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
|
202
|
-
for
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
]
|
207
|
-
pnt_dict["
|
208
|
-
pnt_dict["cell_id"] += range(count)
|
200
|
+
for mesh_name in mesh_area_names:
|
201
|
+
cell_center_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
|
202
|
+
cell_count = len(cell_center_coords)
|
203
|
+
|
204
|
+
pnt_dict["mesh_name"] += [mesh_name] * cell_count
|
205
|
+
pnt_dict["cell_id"] += range(cell_count)
|
209
206
|
pnt_dict["geometry"] += list(
|
210
207
|
np.vectorize(lambda coords: Point(coords), signature="(n)->()")(
|
211
|
-
|
208
|
+
cell_center_coords
|
212
209
|
)
|
213
210
|
)
|
214
211
|
return GeoDataFrame(pnt_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
|
215
212
|
except Exception as e:
|
216
|
-
|
213
|
+
logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
|
217
214
|
return GeoDataFrame()
|
218
215
|
|
219
216
|
@staticmethod
|
@@ -240,16 +237,16 @@ class HdfMesh:
|
|
240
237
|
face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
|
241
238
|
for mesh_name in mesh_area_names:
|
242
239
|
facepoints_index = hdf_file[
|
243
|
-
|
240
|
+
"Geometry/2D Flow Areas/{}/Faces FacePoint Indexes".format(mesh_name)
|
244
241
|
][()]
|
245
242
|
facepoints_coordinates = hdf_file[
|
246
|
-
|
243
|
+
"Geometry/2D Flow Areas/{}/FacePoints Coordinate".format(mesh_name)
|
247
244
|
][()]
|
248
245
|
faces_perimeter_info = hdf_file[
|
249
|
-
|
246
|
+
"Geometry/2D Flow Areas/{}/Faces Perimeter Info".format(mesh_name)
|
250
247
|
][()]
|
251
248
|
faces_perimeter_values = hdf_file[
|
252
|
-
|
249
|
+
"Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
|
253
250
|
][()]
|
254
251
|
face_id = -1
|
255
252
|
for pnt_a_index, pnt_b_index in facepoints_index:
|
@@ -288,14 +285,14 @@ class HdfMesh:
|
|
288
285
|
"""
|
289
286
|
try:
|
290
287
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
291
|
-
d2_flow_area = hdf_file.get(
|
288
|
+
d2_flow_area = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
292
289
|
if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
|
293
290
|
result = {}
|
294
291
|
for name in d2_flow_area.dtype.names:
|
295
292
|
try:
|
296
293
|
value = d2_flow_area[name][()]
|
297
294
|
if isinstance(value, bytes):
|
298
|
-
value = value.decode('utf-8')
|
295
|
+
value = value.decode('utf-8') # Decode as UTF-8
|
299
296
|
result[name] = value
|
300
297
|
except Exception as e:
|
301
298
|
logger.warning(f"Error converting attribute '{name}': {str(e)}")
|
@@ -306,3 +303,52 @@ class HdfMesh:
|
|
306
303
|
except Exception as e:
|
307
304
|
logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
|
308
305
|
return {}
|
306
|
+
|
307
|
+
|
308
|
+
@staticmethod
|
309
|
+
@standardize_input(file_type='geom_hdf')
|
310
|
+
def get_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
311
|
+
"""
|
312
|
+
Extract Face Property Tables for each Face in all 2D Flow Areas.
|
313
|
+
|
314
|
+
Parameters
|
315
|
+
----------
|
316
|
+
hdf_path : Path
|
317
|
+
Path to the HEC-RAS geometry HDF file.
|
318
|
+
|
319
|
+
Returns
|
320
|
+
-------
|
321
|
+
Dict[str, pd.DataFrame]
|
322
|
+
A dictionary where keys are mesh names and values are DataFrames
|
323
|
+
containing the Face Property Tables for all faces in that mesh.
|
324
|
+
"""
|
325
|
+
try:
|
326
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
327
|
+
mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
|
328
|
+
if not mesh_area_names:
|
329
|
+
return {}
|
330
|
+
|
331
|
+
result = {}
|
332
|
+
for mesh_name in mesh_area_names:
|
333
|
+
area_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Info"][()]
|
334
|
+
area_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Values"][()]
|
335
|
+
|
336
|
+
face_data = []
|
337
|
+
for face_id, (start_index, count) in enumerate(area_elevation_info):
|
338
|
+
face_values = area_elevation_values[start_index:start_index+count]
|
339
|
+
for z, area, wetted_perimeter, mannings_n in face_values:
|
340
|
+
face_data.append({
|
341
|
+
'Face ID': face_id,
|
342
|
+
'Z': z.decode('utf-8'), # Decode as UTF-8
|
343
|
+
'Area': area.decode('utf-8'), # Decode as UTF-8
|
344
|
+
'Wetted Perimeter': wetted_perimeter.decode('utf-8'), # Decode as UTF-8
|
345
|
+
"Manning's n": mannings_n.decode('utf-8') # Decode as UTF-8
|
346
|
+
})
|
347
|
+
|
348
|
+
result[mesh_name] = pd.DataFrame(face_data)
|
349
|
+
|
350
|
+
return result
|
351
|
+
|
352
|
+
except Exception as e:
|
353
|
+
logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
|
354
|
+
return {}
|