ras-commander 0.51.0__py3-none-any.whl → 0.52.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/HdfBase.py +344 -307
- ras_commander/HdfFluvialPluvial.py +554 -309
- ras_commander/HdfMesh.py +461 -461
- ras_commander/HdfUtils.py +434 -434
- ras_commander/HdfXsec.py +56 -38
- ras_commander/RasPrj.py +93 -55
- {ras_commander-0.51.0.dist-info → ras_commander-0.52.0.dist-info}/METADATA +11 -8
- {ras_commander-0.51.0.dist-info → ras_commander-0.52.0.dist-info}/RECORD +11 -11
- {ras_commander-0.51.0.dist-info → ras_commander-0.52.0.dist-info}/WHEEL +1 -1
- {ras_commander-0.51.0.dist-info → ras_commander-0.52.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.51.0.dist-info → ras_commander-0.52.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfMesh.py
CHANGED
@@ -1,461 +1,461 @@
|
|
1
|
-
"""
|
2
|
-
A static class for handling mesh-related operations on HEC-RAS HDF files.
|
3
|
-
|
4
|
-
This class provides static methods to extract and analyze mesh data from HEC-RAS HDF files,
|
5
|
-
including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
|
6
|
-
2D flow area attributes. No instantiation is required to use these methods.
|
7
|
-
|
8
|
-
All methods are designed to work with the mesh geometry data stored in
|
9
|
-
HEC-RAS HDF files, providing functionality to retrieve and process various aspects
|
10
|
-
of the 2D flow areas and their associated mesh structures.
|
11
|
-
|
12
|
-
|
13
|
-
List of Functions:
|
14
|
-
-----------------
|
15
|
-
get_mesh_area_names()
|
16
|
-
Returns list of 2D mesh area names
|
17
|
-
get_mesh_areas()
|
18
|
-
Returns 2D flow area perimeter polygons
|
19
|
-
get_mesh_cell_polygons()
|
20
|
-
Returns 2D flow mesh cell polygons
|
21
|
-
get_mesh_cell_points()
|
22
|
-
Returns 2D flow mesh cell center points
|
23
|
-
get_mesh_cell_faces()
|
24
|
-
Returns 2D flow mesh cell faces
|
25
|
-
get_mesh_area_attributes()
|
26
|
-
Returns geometry 2D flow area attributes
|
27
|
-
get_mesh_face_property_tables()
|
28
|
-
Returns Face Property Tables for each Face in all 2D Flow Areas
|
29
|
-
get_mesh_cell_property_tables()
|
30
|
-
Returns Cell Property Tables for each Cell in all 2D Flow Areas
|
31
|
-
|
32
|
-
Each function is decorated with @standardize_input and @log_call for consistent
|
33
|
-
input handling and logging functionality.
|
34
|
-
"""
|
35
|
-
from pathlib import Path
|
36
|
-
import h5py
|
37
|
-
import numpy as np
|
38
|
-
import pandas as pd
|
39
|
-
from geopandas import GeoDataFrame
|
40
|
-
from shapely.geometry import Polygon, Point, LineString, MultiLineString, MultiPolygon
|
41
|
-
from shapely.ops import polygonize # Importing polygonize to resolve the undefined name error
|
42
|
-
from typing import List, Tuple, Optional, Dict, Any
|
43
|
-
import logging
|
44
|
-
from .HdfBase import HdfBase
|
45
|
-
from .HdfUtils import HdfUtils
|
46
|
-
from .Decorators import standardize_input, log_call
|
47
|
-
from .LoggingConfig import setup_logging, get_logger
|
48
|
-
|
49
|
-
logger = get_logger(__name__)
|
50
|
-
|
51
|
-
|
52
|
-
class HdfMesh:
|
53
|
-
"""
|
54
|
-
A class for handling mesh-related operations on HEC-RAS HDF files.
|
55
|
-
|
56
|
-
This class provides methods to extract and analyze mesh data from HEC-RAS HDF files,
|
57
|
-
including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
|
58
|
-
2D flow area attributes.
|
59
|
-
|
60
|
-
Methods in this class are designed to work with the mesh geometry data stored in
|
61
|
-
HEC-RAS HDF files, providing functionality to retrieve and process various aspects
|
62
|
-
of the 2D flow areas and their associated mesh structures.
|
63
|
-
|
64
|
-
Note: This class relies on HdfBase and HdfUtils for some underlying operations.
|
65
|
-
"""
|
66
|
-
|
67
|
-
def __init__(self):
|
68
|
-
self.logger = logging.getLogger(__name__)
|
69
|
-
|
70
|
-
@staticmethod
|
71
|
-
@standardize_input(file_type='plan_hdf')
|
72
|
-
def get_mesh_area_names(hdf_path: Path) -> List[str]:
|
73
|
-
"""
|
74
|
-
Return a list of the 2D mesh area names from the RAS geometry.
|
75
|
-
|
76
|
-
Parameters
|
77
|
-
----------
|
78
|
-
hdf_path : Path
|
79
|
-
Path to the HEC-RAS geometry HDF file.
|
80
|
-
|
81
|
-
Returns
|
82
|
-
-------
|
83
|
-
List[str]
|
84
|
-
A list of the 2D mesh area names within the RAS geometry.
|
85
|
-
Returns an empty list if no 2D areas exist or if there's an error.
|
86
|
-
"""
|
87
|
-
try:
|
88
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
89
|
-
if "Geometry/2D Flow Areas" not in hdf_file:
|
90
|
-
return list()
|
91
|
-
return list(
|
92
|
-
[
|
93
|
-
HdfUtils.convert_ras_string(n.decode('utf-8'))
|
94
|
-
for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
|
95
|
-
]
|
96
|
-
)
|
97
|
-
except Exception as e:
|
98
|
-
|
99
|
-
return list()
|
100
|
-
|
101
|
-
@staticmethod
|
102
|
-
@standardize_input(file_type='geom_hdf')
|
103
|
-
def get_mesh_areas(hdf_path: Path) -> GeoDataFrame:
|
104
|
-
"""
|
105
|
-
Return 2D flow area perimeter polygons.
|
106
|
-
|
107
|
-
Parameters
|
108
|
-
----------
|
109
|
-
hdf_path : Path
|
110
|
-
Path to the HEC-RAS geometry HDF file.
|
111
|
-
|
112
|
-
Returns
|
113
|
-
-------
|
114
|
-
GeoDataFrame
|
115
|
-
A GeoDataFrame containing the 2D flow area perimeter polygons if 2D areas exist.
|
116
|
-
"""
|
117
|
-
try:
|
118
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
119
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
120
|
-
if not mesh_area_names:
|
121
|
-
return GeoDataFrame()
|
122
|
-
mesh_area_polygons = [
|
123
|
-
Polygon(hdf_file["Geometry/2D Flow Areas/{}/Perimeter".format(n)][()])
|
124
|
-
for n in mesh_area_names
|
125
|
-
]
|
126
|
-
return GeoDataFrame(
|
127
|
-
{"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
|
128
|
-
geometry="geometry",
|
129
|
-
crs=HdfBase.get_projection(hdf_file),
|
130
|
-
)
|
131
|
-
except Exception as e:
|
132
|
-
logger.error(f"Error reading mesh areas from {hdf_path}: {str(e)}")
|
133
|
-
return GeoDataFrame()
|
134
|
-
|
135
|
-
@staticmethod
|
136
|
-
@standardize_input(file_type='geom_hdf')
|
137
|
-
def get_mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
|
138
|
-
"""
|
139
|
-
Return 2D flow mesh cell polygons.
|
140
|
-
|
141
|
-
Parameters
|
142
|
-
----------
|
143
|
-
hdf_path : Path
|
144
|
-
Path to the HEC-RAS geometry HDF file.
|
145
|
-
|
146
|
-
Returns
|
147
|
-
-------
|
148
|
-
GeoDataFrame
|
149
|
-
A GeoDataFrame containing the 2D flow mesh cell polygons with columns:
|
150
|
-
- mesh_name: name of the mesh area
|
151
|
-
- cell_id: unique identifier for each cell
|
152
|
-
- geometry: polygon geometry of the cell
|
153
|
-
Returns an empty GeoDataFrame if no 2D areas exist or if there's an error.
|
154
|
-
"""
|
155
|
-
try:
|
156
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
157
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
158
|
-
if not mesh_area_names:
|
159
|
-
return GeoDataFrame()
|
160
|
-
|
161
|
-
# Get face geometries once
|
162
|
-
face_gdf = HdfMesh.get_mesh_cell_faces(hdf_path)
|
163
|
-
|
164
|
-
# Pre-allocate lists for better memory efficiency
|
165
|
-
all_mesh_names = []
|
166
|
-
all_cell_ids = []
|
167
|
-
all_geometries = []
|
168
|
-
|
169
|
-
for mesh_name in mesh_area_names:
|
170
|
-
# Get cell face info in one read
|
171
|
-
cell_face_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"][()]
|
172
|
-
cell_face_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"][()][:, 0]
|
173
|
-
|
174
|
-
# Create face lookup dictionary for this mesh
|
175
|
-
mesh_faces_dict = dict(face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]].values)
|
176
|
-
|
177
|
-
# Process each cell
|
178
|
-
for cell_id, (start, length) in enumerate(cell_face_info[:, :2]):
|
179
|
-
face_ids = cell_face_values[start:start + length]
|
180
|
-
face_geoms = [mesh_faces_dict[face_id] for face_id in face_ids]
|
181
|
-
|
182
|
-
# Create polygon
|
183
|
-
polygons = list(polygonize(face_geoms))
|
184
|
-
if polygons:
|
185
|
-
all_mesh_names.append(mesh_name)
|
186
|
-
all_cell_ids.append(cell_id)
|
187
|
-
all_geometries.append(Polygon(polygons[0]))
|
188
|
-
|
189
|
-
# Create GeoDataFrame in one go
|
190
|
-
return GeoDataFrame(
|
191
|
-
{
|
192
|
-
"mesh_name": all_mesh_names,
|
193
|
-
"cell_id": all_cell_ids,
|
194
|
-
"geometry": all_geometries
|
195
|
-
},
|
196
|
-
geometry="geometry",
|
197
|
-
crs=HdfBase.get_projection(hdf_file)
|
198
|
-
)
|
199
|
-
|
200
|
-
except Exception as e:
|
201
|
-
logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
|
202
|
-
return GeoDataFrame()
|
203
|
-
|
204
|
-
@staticmethod
|
205
|
-
@standardize_input(file_type='plan_hdf')
|
206
|
-
def get_mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
|
207
|
-
"""
|
208
|
-
Return 2D flow mesh cell center points.
|
209
|
-
|
210
|
-
Parameters
|
211
|
-
----------
|
212
|
-
hdf_path : Path
|
213
|
-
Path to the HEC-RAS geometry HDF file.
|
214
|
-
|
215
|
-
Returns
|
216
|
-
-------
|
217
|
-
GeoDataFrame
|
218
|
-
A GeoDataFrame containing the 2D flow mesh cell center points.
|
219
|
-
"""
|
220
|
-
try:
|
221
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
222
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
223
|
-
if not mesh_area_names:
|
224
|
-
return GeoDataFrame()
|
225
|
-
|
226
|
-
# Pre-allocate lists
|
227
|
-
all_mesh_names = []
|
228
|
-
all_cell_ids = []
|
229
|
-
all_points = []
|
230
|
-
|
231
|
-
for mesh_name in mesh_area_names:
|
232
|
-
# Get all cell centers in one read
|
233
|
-
cell_centers = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
|
234
|
-
cell_count = len(cell_centers)
|
235
|
-
|
236
|
-
# Extend lists efficiently
|
237
|
-
all_mesh_names.extend([mesh_name] * cell_count)
|
238
|
-
all_cell_ids.extend(range(cell_count))
|
239
|
-
all_points.extend(Point(coords) for coords in cell_centers)
|
240
|
-
|
241
|
-
# Create GeoDataFrame in one go
|
242
|
-
return GeoDataFrame(
|
243
|
-
{
|
244
|
-
"mesh_name": all_mesh_names,
|
245
|
-
"cell_id": all_cell_ids,
|
246
|
-
"geometry": all_points
|
247
|
-
},
|
248
|
-
geometry="geometry",
|
249
|
-
crs=HdfBase.get_projection(hdf_file)
|
250
|
-
)
|
251
|
-
|
252
|
-
except Exception as e:
|
253
|
-
logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
|
254
|
-
return GeoDataFrame()
|
255
|
-
|
256
|
-
@staticmethod
|
257
|
-
@standardize_input(file_type='plan_hdf')
|
258
|
-
def get_mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
|
259
|
-
"""
|
260
|
-
Return 2D flow mesh cell faces.
|
261
|
-
|
262
|
-
Parameters
|
263
|
-
----------
|
264
|
-
hdf_path : Path
|
265
|
-
Path to the HEC-RAS geometry HDF file.
|
266
|
-
|
267
|
-
Returns
|
268
|
-
-------
|
269
|
-
GeoDataFrame
|
270
|
-
A GeoDataFrame containing the 2D flow mesh cell faces.
|
271
|
-
"""
|
272
|
-
try:
|
273
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
274
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
275
|
-
if not mesh_area_names:
|
276
|
-
return GeoDataFrame()
|
277
|
-
|
278
|
-
# Pre-allocate lists
|
279
|
-
all_mesh_names = []
|
280
|
-
all_face_ids = []
|
281
|
-
all_geometries = []
|
282
|
-
|
283
|
-
for mesh_name in mesh_area_names:
|
284
|
-
# Read all data at once
|
285
|
-
facepoints_index = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"][()]
|
286
|
-
facepoints_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"][()]
|
287
|
-
faces_perim_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"][()]
|
288
|
-
faces_perim_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"][()]
|
289
|
-
|
290
|
-
# Process each face
|
291
|
-
for face_id, ((pnt_a_idx, pnt_b_idx), (start_row, count)) in enumerate(zip(facepoints_index, faces_perim_info)):
|
292
|
-
coords = [facepoints_coords[pnt_a_idx]]
|
293
|
-
|
294
|
-
if count > 0:
|
295
|
-
coords.extend(faces_perim_values[start_row:start_row + count])
|
296
|
-
|
297
|
-
coords.append(facepoints_coords[pnt_b_idx])
|
298
|
-
|
299
|
-
all_mesh_names.append(mesh_name)
|
300
|
-
all_face_ids.append(face_id)
|
301
|
-
all_geometries.append(LineString(coords))
|
302
|
-
|
303
|
-
# Create GeoDataFrame in one go
|
304
|
-
return GeoDataFrame(
|
305
|
-
{
|
306
|
-
"mesh_name": all_mesh_names,
|
307
|
-
"face_id": all_face_ids,
|
308
|
-
"geometry": all_geometries
|
309
|
-
},
|
310
|
-
geometry="geometry",
|
311
|
-
crs=HdfBase.get_projection(hdf_file)
|
312
|
-
)
|
313
|
-
|
314
|
-
except Exception as e:
|
315
|
-
logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
|
316
|
-
return GeoDataFrame()
|
317
|
-
|
318
|
-
@staticmethod
|
319
|
-
@standardize_input(file_type='geom_hdf')
|
320
|
-
def get_mesh_area_attributes(hdf_path: Path) -> pd.DataFrame:
|
321
|
-
"""
|
322
|
-
Return geometry 2D flow area attributes from a HEC-RAS HDF file.
|
323
|
-
|
324
|
-
Parameters
|
325
|
-
----------
|
326
|
-
hdf_path : Path
|
327
|
-
Path to the HEC-RAS geometry HDF file.
|
328
|
-
|
329
|
-
Returns
|
330
|
-
-------
|
331
|
-
pd.DataFrame
|
332
|
-
A DataFrame containing the 2D flow area attributes.
|
333
|
-
"""
|
334
|
-
try:
|
335
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
336
|
-
d2_flow_area = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
337
|
-
if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
|
338
|
-
result = {}
|
339
|
-
for name in d2_flow_area.dtype.names:
|
340
|
-
try:
|
341
|
-
value = d2_flow_area[name][()]
|
342
|
-
if isinstance(value, bytes):
|
343
|
-
value = value.decode('utf-8') # Decode as UTF-8
|
344
|
-
result[name] = value if not isinstance(value, bytes) else value.decode('utf-8')
|
345
|
-
except Exception as e:
|
346
|
-
logger.warning(f"Error converting attribute '{name}': {str(e)}")
|
347
|
-
return pd.DataFrame.from_dict(result, orient='index', columns=['Value'])
|
348
|
-
else:
|
349
|
-
logger.info("No 2D Flow Area attributes found or invalid dataset.")
|
350
|
-
return pd.DataFrame() # Return an empty DataFrame
|
351
|
-
except Exception as e:
|
352
|
-
logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
|
353
|
-
return pd.DataFrame() # Return an empty DataFrame
|
354
|
-
|
355
|
-
@staticmethod
|
356
|
-
@standardize_input(file_type='geom_hdf')
|
357
|
-
def get_mesh_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
358
|
-
"""
|
359
|
-
Extract Face Property Tables for each Face in all 2D Flow Areas.
|
360
|
-
|
361
|
-
Parameters
|
362
|
-
----------
|
363
|
-
hdf_path : Path
|
364
|
-
Path to the HEC-RAS geometry HDF file.
|
365
|
-
|
366
|
-
Returns
|
367
|
-
-------
|
368
|
-
Dict[str, pd.DataFrame]
|
369
|
-
A dictionary where:
|
370
|
-
- keys: mesh area names (str)
|
371
|
-
- values: DataFrames with columns:
|
372
|
-
- Face ID: unique identifier for each face
|
373
|
-
- Z: elevation
|
374
|
-
- Area: face area
|
375
|
-
- Wetted Perimeter: wetted perimeter length
|
376
|
-
- Manning's n: Manning's roughness coefficient
|
377
|
-
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
378
|
-
"""
|
379
|
-
try:
|
380
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
381
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
382
|
-
if not mesh_area_names:
|
383
|
-
return {}
|
384
|
-
|
385
|
-
result = {}
|
386
|
-
for mesh_name in mesh_area_names:
|
387
|
-
area_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Info"][()]
|
388
|
-
area_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Values"][()]
|
389
|
-
|
390
|
-
face_data = []
|
391
|
-
for face_id, (start_index, count) in enumerate(area_elevation_info):
|
392
|
-
face_values = area_elevation_values[start_index:start_index+count]
|
393
|
-
for z, area, wetted_perimeter, mannings_n in face_values:
|
394
|
-
face_data.append({
|
395
|
-
'Face ID': face_id,
|
396
|
-
'Z': str(z),
|
397
|
-
'Area': str(area),
|
398
|
-
'Wetted Perimeter': str(wetted_perimeter),
|
399
|
-
"Manning's n": str(mannings_n)
|
400
|
-
})
|
401
|
-
|
402
|
-
result[mesh_name] = pd.DataFrame(face_data)
|
403
|
-
|
404
|
-
return result
|
405
|
-
|
406
|
-
except Exception as e:
|
407
|
-
logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
|
408
|
-
return {}
|
409
|
-
|
410
|
-
@staticmethod
|
411
|
-
@standardize_input(file_type='geom_hdf')
|
412
|
-
def get_mesh_cell_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
413
|
-
"""
|
414
|
-
Extract Cell Property Tables for each Cell in all 2D Flow Areas.
|
415
|
-
|
416
|
-
Parameters
|
417
|
-
----------
|
418
|
-
hdf_path : Path
|
419
|
-
Path to the HEC-RAS geometry HDF file.
|
420
|
-
|
421
|
-
Returns
|
422
|
-
-------
|
423
|
-
Dict[str, pd.DataFrame]
|
424
|
-
A dictionary where:
|
425
|
-
- keys: mesh area names (str)
|
426
|
-
- values: DataFrames with columns:
|
427
|
-
- Cell ID: unique identifier for each cell
|
428
|
-
- Z: elevation
|
429
|
-
- Volume: cell volume
|
430
|
-
- Surface Area: cell surface area
|
431
|
-
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
432
|
-
"""
|
433
|
-
try:
|
434
|
-
with h5py.File(hdf_path, 'r') as hdf_file:
|
435
|
-
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
436
|
-
if not mesh_area_names:
|
437
|
-
return {}
|
438
|
-
|
439
|
-
result = {}
|
440
|
-
for mesh_name in mesh_area_names:
|
441
|
-
cell_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Info"][()]
|
442
|
-
cell_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Values"][()]
|
443
|
-
|
444
|
-
cell_data = []
|
445
|
-
for cell_id, (start_index, count) in enumerate(cell_elevation_info):
|
446
|
-
cell_values = cell_elevation_values[start_index:start_index+count]
|
447
|
-
for z, volume, surface_area in cell_values:
|
448
|
-
cell_data.append({
|
449
|
-
'Cell ID': cell_id,
|
450
|
-
'Z': str(z),
|
451
|
-
'Volume': str(volume),
|
452
|
-
'Surface Area': str(surface_area)
|
453
|
-
})
|
454
|
-
|
455
|
-
result[mesh_name] = pd.DataFrame(cell_data)
|
456
|
-
|
457
|
-
return result
|
458
|
-
|
459
|
-
except Exception as e:
|
460
|
-
logger.error(f"Error extracting cell property tables from {hdf_path}: {str(e)}")
|
461
|
-
return {}
|
1
|
+
"""
|
2
|
+
A static class for handling mesh-related operations on HEC-RAS HDF files.
|
3
|
+
|
4
|
+
This class provides static methods to extract and analyze mesh data from HEC-RAS HDF files,
|
5
|
+
including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
|
6
|
+
2D flow area attributes. No instantiation is required to use these methods.
|
7
|
+
|
8
|
+
All methods are designed to work with the mesh geometry data stored in
|
9
|
+
HEC-RAS HDF files, providing functionality to retrieve and process various aspects
|
10
|
+
of the 2D flow areas and their associated mesh structures.
|
11
|
+
|
12
|
+
|
13
|
+
List of Functions:
|
14
|
+
-----------------
|
15
|
+
get_mesh_area_names()
|
16
|
+
Returns list of 2D mesh area names
|
17
|
+
get_mesh_areas()
|
18
|
+
Returns 2D flow area perimeter polygons
|
19
|
+
get_mesh_cell_polygons()
|
20
|
+
Returns 2D flow mesh cell polygons
|
21
|
+
get_mesh_cell_points()
|
22
|
+
Returns 2D flow mesh cell center points
|
23
|
+
get_mesh_cell_faces()
|
24
|
+
Returns 2D flow mesh cell faces
|
25
|
+
get_mesh_area_attributes()
|
26
|
+
Returns geometry 2D flow area attributes
|
27
|
+
get_mesh_face_property_tables()
|
28
|
+
Returns Face Property Tables for each Face in all 2D Flow Areas
|
29
|
+
get_mesh_cell_property_tables()
|
30
|
+
Returns Cell Property Tables for each Cell in all 2D Flow Areas
|
31
|
+
|
32
|
+
Each function is decorated with @standardize_input and @log_call for consistent
|
33
|
+
input handling and logging functionality.
|
34
|
+
"""
|
35
|
+
from pathlib import Path
|
36
|
+
import h5py
|
37
|
+
import numpy as np
|
38
|
+
import pandas as pd
|
39
|
+
from geopandas import GeoDataFrame
|
40
|
+
from shapely.geometry import Polygon, Point, LineString, MultiLineString, MultiPolygon
|
41
|
+
from shapely.ops import polygonize # Importing polygonize to resolve the undefined name error
|
42
|
+
from typing import List, Tuple, Optional, Dict, Any
|
43
|
+
import logging
|
44
|
+
from .HdfBase import HdfBase
|
45
|
+
from .HdfUtils import HdfUtils
|
46
|
+
from .Decorators import standardize_input, log_call
|
47
|
+
from .LoggingConfig import setup_logging, get_logger
|
48
|
+
|
49
|
+
logger = get_logger(__name__)
|
50
|
+
|
51
|
+
|
52
|
+
class HdfMesh:
|
53
|
+
"""
|
54
|
+
A class for handling mesh-related operations on HEC-RAS HDF files.
|
55
|
+
|
56
|
+
This class provides methods to extract and analyze mesh data from HEC-RAS HDF files,
|
57
|
+
including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
|
58
|
+
2D flow area attributes.
|
59
|
+
|
60
|
+
Methods in this class are designed to work with the mesh geometry data stored in
|
61
|
+
HEC-RAS HDF files, providing functionality to retrieve and process various aspects
|
62
|
+
of the 2D flow areas and their associated mesh structures.
|
63
|
+
|
64
|
+
Note: This class relies on HdfBase and HdfUtils for some underlying operations.
|
65
|
+
"""
|
66
|
+
|
67
|
+
def __init__(self):
|
68
|
+
self.logger = logging.getLogger(__name__)
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
@standardize_input(file_type='plan_hdf')
|
72
|
+
def get_mesh_area_names(hdf_path: Path) -> List[str]:
|
73
|
+
"""
|
74
|
+
Return a list of the 2D mesh area names from the RAS geometry.
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
hdf_path : Path
|
79
|
+
Path to the HEC-RAS geometry HDF file.
|
80
|
+
|
81
|
+
Returns
|
82
|
+
-------
|
83
|
+
List[str]
|
84
|
+
A list of the 2D mesh area names within the RAS geometry.
|
85
|
+
Returns an empty list if no 2D areas exist or if there's an error.
|
86
|
+
"""
|
87
|
+
try:
|
88
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
89
|
+
if "Geometry/2D Flow Areas" not in hdf_file:
|
90
|
+
return list()
|
91
|
+
return list(
|
92
|
+
[
|
93
|
+
HdfUtils.convert_ras_string(n.decode('utf-8'))
|
94
|
+
for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
|
95
|
+
]
|
96
|
+
)
|
97
|
+
except Exception as e:
|
98
|
+
logger.error(f"Error reading mesh area names from {hdf_path}: {str(e)}")
|
99
|
+
return list()
|
100
|
+
|
101
|
+
@staticmethod
|
102
|
+
@standardize_input(file_type='geom_hdf')
|
103
|
+
def get_mesh_areas(hdf_path: Path) -> GeoDataFrame:
|
104
|
+
"""
|
105
|
+
Return 2D flow area perimeter polygons.
|
106
|
+
|
107
|
+
Parameters
|
108
|
+
----------
|
109
|
+
hdf_path : Path
|
110
|
+
Path to the HEC-RAS geometry HDF file.
|
111
|
+
|
112
|
+
Returns
|
113
|
+
-------
|
114
|
+
GeoDataFrame
|
115
|
+
A GeoDataFrame containing the 2D flow area perimeter polygons if 2D areas exist.
|
116
|
+
"""
|
117
|
+
try:
|
118
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
119
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
120
|
+
if not mesh_area_names:
|
121
|
+
return GeoDataFrame()
|
122
|
+
mesh_area_polygons = [
|
123
|
+
Polygon(hdf_file["Geometry/2D Flow Areas/{}/Perimeter".format(n)][()])
|
124
|
+
for n in mesh_area_names
|
125
|
+
]
|
126
|
+
return GeoDataFrame(
|
127
|
+
{"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
|
128
|
+
geometry="geometry",
|
129
|
+
crs=HdfBase.get_projection(hdf_file),
|
130
|
+
)
|
131
|
+
except Exception as e:
|
132
|
+
logger.error(f"Error reading mesh areas from {hdf_path}: {str(e)}")
|
133
|
+
return GeoDataFrame()
|
134
|
+
|
135
|
+
@staticmethod
|
136
|
+
@standardize_input(file_type='geom_hdf')
|
137
|
+
def get_mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
|
138
|
+
"""
|
139
|
+
Return 2D flow mesh cell polygons.
|
140
|
+
|
141
|
+
Parameters
|
142
|
+
----------
|
143
|
+
hdf_path : Path
|
144
|
+
Path to the HEC-RAS geometry HDF file.
|
145
|
+
|
146
|
+
Returns
|
147
|
+
-------
|
148
|
+
GeoDataFrame
|
149
|
+
A GeoDataFrame containing the 2D flow mesh cell polygons with columns:
|
150
|
+
- mesh_name: name of the mesh area
|
151
|
+
- cell_id: unique identifier for each cell
|
152
|
+
- geometry: polygon geometry of the cell
|
153
|
+
Returns an empty GeoDataFrame if no 2D areas exist or if there's an error.
|
154
|
+
"""
|
155
|
+
try:
|
156
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
157
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
158
|
+
if not mesh_area_names:
|
159
|
+
return GeoDataFrame()
|
160
|
+
|
161
|
+
# Get face geometries once
|
162
|
+
face_gdf = HdfMesh.get_mesh_cell_faces(hdf_path)
|
163
|
+
|
164
|
+
# Pre-allocate lists for better memory efficiency
|
165
|
+
all_mesh_names = []
|
166
|
+
all_cell_ids = []
|
167
|
+
all_geometries = []
|
168
|
+
|
169
|
+
for mesh_name in mesh_area_names:
|
170
|
+
# Get cell face info in one read
|
171
|
+
cell_face_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"][()]
|
172
|
+
cell_face_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"][()][:, 0]
|
173
|
+
|
174
|
+
# Create face lookup dictionary for this mesh
|
175
|
+
mesh_faces_dict = dict(face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]].values)
|
176
|
+
|
177
|
+
# Process each cell
|
178
|
+
for cell_id, (start, length) in enumerate(cell_face_info[:, :2]):
|
179
|
+
face_ids = cell_face_values[start:start + length]
|
180
|
+
face_geoms = [mesh_faces_dict[face_id] for face_id in face_ids]
|
181
|
+
|
182
|
+
# Create polygon
|
183
|
+
polygons = list(polygonize(face_geoms))
|
184
|
+
if polygons:
|
185
|
+
all_mesh_names.append(mesh_name)
|
186
|
+
all_cell_ids.append(cell_id)
|
187
|
+
all_geometries.append(Polygon(polygons[0]))
|
188
|
+
|
189
|
+
# Create GeoDataFrame in one go
|
190
|
+
return GeoDataFrame(
|
191
|
+
{
|
192
|
+
"mesh_name": all_mesh_names,
|
193
|
+
"cell_id": all_cell_ids,
|
194
|
+
"geometry": all_geometries
|
195
|
+
},
|
196
|
+
geometry="geometry",
|
197
|
+
crs=HdfBase.get_projection(hdf_file)
|
198
|
+
)
|
199
|
+
|
200
|
+
except Exception as e:
|
201
|
+
logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
|
202
|
+
return GeoDataFrame()
|
203
|
+
|
204
|
+
@staticmethod
|
205
|
+
@standardize_input(file_type='plan_hdf')
|
206
|
+
def get_mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
|
207
|
+
"""
|
208
|
+
Return 2D flow mesh cell center points.
|
209
|
+
|
210
|
+
Parameters
|
211
|
+
----------
|
212
|
+
hdf_path : Path
|
213
|
+
Path to the HEC-RAS geometry HDF file.
|
214
|
+
|
215
|
+
Returns
|
216
|
+
-------
|
217
|
+
GeoDataFrame
|
218
|
+
A GeoDataFrame containing the 2D flow mesh cell center points.
|
219
|
+
"""
|
220
|
+
try:
|
221
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
222
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
223
|
+
if not mesh_area_names:
|
224
|
+
return GeoDataFrame()
|
225
|
+
|
226
|
+
# Pre-allocate lists
|
227
|
+
all_mesh_names = []
|
228
|
+
all_cell_ids = []
|
229
|
+
all_points = []
|
230
|
+
|
231
|
+
for mesh_name in mesh_area_names:
|
232
|
+
# Get all cell centers in one read
|
233
|
+
cell_centers = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
|
234
|
+
cell_count = len(cell_centers)
|
235
|
+
|
236
|
+
# Extend lists efficiently
|
237
|
+
all_mesh_names.extend([mesh_name] * cell_count)
|
238
|
+
all_cell_ids.extend(range(cell_count))
|
239
|
+
all_points.extend(Point(coords) for coords in cell_centers)
|
240
|
+
|
241
|
+
# Create GeoDataFrame in one go
|
242
|
+
return GeoDataFrame(
|
243
|
+
{
|
244
|
+
"mesh_name": all_mesh_names,
|
245
|
+
"cell_id": all_cell_ids,
|
246
|
+
"geometry": all_points
|
247
|
+
},
|
248
|
+
geometry="geometry",
|
249
|
+
crs=HdfBase.get_projection(hdf_file)
|
250
|
+
)
|
251
|
+
|
252
|
+
except Exception as e:
|
253
|
+
logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
|
254
|
+
return GeoDataFrame()
|
255
|
+
|
256
|
+
@staticmethod
|
257
|
+
@standardize_input(file_type='plan_hdf')
|
258
|
+
def get_mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
|
259
|
+
"""
|
260
|
+
Return 2D flow mesh cell faces.
|
261
|
+
|
262
|
+
Parameters
|
263
|
+
----------
|
264
|
+
hdf_path : Path
|
265
|
+
Path to the HEC-RAS geometry HDF file.
|
266
|
+
|
267
|
+
Returns
|
268
|
+
-------
|
269
|
+
GeoDataFrame
|
270
|
+
A GeoDataFrame containing the 2D flow mesh cell faces.
|
271
|
+
"""
|
272
|
+
try:
|
273
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
274
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
275
|
+
if not mesh_area_names:
|
276
|
+
return GeoDataFrame()
|
277
|
+
|
278
|
+
# Pre-allocate lists
|
279
|
+
all_mesh_names = []
|
280
|
+
all_face_ids = []
|
281
|
+
all_geometries = []
|
282
|
+
|
283
|
+
for mesh_name in mesh_area_names:
|
284
|
+
# Read all data at once
|
285
|
+
facepoints_index = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"][()]
|
286
|
+
facepoints_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"][()]
|
287
|
+
faces_perim_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"][()]
|
288
|
+
faces_perim_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"][()]
|
289
|
+
|
290
|
+
# Process each face
|
291
|
+
for face_id, ((pnt_a_idx, pnt_b_idx), (start_row, count)) in enumerate(zip(facepoints_index, faces_perim_info)):
|
292
|
+
coords = [facepoints_coords[pnt_a_idx]]
|
293
|
+
|
294
|
+
if count > 0:
|
295
|
+
coords.extend(faces_perim_values[start_row:start_row + count])
|
296
|
+
|
297
|
+
coords.append(facepoints_coords[pnt_b_idx])
|
298
|
+
|
299
|
+
all_mesh_names.append(mesh_name)
|
300
|
+
all_face_ids.append(face_id)
|
301
|
+
all_geometries.append(LineString(coords))
|
302
|
+
|
303
|
+
# Create GeoDataFrame in one go
|
304
|
+
return GeoDataFrame(
|
305
|
+
{
|
306
|
+
"mesh_name": all_mesh_names,
|
307
|
+
"face_id": all_face_ids,
|
308
|
+
"geometry": all_geometries
|
309
|
+
},
|
310
|
+
geometry="geometry",
|
311
|
+
crs=HdfBase.get_projection(hdf_file)
|
312
|
+
)
|
313
|
+
|
314
|
+
except Exception as e:
|
315
|
+
logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
|
316
|
+
return GeoDataFrame()
|
317
|
+
|
318
|
+
@staticmethod
|
319
|
+
@standardize_input(file_type='geom_hdf')
|
320
|
+
def get_mesh_area_attributes(hdf_path: Path) -> pd.DataFrame:
|
321
|
+
"""
|
322
|
+
Return geometry 2D flow area attributes from a HEC-RAS HDF file.
|
323
|
+
|
324
|
+
Parameters
|
325
|
+
----------
|
326
|
+
hdf_path : Path
|
327
|
+
Path to the HEC-RAS geometry HDF file.
|
328
|
+
|
329
|
+
Returns
|
330
|
+
-------
|
331
|
+
pd.DataFrame
|
332
|
+
A DataFrame containing the 2D flow area attributes.
|
333
|
+
"""
|
334
|
+
try:
|
335
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
336
|
+
d2_flow_area = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
337
|
+
if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
|
338
|
+
result = {}
|
339
|
+
for name in d2_flow_area.dtype.names:
|
340
|
+
try:
|
341
|
+
value = d2_flow_area[name][()]
|
342
|
+
if isinstance(value, bytes):
|
343
|
+
value = value.decode('utf-8') # Decode as UTF-8
|
344
|
+
result[name] = value if not isinstance(value, bytes) else value.decode('utf-8')
|
345
|
+
except Exception as e:
|
346
|
+
logger.warning(f"Error converting attribute '{name}': {str(e)}")
|
347
|
+
return pd.DataFrame.from_dict(result, orient='index', columns=['Value'])
|
348
|
+
else:
|
349
|
+
logger.info("No 2D Flow Area attributes found or invalid dataset.")
|
350
|
+
return pd.DataFrame() # Return an empty DataFrame
|
351
|
+
except Exception as e:
|
352
|
+
logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
|
353
|
+
return pd.DataFrame() # Return an empty DataFrame
|
354
|
+
|
355
|
+
@staticmethod
|
356
|
+
@standardize_input(file_type='geom_hdf')
|
357
|
+
def get_mesh_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
358
|
+
"""
|
359
|
+
Extract Face Property Tables for each Face in all 2D Flow Areas.
|
360
|
+
|
361
|
+
Parameters
|
362
|
+
----------
|
363
|
+
hdf_path : Path
|
364
|
+
Path to the HEC-RAS geometry HDF file.
|
365
|
+
|
366
|
+
Returns
|
367
|
+
-------
|
368
|
+
Dict[str, pd.DataFrame]
|
369
|
+
A dictionary where:
|
370
|
+
- keys: mesh area names (str)
|
371
|
+
- values: DataFrames with columns:
|
372
|
+
- Face ID: unique identifier for each face
|
373
|
+
- Z: elevation
|
374
|
+
- Area: face area
|
375
|
+
- Wetted Perimeter: wetted perimeter length
|
376
|
+
- Manning's n: Manning's roughness coefficient
|
377
|
+
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
378
|
+
"""
|
379
|
+
try:
|
380
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
381
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
382
|
+
if not mesh_area_names:
|
383
|
+
return {}
|
384
|
+
|
385
|
+
result = {}
|
386
|
+
for mesh_name in mesh_area_names:
|
387
|
+
area_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Info"][()]
|
388
|
+
area_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Values"][()]
|
389
|
+
|
390
|
+
face_data = []
|
391
|
+
for face_id, (start_index, count) in enumerate(area_elevation_info):
|
392
|
+
face_values = area_elevation_values[start_index:start_index+count]
|
393
|
+
for z, area, wetted_perimeter, mannings_n in face_values:
|
394
|
+
face_data.append({
|
395
|
+
'Face ID': face_id,
|
396
|
+
'Z': str(z),
|
397
|
+
'Area': str(area),
|
398
|
+
'Wetted Perimeter': str(wetted_perimeter),
|
399
|
+
"Manning's n": str(mannings_n)
|
400
|
+
})
|
401
|
+
|
402
|
+
result[mesh_name] = pd.DataFrame(face_data)
|
403
|
+
|
404
|
+
return result
|
405
|
+
|
406
|
+
except Exception as e:
|
407
|
+
logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
|
408
|
+
return {}
|
409
|
+
|
410
|
+
@staticmethod
|
411
|
+
@standardize_input(file_type='geom_hdf')
|
412
|
+
def get_mesh_cell_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
|
413
|
+
"""
|
414
|
+
Extract Cell Property Tables for each Cell in all 2D Flow Areas.
|
415
|
+
|
416
|
+
Parameters
|
417
|
+
----------
|
418
|
+
hdf_path : Path
|
419
|
+
Path to the HEC-RAS geometry HDF file.
|
420
|
+
|
421
|
+
Returns
|
422
|
+
-------
|
423
|
+
Dict[str, pd.DataFrame]
|
424
|
+
A dictionary where:
|
425
|
+
- keys: mesh area names (str)
|
426
|
+
- values: DataFrames with columns:
|
427
|
+
- Cell ID: unique identifier for each cell
|
428
|
+
- Z: elevation
|
429
|
+
- Volume: cell volume
|
430
|
+
- Surface Area: cell surface area
|
431
|
+
Returns an empty dictionary if no 2D areas exist or if there's an error.
|
432
|
+
"""
|
433
|
+
try:
|
434
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
435
|
+
mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
|
436
|
+
if not mesh_area_names:
|
437
|
+
return {}
|
438
|
+
|
439
|
+
result = {}
|
440
|
+
for mesh_name in mesh_area_names:
|
441
|
+
cell_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Info"][()]
|
442
|
+
cell_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Elevation Volume Values"][()]
|
443
|
+
|
444
|
+
cell_data = []
|
445
|
+
for cell_id, (start_index, count) in enumerate(cell_elevation_info):
|
446
|
+
cell_values = cell_elevation_values[start_index:start_index+count]
|
447
|
+
for z, volume, surface_area in cell_values:
|
448
|
+
cell_data.append({
|
449
|
+
'Cell ID': cell_id,
|
450
|
+
'Z': str(z),
|
451
|
+
'Volume': str(volume),
|
452
|
+
'Surface Area': str(surface_area)
|
453
|
+
})
|
454
|
+
|
455
|
+
result[mesh_name] = pd.DataFrame(cell_data)
|
456
|
+
|
457
|
+
return result
|
458
|
+
|
459
|
+
except Exception as e:
|
460
|
+
logger.error(f"Error extracting cell property tables from {hdf_path}: {str(e)}")
|
461
|
+
return {}
|