ras-commander 0.42.0__py3-none-any.whl → 0.44.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.
@@ -0,0 +1,505 @@
1
+ """
2
+ Class: HdfBndry
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.
9
+ """
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional, Union, Any
12
+ import h5py
13
+ import numpy as np
14
+ import pandas as pd
15
+ import geopandas as gpd
16
+ from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, Point
17
+ from .HdfBase import HdfBase
18
+ from .HdfUtils import HdfUtils
19
+ from .HdfMesh import HdfMesh
20
+ from .Decorators import standardize_input, log_call
21
+ from .LoggingConfig import setup_logging, get_logger
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class HdfBndry:
27
+ """
28
+ A class for handling boundary-related data from HEC-RAS HDF files.
29
+
30
+ This class provides methods to extract and process various boundary elements
31
+ such as boundary condition lines, breaklines, refinement regions, and reference
32
+ lines/points from HEC-RAS geometry HDF files.
33
+
34
+ Methods in this class return data primarily as GeoDataFrames, making it easy
35
+ to work with spatial data in a geospatial context.
36
+
37
+ Note:
38
+ This class relies on the HdfBase and HdfUtils classes for some of its
39
+ functionality. Ensure these classes are available in the same package.
40
+ """
41
+
42
+ @staticmethod
43
+ @standardize_input(file_type='plan_hdf')
44
+ def bc_lines(hdf_path: Path) -> gpd.GeoDataFrame:
45
+ """
46
+ Return 2D mesh area boundary condition lines.
47
+
48
+ Parameters
49
+ ----------
50
+ hdf_path : Path
51
+ Path to the HEC-RAS geometry HDF file.
52
+
53
+ Returns
54
+ -------
55
+ gpd.GeoDataFrame
56
+ A GeoDataFrame containing the boundary condition lines.
57
+ """
58
+ try:
59
+ with h5py.File(hdf_path, 'r') as hdf_file:
60
+ bc_lines_path = "Geometry/Boundary Condition Lines"
61
+ if bc_lines_path not in hdf_file:
62
+ return gpd.GeoDataFrame()
63
+ bc_line_data = hdf_file[bc_lines_path]
64
+ bc_line_ids = range(bc_line_data["Attributes"][()].shape[0])
65
+ v_conv_str = np.vectorize(HdfUtils.convert_ras_hdf_string)
66
+ names = v_conv_str(bc_line_data["Attributes"][()]["Name"])
67
+ mesh_names = v_conv_str(bc_line_data["Attributes"][()]["SA-2D"])
68
+ types = v_conv_str(bc_line_data["Attributes"][()]["Type"])
69
+ geoms = HdfBndry._get_polylines(hdf_file, bc_lines_path)
70
+ return gpd.GeoDataFrame(
71
+ {
72
+ "bc_line_id": bc_line_ids,
73
+ "name": names,
74
+ "mesh_name": mesh_names,
75
+ "type": types,
76
+ "geometry": geoms,
77
+ },
78
+ geometry="geometry",
79
+ crs=HdfUtils.projection(hdf_file),
80
+ )
81
+ except Exception as e:
82
+ print(f"Error reading boundary condition lines: {str(e)}")
83
+ return gpd.GeoDataFrame()
84
+
85
+ @staticmethod
86
+ @standardize_input(file_type='plan_hdf')
87
+ def breaklines(hdf_path: Path) -> gpd.GeoDataFrame:
88
+ """
89
+ Return 2D mesh area breaklines.
90
+
91
+ Parameters
92
+ ----------
93
+ hdf_path : Path
94
+ Path to the HEC-RAS geometry HDF file.
95
+
96
+ Returns
97
+ -------
98
+ gpd.GeoDataFrame
99
+ A GeoDataFrame containing the breaklines.
100
+ """
101
+ try:
102
+ with h5py.File(hdf_path, 'r') as hdf_file:
103
+ breaklines_path = "Geometry/2D Flow Area Break Lines"
104
+ if breaklines_path not in hdf_file:
105
+ return gpd.GeoDataFrame()
106
+ bl_line_data = hdf_file[breaklines_path]
107
+ bl_line_ids = range(bl_line_data["Attributes"][()].shape[0])
108
+ names = np.vectorize(HdfUtils.convert_ras_hdf_string)(
109
+ bl_line_data["Attributes"][()]["Name"]
110
+ )
111
+ geoms = HdfBndry._get_polylines(hdf_file, breaklines_path)
112
+ return gpd.GeoDataFrame(
113
+ {"bl_id": bl_line_ids, "name": names, "geometry": geoms},
114
+ geometry="geometry",
115
+ crs=HdfUtils.projection(hdf_file),
116
+ )
117
+ except Exception as e:
118
+ print(f"Error reading breaklines: {str(e)}")
119
+ return gpd.GeoDataFrame()
120
+
121
+ @staticmethod
122
+ @standardize_input(file_type='plan_hdf')
123
+ def refinement_regions(hdf_path: Path) -> gpd.GeoDataFrame:
124
+ """
125
+ Return 2D mesh area refinement regions.
126
+
127
+ Parameters
128
+ ----------
129
+ hdf_path : Path
130
+ Path to the HEC-RAS geometry HDF file.
131
+
132
+ Returns
133
+ -------
134
+ gpd.GeoDataFrame
135
+ A GeoDataFrame containing the refinement regions.
136
+ """
137
+ try:
138
+ with h5py.File(hdf_path, 'r') as hdf_file:
139
+ refinement_regions_path = "/Geometry/2D Flow Area Refinement Regions"
140
+ if refinement_regions_path not in hdf_file:
141
+ return gpd.GeoDataFrame()
142
+ rr_data = hdf_file[refinement_regions_path]
143
+ rr_ids = range(rr_data["Attributes"][()].shape[0])
144
+ names = np.vectorize(HdfUtils.convert_ras_hdf_string)(rr_data["Attributes"][()]["Name"])
145
+ geoms = list()
146
+ for pnt_start, pnt_cnt, part_start, part_cnt in rr_data["Polygon Info"][()]:
147
+ points = rr_data["Polygon Points"][()][pnt_start : pnt_start + pnt_cnt]
148
+ if part_cnt == 1:
149
+ geoms.append(Polygon(points))
150
+ else:
151
+ parts = rr_data["Polygon Parts"][()][part_start : part_start + part_cnt]
152
+ geoms.append(
153
+ MultiPolygon(
154
+ list(
155
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
156
+ for part_pnt_start, part_pnt_cnt in parts
157
+ )
158
+ )
159
+ )
160
+ return gpd.GeoDataFrame(
161
+ {"rr_id": rr_ids, "name": names, "geometry": geoms},
162
+ geometry="geometry",
163
+ crs=HdfUtils.projection(hdf_file),
164
+ )
165
+ except Exception as e:
166
+ print(f"Error reading refinement regions: {str(e)}")
167
+ return gpd.GeoDataFrame()
168
+
169
+ @staticmethod
170
+ @standardize_input(file_type='plan_hdf')
171
+ def reference_lines_names(hdf_path: Path, mesh_name: Optional[str] = None) -> Union[Dict[str, List[str]], List[str]]:
172
+ """
173
+ Return reference line names.
174
+
175
+ Parameters
176
+ ----------
177
+ hdf_path : Path
178
+ Path to the HEC-RAS geometry HDF file.
179
+ mesh_name : Optional[str], optional
180
+ Name of the mesh to filter by. Default is None.
181
+
182
+ Returns
183
+ -------
184
+ Union[Dict[str, List[str]], List[str]]
185
+ A dictionary of mesh names to reference line names, or a list of reference line names if mesh_name is provided.
186
+ """
187
+ return HdfBndry._get_reference_lines_points_names(hdf_path, "lines", mesh_name)
188
+
189
+ @staticmethod
190
+ @standardize_input(file_type='plan_hdf')
191
+ def reference_points_names(hdf_path: Path, mesh_name: Optional[str] = None) -> Union[Dict[str, List[str]], List[str]]:
192
+ """
193
+ Return reference point names.
194
+
195
+ Parameters
196
+ ----------
197
+ hdf_path : Path
198
+ Path to the HEC-RAS geometry HDF file.
199
+ mesh_name : Optional[str], optional
200
+ Name of the mesh to filter by. Default is None.
201
+
202
+ Returns
203
+ -------
204
+ Union[Dict[str, List[str]], List[str]]
205
+ A dictionary of mesh names to reference point names, or a list of reference point names if mesh_name is provided.
206
+ """
207
+ return HdfBndry._get_reference_lines_points_names(hdf_path, "points", mesh_name)
208
+
209
+ @staticmethod
210
+ @standardize_input(file_type='plan_hdf')
211
+ def reference_lines(hdf_path: Path) -> gpd.GeoDataFrame:
212
+ """
213
+ Return the reference lines geometry and attributes.
214
+
215
+ Parameters
216
+ ----------
217
+ hdf_path : Path
218
+ Path to the HEC-RAS geometry HDF file.
219
+
220
+ Returns
221
+ -------
222
+ gpd.GeoDataFrame
223
+ A GeoDataFrame containing the reference lines.
224
+ """
225
+ try:
226
+ with h5py.File(hdf_path, 'r') as hdf_file:
227
+ reference_lines_path = "Geometry/Reference Lines"
228
+ attributes_path = f"{reference_lines_path}/Attributes"
229
+ if attributes_path not in hdf_file:
230
+ return gpd.GeoDataFrame()
231
+ attributes = hdf_file[attributes_path][()]
232
+ refline_ids = range(attributes.shape[0])
233
+ v_conv_str = np.vectorize(HdfUtils.convert_ras_hdf_string)
234
+ names = v_conv_str(attributes["Name"])
235
+ mesh_names = v_conv_str(attributes["SA-2D"])
236
+ try:
237
+ types = v_conv_str(attributes["Type"])
238
+ except ValueError:
239
+ # "Type" field doesn't exist -- observed in some RAS HDF files
240
+ types = np.array([""] * attributes.shape[0])
241
+ geoms = HdfBndry._get_polylines(hdf_file, reference_lines_path)
242
+ return gpd.GeoDataFrame(
243
+ {
244
+ "refln_id": refline_ids,
245
+ "refln_name": names,
246
+ "mesh_name": mesh_names,
247
+ "type": types,
248
+ "geometry": geoms,
249
+ },
250
+ geometry="geometry",
251
+ crs=HdfUtils.projection(hdf_file),
252
+ )
253
+ except Exception as e:
254
+ print(f"Error reading reference lines: {str(e)}")
255
+ return gpd.GeoDataFrame()
256
+
257
+ @staticmethod
258
+ @standardize_input(file_type='plan_hdf')
259
+ def reference_points(hdf_path: Path) -> gpd.GeoDataFrame:
260
+ """
261
+ Return the reference points geometry and attributes.
262
+
263
+ Parameters
264
+ ----------
265
+ hdf_path : Path
266
+ Path to the HEC-RAS geometry HDF file.
267
+
268
+ Returns
269
+ -------
270
+ gpd.GeoDataFrame
271
+ A GeoDataFrame containing the reference points.
272
+ """
273
+ try:
274
+ with h5py.File(hdf_path, 'r') as hdf_file:
275
+ reference_points_path = "Geometry/Reference Points"
276
+ attributes_path = f"{reference_points_path}/Attributes"
277
+ if attributes_path not in hdf_file:
278
+ return gpd.GeoDataFrame()
279
+ ref_points_group = hdf_file[reference_points_path]
280
+ attributes = ref_points_group["Attributes"][:]
281
+ v_conv_str = np.vectorize(HdfUtils.convert_ras_hdf_string)
282
+ names = v_conv_str(attributes["Name"])
283
+ mesh_names = v_conv_str(attributes["SA/2D"])
284
+ cell_id = attributes["Cell Index"]
285
+ points = ref_points_group["Points"][()]
286
+ return gpd.GeoDataFrame(
287
+ {
288
+ "refpt_id": range(attributes.shape[0]),
289
+ "refpt_name": names,
290
+ "mesh_name": mesh_names,
291
+ "cell_id": cell_id,
292
+ "geometry": list(map(Point, points)),
293
+ },
294
+ geometry="geometry",
295
+ crs=HdfUtils.projection(hdf_file),
296
+ )
297
+ except Exception as e:
298
+ print(f"Error reading reference points: {str(e)}")
299
+ return gpd.GeoDataFrame()
300
+
301
+ @staticmethod
302
+ def _get_reference_lines_points_names(hdf_path: Path, reftype: str = "lines", mesh_name: Optional[str] = None) -> Union[Dict[str, List[str]], List[str]]:
303
+ """
304
+ Get the names of reference lines or points.
305
+
306
+ Parameters
307
+ ----------
308
+ hdf_path : Path
309
+ Path to the HEC-RAS geometry HDF file.
310
+ reftype : str, optional
311
+ Type of reference, either "lines" or "points" (default "lines").
312
+ mesh_name : Optional[str], optional
313
+ Name of the mesh to filter by. Default is None.
314
+
315
+ Returns
316
+ -------
317
+ Union[Dict[str, List[str]], List[str]]
318
+ A dictionary of mesh names to reference names, or a list of reference names if mesh_name is provided.
319
+ """
320
+ try:
321
+ with h5py.File(hdf_path, 'r') as hdf_file:
322
+ if reftype == "lines":
323
+ path = "Geometry/Reference Lines"
324
+ sa_2d_field = "SA-2D"
325
+ elif reftype == "points":
326
+ path = "Geometry/Reference Points"
327
+ sa_2d_field = "SA/2D"
328
+ else:
329
+ raise ValueError(
330
+ f"Invalid reference type: {reftype} -- must be 'lines' or 'points'."
331
+ )
332
+ attributes_path = f"{path}/Attributes"
333
+ if mesh_name is None and attributes_path not in hdf_file:
334
+ return {m: [] for m in HdfMesh.mesh_area_names(hdf_file)}
335
+ if mesh_name is not None and attributes_path not in hdf_file:
336
+ return []
337
+ attributes = hdf_file[attributes_path][()]
338
+ v_conv_str = np.vectorize(HdfUtils.convert_ras_hdf_string)
339
+ names = v_conv_str(attributes["Name"])
340
+ if mesh_name is not None:
341
+ return names[v_conv_str(attributes[sa_2d_field]) == mesh_name].tolist()
342
+ mesh_names = v_conv_str(attributes[sa_2d_field])
343
+ return {m: names[mesh_names == m].tolist() for m in np.unique(mesh_names)}
344
+ except Exception as e:
345
+ print(f"Error reading reference lines/points names: {str(e)}")
346
+ return {} if mesh_name is None else []
347
+
348
+ @staticmethod
349
+ def _get_polylines(hdf_file: h5py.File, path: str, info_name: str = "Polyline Info", parts_name: str = "Polyline Parts", points_name: str = "Polyline Points") -> List[Union[LineString, MultiLineString]]:
350
+ """
351
+ Get polyline geometries from HDF file.
352
+
353
+ Parameters
354
+ ----------
355
+ hdf_file : h5py.File
356
+ Open HDF file object.
357
+ path : str
358
+ Path to the polyline data in the HDF file.
359
+ info_name : str, optional
360
+ Name of the info dataset (default "Polyline Info").
361
+ parts_name : str, optional
362
+ Name of the parts dataset (default "Polyline Parts").
363
+ points_name : str, optional
364
+ Name of the points dataset (default "Polyline Points").
365
+
366
+ Returns
367
+ -------
368
+ List[Union[LineString, MultiLineString]]
369
+ A list of polyline geometries.
370
+ """
371
+ polyline_info_path = f"{path}/{info_name}"
372
+ polyline_parts_path = f"{path}/{parts_name}"
373
+ polyline_points_path = f"{path}/{points_name}"
374
+
375
+ polyline_info = hdf_file[polyline_info_path][()]
376
+ polyline_parts = hdf_file[polyline_parts_path][()]
377
+ polyline_points = hdf_file[polyline_points_path][()]
378
+
379
+ geoms = []
380
+ for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
381
+ points = polyline_points[pnt_start : pnt_start + pnt_cnt]
382
+ if part_cnt == 1:
383
+ geoms.append(LineString(points))
384
+ else:
385
+ parts = polyline_parts[part_start : part_start + part_cnt]
386
+ geoms.append(
387
+ MultiLineString(
388
+ list(
389
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
390
+ for part_pnt_start, part_pnt_cnt in parts
391
+ )
392
+ )
393
+ )
394
+ return geoms
395
+
396
+ @staticmethod
397
+ @standardize_input(file_type='plan_hdf')
398
+ def get_boundary_attributes(hdf_path: Path, boundary_type: str) -> pd.DataFrame:
399
+ """
400
+ Get attributes of boundary elements.
401
+
402
+ Parameters
403
+ ----------
404
+ hdf_path : Path
405
+ Path to the HEC-RAS geometry HDF file.
406
+ boundary_type : str
407
+ Type of boundary element ('bc_lines', 'breaklines', 'refinement_regions', 'reference_lines', 'reference_points').
408
+
409
+ Returns
410
+ -------
411
+ pd.DataFrame
412
+ A DataFrame containing the attributes of the specified boundary element.
413
+ """
414
+ try:
415
+ with h5py.File(hdf_path, 'r') as hdf_file:
416
+ if boundary_type == 'bc_lines':
417
+ path = "Geometry/Boundary Condition Lines/Attributes"
418
+ elif boundary_type == 'breaklines':
419
+ path = "Geometry/2D Flow Area Break Lines/Attributes"
420
+ elif boundary_type == 'refinement_regions':
421
+ path = "Geometry/2D Flow Area Refinement Regions/Attributes"
422
+ elif boundary_type == 'reference_lines':
423
+ path = "Geometry/Reference Lines/Attributes"
424
+ elif boundary_type == 'reference_points':
425
+ path = "Geometry/Reference Points/Attributes"
426
+ else:
427
+ raise ValueError(f"Invalid boundary type: {boundary_type}")
428
+
429
+ if path not in hdf_file:
430
+ return pd.DataFrame()
431
+
432
+ attributes = hdf_file[path][()]
433
+ return pd.DataFrame(attributes)
434
+ except Exception as e:
435
+ print(f"Error reading {boundary_type} attributes: {str(e)}")
436
+ return pd.DataFrame()
437
+
438
+ @staticmethod
439
+ @standardize_input(file_type='plan_hdf')
440
+ def get_boundary_count(hdf_path: Path, boundary_type: str) -> int:
441
+ """
442
+ Get the count of boundary elements.
443
+
444
+ Parameters
445
+ ----------
446
+ hdf_path : Path
447
+ Path to the HEC-RAS geometry HDF file.
448
+ boundary_type : str
449
+ Type of boundary element ('bc_lines', 'breaklines', 'refinement_regions', 'reference_lines', 'reference_points').
450
+
451
+ Returns
452
+ -------
453
+ int
454
+ The count of the specified boundary element.
455
+ """
456
+ try:
457
+ with h5py.File(hdf_path, 'r') as hdf_file:
458
+ if boundary_type == 'bc_lines':
459
+ path = "Geometry/Boundary Condition Lines/Attributes"
460
+ elif boundary_type == 'breaklines':
461
+ path = "Geometry/2D Flow Area Break Lines/Attributes"
462
+ elif boundary_type == 'refinement_regions':
463
+ path = "Geometry/2D Flow Area Refinement Regions/Attributes"
464
+ elif boundary_type == 'reference_lines':
465
+ path = "Geometry/Reference Lines/Attributes"
466
+ elif boundary_type == 'reference_points':
467
+ path = "Geometry/Reference Points/Attributes"
468
+ else:
469
+ raise ValueError(f"Invalid boundary type: {boundary_type}")
470
+
471
+ if path not in hdf_file:
472
+ return 0
473
+
474
+ return hdf_file[path].shape[0]
475
+ except Exception as e:
476
+ print(f"Error getting {boundary_type} count: {str(e)}")
477
+ return 0
478
+
479
+ @staticmethod
480
+ @standardize_input(file_type='plan_hdf')
481
+ def get_boundary_names(hdf_path: Path, boundary_type: str) -> List[str]:
482
+ """
483
+ Get the names of boundary elements.
484
+
485
+ Parameters
486
+ ----------
487
+ hdf_path : Path
488
+ Path to the HEC-RAS geometry HDF file.
489
+ boundary_type : str
490
+ Type of boundary element ('bc_lines', 'breaklines', 'refinement_regions', 'reference_lines', 'reference_points').
491
+
492
+ Returns
493
+ -------
494
+ List[str]
495
+ A list of names for the specified boundary element.
496
+ """
497
+ try:
498
+ df = HdfBndry.get_boundary_attributes(hdf_path, boundary_type)
499
+ if 'Name' in df.columns:
500
+ return df['Name'].tolist()
501
+ else:
502
+ return []
503
+ except Exception as e:
504
+ print(f"Error getting {boundary_type} names: {str(e)}")
505
+ return []