ras-commander 0.48.0__py3-none-any.whl → 0.50.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,11 +1,36 @@
1
1
  """
2
- Class: HdfMesh
3
-
4
- Attribution: A substantial amount of code in this file is sourced or derived
5
- from the https://github.com/fema-ffrd/rashdf library,
6
- released under MIT license and Copyright (c) 2024 fema-ffrd
7
-
8
- The file has been forked and modified for use in RAS Commander.
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.
9
34
  """
10
35
  from pathlib import Path
11
36
  import h5py
@@ -44,9 +69,9 @@ class HdfMesh:
44
69
 
45
70
  @staticmethod
46
71
  @standardize_input(file_type='plan_hdf')
47
- def mesh_area_names(hdf_path: Path) -> List[str]:
72
+ def get_mesh_area_names(hdf_path: Path) -> List[str]:
48
73
  """
49
- Return a list of the 2D mesh area names of the RAS geometry.
74
+ Return a list of the 2D mesh area names from the RAS geometry.
50
75
 
51
76
  Parameters
52
77
  ----------
@@ -56,7 +81,8 @@ class HdfMesh:
56
81
  Returns
57
82
  -------
58
83
  List[str]
59
- A list of the 2D mesh area names (str) within the RAS geometry if 2D areas exist.
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.
60
86
  """
61
87
  try:
62
88
  with h5py.File(hdf_path, 'r') as hdf_file:
@@ -64,7 +90,7 @@ class HdfMesh:
64
90
  return list()
65
91
  return list(
66
92
  [
67
- HdfUtils.convert_ras_hdf_string(n.decode('utf-8')) # Decode as UTF-8
93
+ HdfUtils.convert_ras_string(n.decode('utf-8'))
68
94
  for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
69
95
  ]
70
96
  )
@@ -74,7 +100,7 @@ class HdfMesh:
74
100
 
75
101
  @staticmethod
76
102
  @standardize_input(file_type='geom_hdf')
77
- def mesh_areas(hdf_path: Path) -> GeoDataFrame:
103
+ def get_mesh_areas(hdf_path: Path) -> GeoDataFrame:
78
104
  """
79
105
  Return 2D flow area perimeter polygons.
80
106
 
@@ -90,7 +116,7 @@ class HdfMesh:
90
116
  """
91
117
  try:
92
118
  with h5py.File(hdf_path, 'r') as hdf_file:
93
- mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
119
+ mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
94
120
  if not mesh_area_names:
95
121
  return GeoDataFrame()
