ras-commander 0.45.0__tar.gz → 0.47.0__tar.gz

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.
Files changed (40) hide show
  1. {ras_commander-0.45.0/ras_commander.egg-info → ras_commander-0.47.0}/PKG-INFO +2 -2
  2. {ras_commander-0.45.0 → ras_commander-0.47.0}/README.md +1 -1
  3. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfBndry.py +9 -9
  4. ras_commander-0.47.0/ras_commander/HdfFluvialPluvial.py +317 -0
  5. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfMesh.py +73 -27
  6. ras_commander-0.47.0/ras_commander/HdfPipe.py +771 -0
  7. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfPlan.py +5 -0
  8. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfPump.py +25 -11
  9. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfResultsMesh.py +135 -62
  10. ras_commander-0.47.0/ras_commander/HdfResultsXsec.py +272 -0
  11. ras_commander-0.47.0/ras_commander/HdfStruc.py +245 -0
  12. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfUtils.py +51 -0
  13. ras_commander-0.47.0/ras_commander/HdfXsec.py +613 -0
  14. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasPlan.py +298 -45
  15. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasPrj.py +2 -7
  16. ras_commander-0.47.0/ras_commander/RasToGo.py +21 -0
  17. ras_commander-0.47.0/ras_commander/RasUnsteady.py +710 -0
  18. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/__init__.py +3 -1
  19. {ras_commander-0.45.0 → ras_commander-0.47.0/ras_commander.egg-info}/PKG-INFO +2 -2
  20. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander.egg-info/SOURCES.txt +2 -0
  21. {ras_commander-0.45.0 → ras_commander-0.47.0}/setup.py +1 -1
  22. ras_commander-0.45.0/ras_commander/HdfPipe.py +0 -262
  23. ras_commander-0.45.0/ras_commander/HdfResultsXsec.py +0 -443
  24. ras_commander-0.45.0/ras_commander/HdfStruc.py +0 -147
  25. ras_commander-0.45.0/ras_commander/HdfXsec.py +0 -282
  26. ras_commander-0.45.0/ras_commander/RasUnsteady.py +0 -109
  27. {ras_commander-0.45.0 → ras_commander-0.47.0}/LICENSE +0 -0
  28. {ras_commander-0.45.0 → ras_commander-0.47.0}/pyproject.toml +0 -0
  29. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/Decorators.py +0 -0
  30. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfBase.py +0 -0
  31. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/HdfResultsPlan.py +0 -0
  32. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/LoggingConfig.py +0 -0
  33. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasCmdr.py +0 -0
  34. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasExamples.py +0 -0
  35. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasGeo.py +0 -0
  36. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasGpt.py +0 -0
  37. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander/RasUtils.py +0 -0
  38. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander.egg-info/dependency_links.txt +0 -0
  39. {ras_commander-0.45.0 → ras_commander-0.47.0}/ras_commander.egg-info/top_level.txt +0 -0
  40. {ras_commander-0.45.0 → ras_commander-0.47.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ras-commander
3
- Version: 0.45.0
3
+ Version: 0.47.0
4
4
  Summary: A Python library for automating HEC-RAS operations
5
5
  Home-page: https://github.com/billk-FM/ras-commander
6
6
  Author: William M. Katzenmeyer
@@ -61,7 +61,7 @@ Create a virtual environment with conda or venv (ask ChatGPT if you need help)
61
61
 
62
62
  In your virtual environment, install ras-commander using pip:
63
63
  ```
64
- pip install h5py numpy pandas requests tqdm scipy
64
+ pip install h5py numpy pandas requests tqdm scipy rtree pyproj shapely xarray rasterio
65
65
  pip install --upgrade ras-commander
66
66
  ```
67
67
 
@@ -50,7 +50,7 @@ Create a virtual environment with conda or venv (ask ChatGPT if you need help)
50
50
 
51
51
  In your virtual environment, install ras-commander using pip:
52
52
  ```
53
- pip install h5py numpy pandas requests tqdm scipy
53
+ pip install h5py numpy pandas requests tqdm scipy rtree pyproj shapely xarray rasterio
54
54
  pip install --upgrade ras-commander
55
55
  ```
56
56
 
@@ -70,9 +70,9 @@ class HdfBndry:
70
70
  return gpd.GeoDataFrame(
71
71
  {
72
72
  "bc_line_id": bc_line_ids,
73
- "name": names,
73
+ "Name": names,
74
74
  "mesh_name": mesh_names,
75
- "type": types,
75
+ "Type": types,
76
76
  "geometry": geoms,
77
77
  },
78
78
  geometry="geometry",
@@ -110,7 +110,7 @@ class HdfBndry:
110
110
  )
111
111
  geoms = HdfBndry._get_polylines(hdf_file, breaklines_path)
112
112
  return gpd.GeoDataFrame(
113
- {"bl_id": bl_line_ids, "name": names, "geometry": geoms},
113
+ {"bl_id": bl_line_ids, "Name": names, "geometry": geoms},
114
114
  geometry="geometry",
115
115
  crs=HdfUtils.projection(hdf_file),
116
116
  )
@@ -158,7 +158,7 @@ class HdfBndry:
158
158
  )
