ras-commander 0.50.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/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
- self.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 {}
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 {}