96
122
  mesh_area_polygons = [
@@ -100,7 +126,7 @@ class HdfMesh:
100
126
  return GeoDataFrame(
101
127
  {"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
102
128
  geometry="geometry",
103
- crs=HdfUtils.projection(hdf_file), # Pass the h5py.File object instead of the path
129
+ crs=HdfBase.get_projection(hdf_file),
104
130
  )
105
131
  except Exception as e:
106
132
  logger.error(f"Error reading mesh areas from {hdf_path}: {str(e)}")
@@ -108,7 +134,7 @@ class HdfMesh:
108
134
 
109
135
  @staticmethod
110
136
  @standardize_input(file_type='geom_hdf')
111
- def mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
137
+ def get_mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
112
138
  """
113
139
  Return 2D flow mesh cell polygons.
114
140
 
@@ -120,63 +146,64 @@ class HdfMesh:
120
146
  Returns
121
147
  -------
122
148
  GeoDataFrame
123
- A GeoDataFrame containing the 2D flow mesh cell polygons.
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.
124
154
  """
125
155
  try:
126
156
  with h5py.File(hdf_path, 'r') as hdf_file:
127
- mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
157
+ mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
128
158
  if not mesh_area_names:
129
159
  return GeoDataFrame()
130
160
 
131
- face_gdf = HdfMesh.mesh_cell_faces(hdf_path)
132
-
133
- cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
134
- for i, mesh_name in enumerate(mesh_area_names):
135
- cell_cnt = hdf_file["Geometry/2D Flow Areas/Cell Info"][()][i][1]
136
- cell_ids = list(range(cell_cnt))
137
- cell_face_info = hdf_file[
138
- "Geometry/2D Flow Areas/{}/Cells Face and Orientation Info".format(mesh_name)
139
- ][()]
140
- cell_face_values = hdf_file[
141
- "Geometry/2D Flow Areas/{}/Cells Face and Orientation Values".format(mesh_name)
142
- ][()][:, 0]
143
- face_id_lists = list(
144
- np.vectorize(
145
- lambda cell_id: str(
146
- cell_face_values[
147
- cell_face_info[cell_id][0] : cell_face_info[cell_id][0]
148
- + cell_face_info[cell_id][1]
149
- ]
150
- )
151
- )(cell_ids)
152
- )
153
- mesh_faces = (
154
- face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]]
155
- .set_index("face_id")
156
- .to_numpy()
157
- )
158
- cell_dict["mesh_name"] += [mesh_name] * cell_cnt
159
- cell_dict["cell_id"] += cell_ids
160
- cell_dict["geometry"] += list(
161
- np.vectorize(
162
- lambda face_id_list: (
163
- lambda geom_col: Polygon(list(polygonize(geom_col))[0])
164
- )(
165
- np.ravel(
166
- mesh_faces[
167
- np.array(face_id_list.strip("[]").split()).astype(int)
168
- ]
169
- )
170
- )
171
- )(face_id_lists)
172
- )
173
- return GeoDataFrame(cell_dict, geometry="geometry", crs=HdfUtils.projection(hdf_file))
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
+
174
200
  except Exception as e:
175
201
  logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
176
202
  return GeoDataFrame()
203
+
177
204
  @staticmethod
178
205
  @standardize_input(file_type='plan_hdf')
179
- def mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
206
+ def get_mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
180
207
  """
181
208
  Return 2D flow mesh cell center points.
182
209
 
@@ -192,30 +219,43 @@ class HdfMesh:
192
219
  """
193
220
  try:
194
221
  with h5py.File(hdf_path, 'r') as hdf_file:
195
- mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
222
+ mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
196
223
  if not mesh_area_names:
197
224
  return GeoDataFrame()
198
225
 
199
- pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
226
+ # Pre-allocate lists
227
+ all_mesh_names = []
228
+ all_cell_ids = []
229
+ all_points = []
230
+
200
231
  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)
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)
203
235
 
