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,308 @@
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.
9
+ """
10
+ from pathlib import Path
11
+ import h5py
12
+ import numpy as np
13
+ import pandas as pd
14
+ from geopandas import GeoDataFrame
15
+ from shapely.geometry import Polygon, Point, LineString, MultiLineString, MultiPolygon
16
+ from shapely.ops import polygonize # Importing polygonize to resolve the undefined name error
17
+ from typing import List, Tuple, Optional, Dict, Any
18
+ import logging
19
+ from .HdfBase import HdfBase
20
+ from .HdfUtils import HdfUtils
21
+ from .Decorators import standardize_input, log_call
22
+ from .LoggingConfig import setup_logging, get_logger
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ class HdfMesh:
28
+ """
29
+ A class for handling mesh-related operations on HEC-RAS HDF files.
30
+
31
+ This class provides methods to extract and analyze mesh data from HEC-RAS HDF files,
32
+ including mesh area names, mesh areas, cell polygons, cell points, cell faces, and
33
+ 2D flow area attributes.
34
+
35
+ Methods in this class are designed to work with the mesh geometry data stored in
36
+ HEC-RAS HDF files, providing functionality to retrieve and process various aspects
37
+ of the 2D flow areas and their associated mesh structures.
38
+
39
+ Note: This class relies on HdfBase and HdfUtils for some underlying operations.
40
+ """
41
+
42
+ FLOW_AREA_2D_PATH = "Geometry/2D Flow Areas"
43
+
44
+ def __init__(self):
45
+ self.logger = logging.getLogger(__name__)
46
+
47
+ @staticmethod
48
+ @standardize_input(file_type='plan_hdf')
49
+ def mesh_area_names(hdf_path: Path) -> List[str]:
50
+ """
51
+ Return a list of the 2D mesh area names of the RAS geometry.
52
+
53
+ Parameters
54
+ ----------
55
+ hdf_path : Path
56
+ Path to the HEC-RAS geometry HDF file.
57
+
58
+ Returns
59
+ -------
60
+ List[str]
61
+ A list of the 2D mesh area names (str) within the RAS geometry if 2D areas exist.
62
+ """
63
+ try:
64
+ with h5py.File(hdf_path, 'r') as hdf_file:
65
+ if HdfMesh.FLOW_AREA_2D_PATH not in hdf_file:
66
+ return list()
67
+ return list(
68
+ [
69
+ HdfUtils.convert_ras_hdf_string(n)
70
+ for n in hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Attributes"][()]["Name"]
71
+ ]
72
+ )
73
+ except Exception as e:
74
+ self.logger.error(f"Error reading mesh area names from {hdf_path}: {str(e)}")
75
+ return list()
76
+
77
+ @staticmethod
78
+ @standardize_input(file_type='geom_hdf')
79
+ def mesh_areas(hdf_path: Path) -> GeoDataFrame:
80
+ """
81
+ Return 2D flow area perimeter polygons.
82
+
83
+ Parameters
84
+ ----------
85
+ hdf_path : Path
86
+ Path to the HEC-RAS geometry HDF file.
87
+
88
+ Returns
89
+ -------
90
+ GeoDataFrame
91
+ A GeoDataFrame containing the 2D flow area perimeter polygons if 2D areas exist.
92
+ """
93
+ try:
94
+ with h5py.File(hdf_path, 'r') as hdf_file:
95
+ mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
96
+ if not mesh_area_names:
97
+ return GeoDataFrame()
98
+ mesh_area_polygons = [
99
+ Polygon(hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
100
+ for n in mesh_area_names
101
+ ]
102
+ return GeoDataFrame(
103
+ {"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
104
+ geometry="geometry",
105
+ crs=HdfUtils.projection(hdf_file), # Pass the h5py.File object instead of the path
106
+ )
107
+ except Exception as e:
108
+ logger.error(f"Error reading mesh areas from {hdf_path}: {str(e)}")
109
+ return GeoDataFrame()
110
+
111
+ @staticmethod
112
+ @standardize_input(file_type='geom_hdf')
113
+ def mesh_cell_polygons(hdf_path: Path) -> GeoDataFrame:
114
+ """
115
+ Return 2D flow mesh cell polygons.
116
+
117
+ Parameters
118
+ ----------
119
+ hdf_path : Path
120
+ Path to the HEC-RAS geometry HDF file.
121
+
122
+ Returns
123
+ -------
124
+ GeoDataFrame
125
+ A GeoDataFrame containing the 2D flow mesh cell polygons.
126
+ """
127
+ try:
128
+ with h5py.File(hdf_path, 'r') as hdf_file:
129
+ mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
130
+ if not mesh_area_names:
131
+ return GeoDataFrame()
132
+
133
+ face_gdf = HdfMesh.mesh_cell_faces(hdf_path)
134
+
135
+ cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
136
+ for i, mesh_name in enumerate(mesh_area_names):
137
+ cell_cnt = hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Cell Info"][()][i][1]
138
+ cell_ids = list(range(cell_cnt))
139
+ cell_face_info = hdf_file[
140
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Info"
141
+ ][()]
142
+ cell_face_values = hdf_file[
143
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Values"
144
+ ][()][:, 0]
145
+ face_id_lists = list(
146
+ np.vectorize(
147
+ lambda cell_id: str(
148
+ cell_face_values[
149
+ cell_face_info[cell_id][0] : cell_face_info[cell_id][0]
150
+ + cell_face_info[cell_id][1]
151
+ ]
152
+ )
153
+ )(cell_ids)
154
+ )
155
+ mesh_faces = (
156
+ face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]]
157
+ .set_index("face_id")
158
+ .to_numpy()
159
+ )
160
+ cell_dict["mesh_name"] += [mesh_name] * cell_cnt
161
+ cell_dict["cell_id"] += cell_ids
162
+ cell_dict["geometry"] += list(
163
+ np.vectorize(
164
+ lambda face_id_list: (
165
+ lambda geom_col: Polygon(list(polygonize(geom_col))[0])
166
+ )(
167
+ np.ravel(
168
+ mesh_faces[
169
+ np.array(face_id_list.strip("[]").split()).astype(int)
170
+ ]
171
+ )
172
+ )
173
+ )(face_id_lists)
174
+ )
175
+ return GeoDataFrame(cell_dict, geometry="geometry", crs=HdfUtils.projection(hdf_file))
176
+ except Exception as e:
177
+ logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
178
+ return GeoDataFrame()
179
+
180
+ @staticmethod
181
+ @standardize_input(file_type='plan_hdf')
182
+ def mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
183
+ """
184
+ Return 2D flow mesh cell points.
185
+
186
+ Parameters
187
+ ----------
188
+ hdf_path : Path
189
+ Path to the HEC-RAS geometry HDF file.
190
+
191
+ Returns
192
+ -------
193
+ GeoDataFrame
194
+ A GeoDataFrame containing the 2D flow mesh cell points.
195
+ """
196
+ try:
197
+ with h5py.File(hdf_path, 'r') as hdf_file:
198
+ mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
199
+ if not mesh_area_names:
200
+ return GeoDataFrame()
201
+ pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
202
+ for i, mesh_name in enumerate(mesh_area_names):
203
+ starting_row, count = hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Cell Info"][()][i]
204
+ cell_pnt_coords = hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Cell Points"][()][
205
+ starting_row : starting_row + count
206
+ ]
207
+ pnt_dict["mesh_name"] += [mesh_name] * cell_pnt_coords.shape[0]
208
+ pnt_dict["cell_id"] += range(count)
209
+ pnt_dict["geometry"] += list(
210
+ np.vectorize(lambda coords: Point(coords), signature="(n)->()")(
211
+ cell_pnt_coords
212
+ )
213
+ )
214
+ return GeoDataFrame(pnt_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
215
+ except Exception as e:
216
+ self.logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
217
+ return GeoDataFrame()
218
+
219
+ @staticmethod
220
+ @standardize_input(file_type='plan_hdf')
221
+ def mesh_cell_faces(hdf_path: Path) -> GeoDataFrame:
222
+ """
223
+ Return 2D flow mesh cell faces.
224
+
225
+ Parameters
226
+ ----------
227
+ hdf_path : Path
228
+ Path to the HEC-RAS geometry HDF file.
229
+
230
+ Returns
231
+ -------
232
+ GeoDataFrame
233
+ A GeoDataFrame containing the 2D flow mesh cell faces.
234
+ """
235
+ try:
236
+ with h5py.File(hdf_path, 'r') as hdf_file:
237
+ mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
238
+ if not mesh_area_names:
239
+ return GeoDataFrame()
240
+ face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
241
+ for mesh_name in mesh_area_names:
242
+ facepoints_index = hdf_file[
243
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
244
+ ][()]
245
+ facepoints_coordinates = hdf_file[
246
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
247
+ ][()]
248
+ faces_perimeter_info = hdf_file[
249
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
250
+ ][()]
251
+ faces_perimeter_values = hdf_file[
252
+ f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Values"
253
+ ][()]
254
+ face_id = -1
255
+ for pnt_a_index, pnt_b_index in facepoints_index:
256
+ face_id += 1
257
+ face_dict["mesh_name"].append(mesh_name)
258
+ face_dict["face_id"].append(face_id)
259
+ coordinates = list()
260
+ coordinates.append(facepoints_coordinates[pnt_a_index])
261
+ starting_row, count = faces_perimeter_info[face_id]
262
+ if count > 0:
263
+ coordinates += list(
264
+ faces_perimeter_values[starting_row : starting_row + count]
265
+ )
266
+ coordinates.append(facepoints_coordinates[pnt_b_index])
267
+ face_dict["geometry"].append(LineString(coordinates))
268
+ return GeoDataFrame(face_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
269
+ except Exception as e:
270
+ self.logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
271
+ return GeoDataFrame()
272
+
273
+ @staticmethod
274
+ @standardize_input(file_type='geom_hdf')
275
+ def get_geom_2d_flow_area_attrs(hdf_path: Path) -> Dict[str, Any]:
276
+ """
277
+ Return geometry 2D flow area attributes from a HEC-RAS HDF file.
278
+
279
+ Parameters
280
+ ----------
281
+ hdf_path : Path
282
+ Path to the HEC-RAS geometry HDF file.
283
+
284
+ Returns
285
+ -------
286
+ Dict[str, Any]
287
+ A dictionary containing the 2D flow area attributes.
288
+ """
289
+ try:
290
+ with h5py.File(hdf_path, 'r') as hdf_file:
291
+ d2_flow_area = hdf_file.get(f"{HdfMesh.FLOW_AREA_2D_PATH}/Attributes")
292
+ if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
293
+ result = {}
294
+ for name in d2_flow_area.dtype.names:
295
+ try:
296
+ value = d2_flow_area[name][()]
297
+ if isinstance(value, bytes):
298
+ value = value.decode('utf-8')
299
+ result[name] = value
300
+ except Exception as e:
301
+ logger.warning(f"Error converting attribute '{name}': {str(e)}")
302
+ return result
303
+ else:
304
+ logger.info("No 2D Flow Area attributes found or invalid dataset.")
305
+ return {}
306
+ except Exception as e:
307
+ logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
308
+ return {}
@@ -0,0 +1,200 @@
1
+ """
2
+ Class: HdfPlan
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
+
11
+ import h5py
12
+ import pandas as pd
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Dict, List, Optional
16
+
17
+ from .HdfBase import HdfBase
18
+ from .HdfUtils import HdfUtils
19
+ from .Decorators import standardize_input, log_call
20
+ from .LoggingConfig import setup_logging, get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class HdfPlan:
26
+ """
27
+ A class for handling operations on HEC-RAS plan HDF files.
28
+
29
+ This class provides methods for extracting and analyzing data from HEC-RAS plan HDF files,
30
+ including simulation times, plan information, and geometry attributes.
31
+
32
+ Methods in this class use the @standardize_input decorator to handle different input types
33
+ (e.g., plan number, file path) and the @log_call decorator for logging method calls.
34
+
35
+ Attributes:
36
+ None
37
+
38
+ Methods:
39
+ get_simulation_start_time: Get the simulation start time.
40
+ get_simulation_end_time: Get the simulation end time.
41
+ get_unsteady_datetimes: Get a list of unsteady datetimes.
42
+ get_plan_info_attrs: Get plan information attributes.
43
+ get_plan_param_attrs: Get plan parameter attributes.
44
+ get_meteorology_precip_attrs: Get precipitation attributes.
45
+ get_geom_attrs: Get geometry attributes.
46
+ """
47
+
48
+ @staticmethod
49
+ @log_call
50
+ @standardize_input(file_type='plan_hdf')
51
+ def get_simulation_start_time(hdf_path: Path) -> datetime:
52
+ """
53
+ Get the simulation start time from the plan file.
54
+
55
+ Args:
56
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
57
+
58
+ Returns:
59
+ datetime: The simulation start time.
60
+
61
+ Raises:
62
+ ValueError: If there's an error reading the simulation start time.
63
+ """
64
+ try:
65
+ with h5py.File(hdf_path, 'r') as hdf_file:
66
+ return HdfBase._get_simulation_start_time(hdf_file)
67
+ except Exception as e:
68
+ raise ValueError(f"Failed to get simulation start time: {str(e)}")
69
+
70
+ @staticmethod
71
+ @log_call
72
+ @standardize_input(file_type='plan_hdf')
73
+ def get_simulation_end_time(hdf_path: Path) -> datetime:
74
+ """
75
+ Get the simulation end time from the plan file.
76
+
77
+ Args:
78
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
79
+
80
+ Returns:
81
+ datetime: The simulation end time.
82
+
83
+ Raises:
84
+ ValueError: If there's an error reading the simulation end time.
85
+ """
86
+ try:
87
+ with h5py.File(hdf_path, 'r') as hdf_file:
88
+ plan_info = hdf_file.get('Plan Data/Plan Information')
89
+ if plan_info is None:
90
+ raise ValueError("Plan Information not found in HDF file")
91
+ time_str = plan_info.attrs.get('Simulation End Time')
92
+ return datetime.strptime(time_str.decode('utf-8'), "%d%b%Y %H:%M:%S")
93
+ except Exception as e:
94
+ raise ValueError(f"Failed to get simulation end time: {str(e)}")
95
+
96
+ @staticmethod
97
+ @log_call
98
+ @standardize_input(file_type='plan_hdf')
99
+ def get_unsteady_datetimes(hdf_path: Path) -> List[datetime]:
100
+ """
101
+ Get the list of unsteady datetimes from the HDF file.
102
+
103
+ Args:
104
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
105
+
106
+ Returns:
107
+ List[datetime]: A list of datetime objects representing the unsteady timestamps.
108
+
109
+ Raises:
110
+ ValueError: If there's an error retrieving the unsteady datetimes.
111
+ """
112
+ try:
113
+ with h5py.File(hdf_path, 'r') as hdf_file:
114
+ return HdfBase._get_unsteady_datetimes(hdf_file)
115
+ except Exception as e:
116
+ raise ValueError(f"Failed to get unsteady datetimes: {str(e)}")
117
+
118
+ @staticmethod
119
+ @log_call
120
+ @standardize_input(file_type='plan_hdf')
121
+ def get_plan_info_attrs(hdf_path: Path) -> Dict:
122
+ """
123
+ Get plan information attributes from a HEC-RAS HDF plan file.
124
+
125
+ Args:
126
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
127
+
128
+ Returns:
129
+ Dict: A dictionary containing the plan information attributes.
130
+
131
+ Raises:
132
+ ValueError: If there's an error retrieving the plan information attributes.
133
+ """
134
+ try:
135
+ return HdfUtils.get_attrs(hdf_path, "Plan Data/Plan Information")
136
+ except Exception as e:
137
+ raise ValueError(f"Failed to get plan information attributes: {str(e)}")
138
+
139
+ @staticmethod
140
+ @log_call
141
+ @standardize_input(file_type='plan_hdf')
142
+ def get_plan_param_attrs(hdf_path: Path) -> Dict:
143
+ """
144
+ Get plan parameter attributes from a HEC-RAS HDF plan file.
145
+
146
+ Args:
147
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
148
+
149
+ Returns:
150
+ Dict: A dictionary containing the plan parameter attributes.
151
+
152
+ Raises:
153
+ ValueError: If there's an error retrieving the plan parameter attributes.
154
+ """
155
+ try:
156
+ return HdfUtils.get_attrs(hdf_path, "Plan Data/Plan Parameters")
157
+ except Exception as e:
158
+ raise ValueError(f"Failed to get plan parameter attributes: {str(e)}")
159
+
160
+ @staticmethod
161
+ @log_call
162
+ @standardize_input(file_type='plan_hdf')
163
+ def get_meteorology_precip_attrs(hdf_path: Path) -> Dict:
164
+ """
165
+ Get precipitation attributes from a HEC-RAS HDF plan file.
166
+
167
+ Args:
168
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
169
+
170
+ Returns:
171
+ Dict: A dictionary containing the precipitation attributes.
172
+
173
+ Raises:
174
+ ValueError: If there's an error retrieving the precipitation attributes.
175
+ """
176
+ try:
177
+ return HdfUtils.get_attrs(hdf_path, "Event Conditions/Meteorology/Precipitation")
178
+ except Exception as e:
179
+ raise ValueError(f"Failed to get precipitation attributes: {str(e)}")
180
+
181
+ @staticmethod
182
+ @log_call
183
+ @standardize_input(file_type='plan_hdf')
184
+ def get_geom_attrs(hdf_path: Path) -> Dict:
185
+ """
186
+ Get geometry attributes from a HEC-RAS HDF plan file.
187
+
188
+ Args:
189
+ hdf_path (Path): Path to the HEC-RAS plan HDF file.
190
+
191
+ Returns:
192
+ Dict: A dictionary containing the geometry attributes.
193
+
194
+ Raises:
195
+ ValueError: If there's an error retrieving the geometry attributes.
196
+ """
197
+ try:
198
+ return HdfUtils.get_attrs(hdf_path, "Geometry")
199
+ except Exception as e:
200
+ raise ValueError(f"Failed to get geometry attributes: {str(e)}")