159
159
  )
160
160
  return gpd.GeoDataFrame(
161
- {"rr_id": rr_ids, "name": names, "geometry": geoms},
161
+ {"rr_id": rr_ids, "Name": names, "geometry": geoms},
162
162
  geometry="geometry",
163
163
  crs=HdfUtils.projection(hdf_file),
164
164
  )
@@ -242,9 +242,9 @@ class HdfBndry:
242
242
  return gpd.GeoDataFrame(
243
243
  {
244
244
  "refln_id": refline_ids,
245
- "refln_name": names,
246
- "mesh_name": mesh_names,
247
- "type": types,
245
+ "Name": names,
246
+ "mesh-name": mesh_names,
247
+ "Type": types,
248
248
  "geometry": geoms,
249
249
  },
250
250
  geometry="geometry",
@@ -286,9 +286,9 @@ class HdfBndry:
286
286
  return gpd.GeoDataFrame(
287
287
  {
288
288
  "refpt_id": range(attributes.shape[0]),
289
- "refpt_name": names,
289
+ "Name": names,
290
290
  "mesh_name": mesh_names,
291
- "cell_id": cell_id,
291
+ "Cell Index": cell_id,
292
292
  "geometry": list(map(Point, points)),
293
293
  },
294
294
  geometry="geometry",
@@ -0,0 +1,317 @@
1
+ from typing import Dict, List, Tuple
2
+ import pandas as pd
3
+ import geopandas as gpd
4
+ import matplotlib.pyplot as plt
5
+ from collections import defaultdict
6
+ from rtree import index
7
+ from shapely.geometry import LineString, MultiLineString
8
+ from tqdm import tqdm
9
+
10
+ from .HdfMesh import HdfMesh
11
+ from .HdfUtils import HdfUtils
12
+
13
+ class HdfFluvialPluvial:
14
+ """
15
+ A class for analyzing and visualizing fluvial-pluvial boundaries in HEC-RAS 2D model results.
16
+
17
+ This class provides methods to process and visualize HEC-RAS 2D model outputs,
18
+ specifically focusing on the delineation of fluvial and pluvial flood areas.
19
+ It includes functionality for plotting maximum water surface elevations,
20
+ extracting cell polygons, and calculating fluvial-pluvial boundaries based on
21
+ the timing of maximum water surface elevations.
22
+
23
+ Key Features:
24
+ - Plotting maximum water surface elevations and their timing
25
+ - Extracting and visualizing 2D flow area cell polygons
26
+ - Calculating and visualizing fluvial-pluvial boundaries
27
+
28
+ Data Requirements:
29
+ 1. For plotting maximum water surface:
30
+ - Use HdfResultsMesh.mesh_max_ws(hdf_path) to get max_ws_df
31
+ - Use HdfResultsMesh.mesh_timeseries_output(hdf_path, mesh_name, 'water_surface')
32
+ to get time series data
33
+
34
+ 2. For extracting cell polygons:
35
+ - Use HdfMesh.mesh_cell_polygons(geom_hdf_path) to get cell_polygons_df
36
+ - Use HdfUtils.projection(hdf_path) to get the projection
37
+
38
+ 3. For calculating fluvial-pluvial boundary:
39
+ - Requires cell_polygons_gdf (from step 2)
40
+ - Requires max_ws_df with 'cell_id' and 'max_wsel_time' columns
41
+ (can be derived from HdfResultsMesh.mesh_max_ws(hdf_path))
42
+
43
+ Usage:
44
+ To use this class effectively, first initialize a RasPrj object and load the
45
+ necessary HDF files. Then, use the methods provided to analyze and visualize
46
+ the fluvial-pluvial characteristics of your 2D model results.
47
+
48
+ Example:
49
+ ras = RasPrj()
50
+ ras = init_ras_project(project_path, ras_version)
51
+ hdf_path = ras.get_plan_value(plan_number, 'Results_Output')
52
+
53
+ # Get maximum water surface data
54
+ max_ws_df = HdfResultsMesh.mesh_max_ws(hdf_path)
55
+
56
+ # Plot maximum water surface
57
+ HdfFluvialPluvial.plot_max_water_surface(max_ws_df)
58
+
59
+ # Extract cell polygons
60
+ cell_polygons_df = HdfMesh.mesh_cell_polygons(hdf_path)
61
+ projection = HdfUtils.projection(hdf_path)
62
+ cell_polygons_gdf = HdfFluvialPluvial.plot_cell_polygons(cell_polygons_df, projection)
63
+
64
+ # Calculate fluvial-pluvial boundary
65
+ boundary_gdf = HdfFluvialPluvial.calculate_fluvial_pluvial_boundary(cell_polygons_gdf, max_ws_df)
66
+
67
+ Note: Ensure that you have the necessary permissions and have initialized
68
+ the RAS project correctly before attempting to access HDF files.
69
+ """
70
+
71
+ @staticmethod
72
+ def plot_max_water_surface(max_ws_df):
73
+ """
74
+ Plots the maximum water surface elevation per cell.
75
+
76
+ Parameters:
77
+ - max_ws_df: DataFrame containing merged data with coordinates and max water surface.
78
+
79
+ Returns:
80
+ - None
81
+ """
82
+ # Extract x and y coordinates from the geometry column
83
+ max_ws_df['x'] = max_ws_df['geometry'].apply(lambda geom: geom.x if geom is not None else None)
84
+ max_ws_df['y'] = max_ws_df['geometry'].apply(lambda geom: geom.y if geom is not None else None)
85
+
86
+ # Check if 'x' and 'y' columns exist in max_ws_df
87
+ if 'x' not in max_ws_df.columns or 'y' not in max_ws_df.columns:
88
+ print("Error: 'x' or 'y' columns not found in the merged dataframe.")
89
+ print("Available columns:", max_ws_df.columns.tolist())
90
+ return
91
+
92
+ # Create the plot
93
+ fig, ax = plt.subplots(figsize=(12, 8))
94
+ scatter = ax.scatter(max_ws_df['x'], max_ws_df['y'], c=max_ws_df['maximum_water_surface'], cmap='viridis', s=10)
95
+
96
+ # Customize the plot
97
+ ax.set_title('Max Water Surface per Cell')
98
+ ax.set_xlabel('X Coordinate')
99
+ ax.set_ylabel('Y Coordinate')
100
+ plt.colorbar(scatter, label='Max Water Surface (ft)')
101
+
102
+ # Add grid lines
103
+ ax.grid(True, linestyle='--', alpha=0.7)
104
+
105
+ # Increase font size for better readability
106
+ plt.rcParams.update({'font.size': 12})
107
+
108
+ # Adjust layout to prevent cutting off labels
109
+ plt.tight_layout()
110
+
111
+ # Show the plot
112
+ plt.show()
113
+
114
+
115
+
116
+
117
+ @staticmethod
118
+ def plot_max_wsel_time(max_ws_df: pd.DataFrame) -> None:
119
+ """
120
+ Plots the time of the maximum water surface elevation (WSEL) per cell.
121
+
122
+ Parameters:
123
+ - max_ws_df: DataFrame containing merged data with coordinates and max water surface.
124
+
125
+ Returns:
126
+ - None
127
+ """
128
+ max_ws_df['max_wsel_time'] = pd.to_datetime(max_ws_df['maximum_water_surface_time'])
129
+ HdfFluvialPluvial._extract_coordinates(max_ws_df)
130
+
131
+ if 'x' not in max_ws_df.columns or 'y' not in max_ws_df.columns:
132
+ raise ValueError("x and y coordinates are missing from the DataFrame. Make sure the 'face_point' column exists and contains valid coordinate data.")
133
+
134
+ fig, ax = plt.subplots(figsize=(12, 8))
135
+
136
+ min_time = max_ws_df['max_wsel_time'].min()
137
+ color_values = (max_ws_df['max_wsel_time'] - min_time).dt.total_seconds() / 3600
138
+
139
+ scatter = ax.scatter(max_ws_df['x'], max_ws_df['y'], c=color_values, cmap='viridis', s=10)
140
+
141
+ ax.set_title('Time of Maximum Water Surface Elevation per Cell')
142
+ ax.set_xlabel('X Coordinate')
143
+ ax.set_ylabel('Y Coordinate')
144
+
145
+ cbar = plt.colorbar(scatter)
146
+ cbar.set_label('Hours since simulation start')
147
+ cbar.set_ticks(range(0, int(color_values.max()) + 1, 6))
148
+ cbar.set_ticklabels([f'{h}h' for h in range(0, int(color_values.max()) + 1, 6)])
149
+
150
+ ax.grid(True, linestyle='--', alpha=0.7)
151
+ plt.rcParams.update({'font.size': 12})
152
+ plt.tight_layout()
153
+ plt.show()
154
+
155
+ HdfFluvialPluvial._print_max_wsel_info(max_ws_df, min_time)
156
+
157
+ @staticmethod
158
+ def plot_cell_polygons(cell_polygons_df: pd.DataFrame, projection: str) -> gpd.GeoDataFrame:
159
+ """
160
+ Plots the cell polygons from the provided DataFrame and returns the GeoDataFrame.
161
+
162
+ Args:
163
+ cell_polygons_df (pd.DataFrame): DataFrame containing cell polygons.
164
+ projection (str): The coordinate reference system to assign to the GeoDataFrame.
165
+
166
+ Returns:
167
+ gpd.GeoDataFrame: GeoDataFrame containing the cell polygons.
168
+ """
169
+ if cell_polygons_df.empty:
170
+ print("No Cell Polygons found.")
171
+ return None
172
+
173
+ cell_polygons_gdf = HdfFluvialPluvial._convert_to_geodataframe(cell_polygons_df, projection)
174
+
175
+ print("Cell Polygons CRS:", cell_polygons_gdf.crs)
176
+ display(cell_polygons_gdf.head())
177
+
178
+ fig, ax = plt.subplots(figsize=(12, 8))
179
+ cell_polygons_gdf.plot(ax=ax, edgecolor='blue', facecolor='none')
180
+ ax.set_xlabel('X Coordinate')
181
+ ax.set_ylabel('Y Coordinate')
182
+ ax.set_title('2D Flow Area Cell Polygons')
183
+ ax.grid(True)
184
+ plt.tight_layout()
185
+ plt.show()
186
+
187
+ return cell_polygons_gdf
188
+
189
+ @staticmethod
190
+ def calculate_fluvial_pluvial_boundary(cell_polygons_gdf: gpd.GeoDataFrame, max_ws_df: pd.DataFrame, delta_t: float = 12) -> gpd.GeoDataFrame:
191
+ """
192
+ Calculate the fluvial-pluvial boundary based on cell polygons and maximum water surface elevation times.
193
+
194
+ Args:
195
+ cell_polygons_gdf (gpd.GeoDataFrame): GeoDataFrame containing cell polygons with 'cell_id' and 'geometry' columns.
196
+ max_ws_df (pd.DataFrame): DataFrame containing 'cell_id' and 'max_wsel_time' columns.
197
+ delta_t (float): Threshold time difference in hours. Default is 12 hours.
198
+
199
+ Returns:
200
+ gpd.GeoDataFrame: GeoDataFrame containing the fluvial-pluvial boundary as simple LineStrings.
201
+ """
202
+ cell_adjacency, common_edges = HdfFluvialPluvial._process_cell_adjacencies(cell_polygons_gdf)
203
+ cell_times = max_ws_df.set_index('cell_id')['max_wsel_time'].to_dict()
204
+ boundary_edges = HdfFluvialPluvial._identify_boundary_edges(cell_adjacency, common_edges, cell_times, delta_t)
205
+
206
+ # Join adjacent LineStrings into simple LineStrings
207
+ joined_lines = []
208
+ current_line = []
209
+
210
+ for edge in boundary_edges:
211
+ if not current_line:
212
+ current_line.append(edge)
213
+ else:
214
+ if current_line[-1].coords[-1] == edge.coords[0]: # Check if the last point of the current line matches the first point of the new edge
215
+ current_line.append(edge)
216
+ else:
217
+ # Create a simple LineString from the current line and reset
218
+ joined_lines.append(LineString([point for line in current_line for point in line.coords]))
219
+ current_line = [edge]
220
+
221
+ # Add the last collected line if exists
222
+ if current_line:
223
+ joined_lines.append(LineString([point for line in current_line for point in line.coords]))
224
+
225
+ boundary_gdf = gpd.GeoDataFrame(geometry=joined_lines, crs=cell_polygons_gdf.crs)
226
+ return boundary_gdf
227
+
228
+ @staticmethod
229
+ def _print_max_wsel_info(max_ws_df: pd.DataFrame, min_time: pd.Timestamp) -> None:
230
+ max_wsel_row = max_ws_df.loc[max_ws_df['maximum_water_surface'].idxmax()]
231
+ hours_since_start = (max_wsel_row['max_wsel_time'] - min_time).total_seconds() / 3600
232
+ print(f"\nOverall Maximum WSEL: {max_wsel_row['maximum_water_surface']:.2f} ft")
233
+ print(f"Time of Overall Maximum WSEL: {max_wsel_row['max_wsel_time']}")
234
+ print(f"Hours since simulation start: {hours_since_start:.2f} hours")
235
+ print(f"Location of Overall Maximum WSEL: X={max_wsel_row['x']}, Y={max_wsel_row['y']}")
236
+
237
+ @staticmethod
238
+ def _process_cell_adjacencies(cell_polygons_gdf: gpd.GeoDataFrame) -> Tuple[Dict[int, List[int]], Dict[int, Dict[int, LineString]]]:
239
+ cell_adjacency = defaultdict(list)
240
+ common_edges = defaultdict(dict)
241
+ idx = index.Index()
242
+ for i, geom in enumerate(cell_polygons_gdf.geometry):
243
+ idx.insert(i, geom.bounds)
244
+
245
+ with tqdm(total=len(cell_polygons_gdf), desc="Processing cell adjacencies") as pbar:
246
+ for idx1, row1 in cell_polygons_gdf.iterrows():
247
+ cell_id1 = row1['cell_id']
248
+ poly1 = row1['geometry']
249
+ potential_neighbors = list(idx.intersection(poly1.bounds))
250
+
251
+ for idx2 in potential_neighbors:
252
+ if idx1 >= idx2:
253
+ continue
254
+
255
+ row2 = cell_polygons_gdf.iloc[idx2]
256
+ cell_id2 = row2['cell_id']
257
+ poly2 = row2['geometry']
258
+
259
+ if poly1.touches(poly2):
260
+ intersection = poly1.intersection(poly2)
261
+ if isinstance(intersection, LineString):
262
+ cell_adjacency[cell_id1].append(cell_id2)
263
+ cell_adjacency[cell_id2].append(cell_id1)
264
+ common_edges[cell_id1][cell_id2] = intersection
265
+ common_edges[cell_id2][cell_id1] = intersection
266
+
267
+ pbar.update(1)
268
+
269
+ return cell_adjacency, common_edges
270
+
271
+ @staticmethod
272
+ def _identify_boundary_edges(cell_adjacency: Dict[int, List[int]], common_edges: Dict[int, Dict[int, LineString]], cell_times: Dict[int, pd.Timestamp], delta_t: float) -> List[LineString]:
273
+ boundary_edges = []
274
+ with tqdm(total=len(cell_adjacency), desc="Processing cell adjacencies") as pbar:
275
+ for cell_id, neighbors in cell_adjacency.items():
276
+ cell_time = cell_times[cell_id]
277
+
278
+ for neighbor_id in neighbors:
279
+ neighbor_time = cell_times[neighbor_id]
280
+ time_diff = abs((cell_time - neighbor_time).total_seconds() / 3600)
281
+
282
+ if time_diff >= delta_t:
283
+ boundary_edges.append(common_edges[cell_id][neighbor_id])
284
+
285
+ pbar.update(1)
286
+ return boundary_edges
287
+
288
+ @staticmethod
289
+ def _extract_coordinates(df: pd.DataFrame) -> None:
290
+ """
291
+ Extract x and y coordinates from the 'face_point' column.
292
+
293
+ Parameters:
294
+ - df: DataFrame containing the 'face_point' column.
295
+
296
+ Returns:
297
+ - None (modifies the DataFrame in-place)
298
+ """
299
+ if 'face_point' in df.columns:
300
+ df[['x', 'y']] = df['face_point'].str.strip('()').str.split(',', expand=True).astype(float)
301
+ else:
302
+ print("Warning: 'face_point' column not found in the DataFrame.")
303
+
304
+ @staticmethod
305
+ def _convert_to_geodataframe(df: pd.DataFrame, projection: str) -> gpd.GeoDataFrame:
306
+ """
307
+ Convert a DataFrame to a GeoDataFrame.
308
+
309
+ Parameters:
310
+ - df: DataFrame containing 'geometry' column.
311
+ - projection: The coordinate reference system to assign to the GeoDataFrame.
312
+
313
+ Returns:
314
+ - GeoDataFrame with the specified projection.
315
+ """
316
+ gdf = gpd.GeoDataFrame(df, geometry='geometry', crs=projection)
317
+ return gdf
@@ -39,8 +39,6 @@ class HdfMesh:
39
39
  Note: This class relies on HdfBase and HdfUtils for some underlying operations.
40
40
  """
41
41
 
42
- FLOW_AREA_2D_PATH = "Geometry/2D Flow Areas"
43
-
44
42
  def __init__(self):
45
43
  self.logger = logging.getLogger(__name__)
46
44
 
@@ -62,12 +60,12 @@ class HdfMesh:
62
60
  """
63
61
  try:
64
62
  with h5py.File(hdf_path, 'r') as hdf_file:
65
- if HdfMesh.FLOW_AREA_2D_PATH not in hdf_file:
63
+ if "Geometry/2D Flow Areas" not in hdf_file:
66
64
  return list()
67
65
  return list(
68
66
  [
69
- HdfUtils.convert_ras_hdf_string(n)
70
- for n in hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Attributes"][()]["Name"]
67
+ HdfUtils.convert_ras_hdf_string(n.decode('utf-8')) # Decode as UTF-8
68
+ for n in hdf_file["Geometry/2D Flow Areas/Attributes"][()]["Name"]
71
69
  ]
72
70
  )
73
71
  except Exception as e:
@@ -96,7 +94,7 @@ class HdfMesh:
96
94
  if not mesh_area_names:
97
95
  return GeoDataFrame()
98
96
  mesh_area_polygons = [
99
- Polygon(hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
97
+ Polygon(hdf_file["Geometry/2D Flow Areas/{}/Perimeter".format(n)][()])
100
98
  for n in mesh_area_names
101
99
  ]
102
100
  return GeoDataFrame(
@@ -134,13 +132,13 @@ class HdfMesh:
134
132
 
135
133
  cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
136
134
  for i, mesh_name in enumerate(mesh_area_names):
137
- cell_cnt = hdf_file[f"{HdfMesh.FLOW_AREA_2D_PATH}/Cell Info"][()][i][1]
135
+ cell_cnt = hdf_file["Geometry/2D Flow Areas/Cell Info"][()][i][1]
138
136
  cell_ids = list(range(cell_cnt))
139
137
  cell_face_info = hdf_file[
140
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Info"
138
+ "Geometry/2D Flow Areas/{}/Cells Face and Orientation Info".format(mesh_name)
141
139
  ][()]
142
140
  cell_face_values = hdf_file[
143
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Values"
141
+ "Geometry/2D Flow Areas/{}/Cells Face and Orientation Values".format(mesh_name)
144
142
  ][()][:, 0]
145
143
  face_id_lists = list(
146
144
  np.vectorize(
@@ -176,12 +174,11 @@ class HdfMesh:
176
174
  except Exception as e:
177
175
  logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
178
176
  return GeoDataFrame()
179
-
180
177
  @staticmethod
181
178
  @standardize_input(file_type='plan_hdf')
182
179
  def mesh_cell_points(hdf_path: Path) -> GeoDataFrame:
183
180
  """
184
- Return 2D flow mesh cell points.
181
+ Return 2D flow mesh cell center points.
185
182
 
186
183
  Parameters
187
184
  ----------
@@ -191,29 +188,29 @@ class HdfMesh:
191
188
  Returns
192
189
  -------
193
190
  GeoDataFrame
194
- A GeoDataFrame containing the 2D flow mesh cell points.
191
+ A GeoDataFrame containing the 2D flow mesh cell center points.
195
192
  """
196
193
  try:
197
194
  with h5py.File(hdf_path, 'r') as hdf_file:
198
195
  mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
199
196
  if not mesh_area_names:
200
197
  return GeoDataFrame()
198
+
201
199
  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)
200
+ 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)
203
+
204
+ pnt_dict["mesh_name"] += [mesh_name] * cell_count
205
+ pnt_dict["cell_id"] += range(cell_count)
209
206
  pnt_dict["geometry"] += list(
210
207
  np.vectorize(lambda coords: Point(coords), signature="(n)->()")(
211
- cell_pnt_coords
208
+ cell_center_coords
212
209
  )
213
210
  )
214
211
  return GeoDataFrame(pnt_dict, geometry="geometry", crs=HdfUtils.projection(hdf_path))
215
212
  except Exception as e:
216
- self.logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
213
+ logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
217
214
  return GeoDataFrame()
218
215
 
219
216
  @staticmethod
@@ -240,16 +237,16 @@ class HdfMesh:
240
237
  face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
241
238
  for mesh_name in mesh_area_names:
242
239
  facepoints_index = hdf_file[
243
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
240
+ "Geometry/2D Flow Areas/{}/Faces FacePoint Indexes".format(mesh_name)
244
241
  ][()]
245
242
  facepoints_coordinates = hdf_file[
246
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
243
+ "Geometry/2D Flow Areas/{}/FacePoints Coordinate".format(mesh_name)
247
244
  ][()]
248
245
  faces_perimeter_info = hdf_file[
249
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
246
+ "Geometry/2D Flow Areas/{}/Faces Perimeter Info".format(mesh_name)
250
247
  ][()]
251
248
  faces_perimeter_values = hdf_file[
252
- f"{HdfMesh.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Values"
249
+ "Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
253
250
  ][()]
254
251
  face_id = -1
255
252
  for pnt_a_index, pnt_b_index in facepoints_index:
@@ -288,14 +285,14 @@ class HdfMesh:
288
285
  """
289
286
  try:
290
287
  with h5py.File(hdf_path, 'r') as hdf_file:
291
- d2_flow_area = hdf_file.get(f"{HdfMesh.FLOW_AREA_2D_PATH}/Attributes")
288
+ d2_flow_area = hdf_file.get("Geometry/2D Flow Areas/Attributes")
292
289
  if d2_flow_area is not None and isinstance(d2_flow_area, h5py.Dataset):
293
290
  result = {}
294
291
  for name in d2_flow_area.dtype.names:
295
292
  try:
296
293
  value = d2_flow_area[name][()]
297
294
  if isinstance(value, bytes):
298
- value = value.decode('utf-8')
295
+ value = value.decode('utf-8') # Decode as UTF-8
299
296
  result[name] = value
300
297
  except Exception as e:
301
298
  logger.warning(f"Error converting attribute '{name}': {str(e)}")
@@ -306,3 +303,52 @@ class HdfMesh:
306
303
  except Exception as e:
307
304
  logger.error(f"Error reading 2D flow area attributes from {hdf_path}: {str(e)}")
308
305
  return {}
306
+
307
+
308
+ @staticmethod
309
+ @standardize_input(file_type='geom_hdf')
310
+ def get_face_property_tables(hdf_path: Path) -> Dict[str, pd.DataFrame]:
311
+ """
312
+ Extract Face Property Tables for each Face in all 2D Flow Areas.
313
+
314
+ Parameters
315
+ ----------
316
+ hdf_path : Path
317
+ Path to the HEC-RAS geometry HDF file.
318
+
319
+ Returns
320
+ -------
321
+ Dict[str, pd.DataFrame]
322
+ A dictionary where keys are mesh names and values are DataFrames
323
+ containing the Face Property Tables for all faces in that mesh.
324
+ """
325
+ try:
326
+ with h5py.File(hdf_path, 'r') as hdf_file:
327
+ mesh_area_names = HdfMesh.mesh_area_names(hdf_path)
328
+ if not mesh_area_names:
329
+ return {}
330
+
331
+ result = {}
332
+ for mesh_name in mesh_area_names:
333
+ area_elevation_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Info"][()]
334
+ area_elevation_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Area Elevation Values"][()]
335
+
336
+ face_data = []
337
+ for face_id, (start_index, count) in enumerate(area_elevation_info):
338
+ face_values = area_elevation_values[start_index:start_index+count]
339
+ for z, area, wetted_perimeter, mannings_n in face_values:
340
+ face_data.append({
341
+ 'Face ID': face_id,
342
+ 'Z': z.decode('utf-8'), # Decode as UTF-8
343
+ 'Area': area.decode('utf-8'), # Decode as UTF-8
344
+ 'Wetted Perimeter': wetted_perimeter.decode('utf-8'), # Decode as UTF-8
345
+ "Manning's n": mannings_n.decode('utf-8') # Decode as UTF-8
346
+ })
347
+
348
+ result[mesh_name] = pd.DataFrame(face_data)
349
+
350
+ return result
351
+
352
+ except Exception as e:
353
+ logger.error(f"Error extracting face property tables from {hdf_path}: {str(e)}")
354
+ return {}