204
- pnt_dict["mesh_name"] += [mesh_name] * cell_count
205
- pnt_dict["cell_id"] += range(cell_count)
206
- pnt_dict["geometry"] += list(
207
- np.vectorize(lambda coords: Point(coords), signature="(n)->()")(
208
- cell_center_coords
209
- )
210
- )
211
- return GeoDataFrame(pnt_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
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
+
212
252
  except Exception as e:
213
253
  logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
214
254
  return GeoDataFrame()
215
255
 
216
256
  @staticmethod
217
257
  @standardize_input(file_type='plan_hdf')
218
- def mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
258
+ def get_mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
219
259
  """
220
260
  Return 2D flow mesh cell faces.
221
261
 
@@ -231,45 +271,53 @@ class HdfMesh:
231
271
  """
232
272
  try:
233
273
  with h5py.File(hdf_path, 'r') as hdf_file:
234
- mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
274
+ mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
235
275
  if not mesh_area_names:
236
276
  return GeoDataFrame()
237
- face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
277
+
278
+ # Pre-allocate lists
279
+ all_mesh_names = []
280
+ all_face_ids = []
281
+ all_geometries = []
282
+
238
283
  for mesh_name in mesh_area_names:
239
- facepoints_index = hdf_file[
240
- "Geometry/2D Flow Areas/{}/Faces FacePoint Indexes".format(mesh_name)
241
- ][()]
242
- facepoints_coordinates = hdf_file[
243
- "Geometry/2D Flow Areas/{}/FacePoints Coordinate".format(mesh_name)
244
- ][()]
245
- faces_perimeter_info = hdf_file[
246
- "Geometry/2D Flow Areas/{}/Faces Perimeter Info".format(mesh_name)
247
- ][()]
248
- faces_perimeter_values = hdf_file[
249
- "Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
250
- ][()]
251
- face_id = -1
252
- for pnt_a_index, pnt_b_index in facepoints_index:
253
- face_id += 1
254
- face_dict["mesh_name"].append(mesh_name)
255
- face_dict["face_id"].append(face_id)
256
- coordinates = list()
257
- coordinates.append(facepoints_coordinates[pnt_a_index])
258
- starting_row, count = faces_perimeter_info[face_id]
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
+
259
294
  if count > 0:
260
- coordinates += list(
261
- faces_perimeter_values[starting_row : starting_row + count]
262
- )
263
- coordinates.append(facepoints_coordinates[pnt_b_index])
264
- face_dict["geometry"].append(LineString(coordinates))
265
- return GeoDataFrame(face_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
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
+
266
314
  except Exception as e:
267
- self.logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
315
+ logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
268
316
  return GeoDataFrame()
269
317
 
270
318
  @staticmethod
271
319
  @standardize_input(file_type='geom_hdf')
272
- def get_geom_2d_flow_area_attrs(hdf_path: Path) -> Dict[str, Any]:
320
+ def get_mesh_area_attributes(hdf_path: Path) -> pd.DataFrame:
273
321
  """
274
322
  Return geometry 2D flow area attributes from a HEC-RAS HDF file.
275
323
 
@@ -280,8 +328,8 @@ class HdfMesh:
280
328
 
281
329
  Returns
282
330
  -------
283
- Dict[str, Any]
284
- A dictionary containing the 2D flow area attributes.
331
+ pd.DataFrame
332
+ A DataFrame containing the 2D flow area attributes.
285
333
  """
286
334
  try:
287
335
  with h5py.File(hdf_path, 'r') as hdf_file:
@@ -293,20 +341,20 @@ class HdfMesh:
293
341
  value = d2_flow_area[name][()]
294
342
  if isinstance(value, bytes):
295
343
  value = value.decode('utf-8') # Decode as UTF-8
296
- result[name] = value
344
+ result[name] = value if not isinstance(value, bytes) else value.decode('utf-8')
297
345
  except Exception as e:
298
346
  logger.warning(f"Error converting attribute '{name}': {str(e)}")
299
- return result
347
+ return pd.DataFrame.from_dict(result, orient='index', columns=['Value'])
300
348
  else:
301
349
  logger.info("No 2D Flow Area attributes found or invalid dataset.")
302
- return {}
350
+ return pd.DataFrame() # Return an empty DataFrame
303
351
  except Exception as e:
304
352
  logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
305
- return {}
353
+ return pd.DataFrame() # Return an empty DataFrame
306
354
 
307
355
  @staticmethod
308
356
  @standardize_input(file_type='geom_hdf')
309
- def get_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
357
+ def get_mesh_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
310
358
  """
311
359
  Extract Face Property Tables for each Face in all 2D Flow Areas.
312
360
 
@@ -318,12 +366,19 @@ class HdfMesh:
318
366
  Returns
319
367
  -------
320
368
  Dict[str, pd.DataFrame]
321
- A dictionary where keys are mesh names and values are DataFrames
322
- containing the Face Property Tables for all faces in that mesh.
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.
323
378
  """
324
379
  try:
325
380
  with h5py.File(hdf_path, 'r') as hdf_file:
326
- mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
381
+ mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
327
382
  if not mesh_area_names:
328
383
  return {}
329
384
 
@@ -351,3 +406,56 @@ class HdfMesh:
351
406
  except Exception as e:
352
407
  logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
353
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 {}