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