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