ras-commander 0.49.0__py3-none-any.whl → 0.50.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.
@@ -14,7 +14,7 @@ from typing import Dict, List, Tuple
14
14
  import pandas as pd
15
15
  import geopandas as gpd
16
16
  from collections import defaultdict
17
- from shapely.geometry import LineString
17
+ from shapely.geometry import LineString, MultiLineString # Added MultiLineString import
18
18
  from tqdm import tqdm
19
19
  from .HdfMesh import HdfMesh
20
20
  from .HdfUtils import HdfUtils
@@ -53,7 +53,9 @@ class HdfFluvialPluvial:
53
53
  ... delta_t=12
54
54
  ... )
55
55
  """
56
-
56
+ def __init__(self):
57
+ self.logger = get_logger(__name__) # Initialize logger with module name
58
+
57
59
  @staticmethod
58
60
  @standardize_input(file_type='plan_hdf')
59
61
  def calculate_fluvial_pluvial_boundary(hdf_path: Path, delta_t: float = 12) -> gpd.GeoDataFrame:
@@ -93,106 +95,179 @@ class HdfFluvialPluvial:
93
95
  raise ValueError("No maximum water surface data found in HDF file")
94
96
 
95
97
  # Convert timestamps using the renamed utility function
98
+ logger.info("Converting maximum water surface timestamps...")
96
99
  if 'maximum_water_surface_time' in max_ws_df.columns:
97
100
  max_ws_df['maximum_water_surface_time'] = max_ws_df['maximum_water_surface_time'].apply(
98
101
  lambda x: HdfUtils.parse_ras_datetime(x) if isinstance(x, str) else x
99
102
  )
100
103
 
101
104
  # Process cell adjacencies
105
+ logger.info("Processing cell adjacencies...")
102
106
  cell_adjacency, common_edges = HdfFluvialPluvial._process_cell_adjacencies(cell_polygons_gdf)
103
107
 
104
108
  # Get cell times from max_ws_df
109
+ logger.info("Extracting cell times from maximum water surface data...")
105
110
  cell_times = max_ws_df.set_index('cell_id')['maximum_water_surface_time'].to_dict()
106
111
 
107
112
  # Identify boundary edges
113
+ logger.info("Identifying boundary edges...")
108
114
  boundary_edges = HdfFluvialPluvial._identify_boundary_edges(
109
115
  cell_adjacency, common_edges, cell_times, delta_t
110
116
  )
111
117
 
112
118
  # Join adjacent LineStrings into simple LineStrings
119
+ logger.info("Joining adjacent LineStrings into simple LineStrings...")
113
120
  joined_lines = []
114
- current_line = []
115
-
116
- for edge in boundary_edges:
117
- if not current_line:
118
- current_line.append(edge)
119
- else:
120
- if current_line[-1].coords[-1] == edge.coords[0]:
121
- current_line.append(edge)
122
- else:
123
- joined_lines.append(LineString([point for line in current_line for point in line.coords]))
124
- current_line = [edge]
125
-
126
- if current_line:
127
- joined_lines.append(LineString([point for line in current_line for point in line.coords]))
121
+
122
+ def get_coords(geom):
123
+ """Helper function to get coordinates from either LineString or MultiLineString"""
124
+ if isinstance(geom, LineString):
125
+ return list(geom.coords)
126
+ elif isinstance(geom, MultiLineString):
127
+ return list(geom.geoms[0].coords)
128
+ return None
129
+
130
+ # Create a dictionary to store start and end points for each line
131
+ line_endpoints = {}
132
+ for i, edge in enumerate(boundary_edges):
133
+ coords = get_coords(edge)
134
+ if coords:
135
+ line_endpoints[i] = (coords[0], coords[-1])
136
+
137
+ # Process lines in order
138
+ used_indices = set()
139
+ while len(used_indices) < len(boundary_edges):
140
+ current_line = []
141
+ current_points = []
142
+
143
+ # Find a new starting line if needed
144
+ for i in range(len(boundary_edges)):
145
+ if i not in used_indices:
146
+ current_line.append(boundary_edges[i])
147
+ coords = get_coords(boundary_edges[i])
148
+ if coords:
149
+ current_points.extend(coords)
150
+ used_indices.add(i)
151
+ break
152
+
153
+ # Continue adding connected lines
154
+ while True:
155
+ found_next = False
156
+ current_end = current_points[-1] if current_points else None
157
+
158
+ # Look for the next connected line
159
+ for i, (start, end) in line_endpoints.items():
160
+ if i not in used_indices and current_end:
161
+ if start == current_end:
162
+ # Add line in forward direction
163
+ coords = get_coords(boundary_edges[i])
164
+ if coords:
165
+ current_points.extend(coords[1:]) # Skip first point to avoid duplication
166
+ current_line.append(boundary_edges[i])
167
+ used_indices.add(i)
168
+ found_next = True
169
+ break
170
+ elif end == current_end:
171
+ # Add line in reverse direction
172
+ coords = get_coords(boundary_edges[i])
173
+ if coords:
174
+ current_points.extend(reversed(coords[:-1])) # Skip last point to avoid duplication
175
+ current_line.append(boundary_edges[i])
176
+ used_indices.add(i)
177
+ found_next = True
178
+ break
179
+
180
+ if not found_next:
181
+ break
182
+
183
+ # Create a single LineString from the collected points
184
+ if current_points:
185
+ joined_lines.append(LineString(current_points))
128
186
 
129
187
  # Create final GeoDataFrame with CRS from cell_polygons_gdf
188
+ logger.info("Creating final GeoDataFrame for boundaries...")
130
189
  boundary_gdf = gpd.GeoDataFrame(
131
190
  geometry=joined_lines,
132
191
  crs=cell_polygons_gdf.crs
133
192
  )
134
193
 
135
194
  # Clean up intermediate dataframes
195
+ logger.info("Cleaning up intermediate dataframes...")
136
196
  del cell_polygons_gdf
137
197
  del max_ws_df
138
198
 
199
+ logger.info("Fluvial-pluvial boundary calculation completed successfully.")
139
200
  return boundary_gdf
140
201
 
141
202
  except Exception as e:
142
- logger.error(f"Error calculating fluvial-pluvial boundary: {str(e)}")
143
- raise
144
-
203
+ self.logger.error(f"Error calculating fluvial-pluvial boundary: {str(e)}")
204
+ return None
205
+
206
+
145
207
  @staticmethod
146
208
  def _process_cell_adjacencies(cell_polygons_gdf: gpd.GeoDataFrame) -> Tuple[Dict[int, List[int]], Dict[int, Dict[int, LineString]]]:
147
209
  """
148
- Process cell adjacencies and common edges using R-tree spatial indexing for efficiency.
149
-
210
+ Optimized method to process cell adjacencies by extracting shared edges directly.
211
+
150
212
  Args:
151
213
  cell_polygons_gdf (gpd.GeoDataFrame): GeoDataFrame containing 2D mesh cell polygons
152
- with 'cell_id' and 'geometry' columns
214
+ with 'cell_id' and 'geometry' columns.
153
215
 
154
216
  Returns:
155
217
  Tuple containing:
156
- - Dict[int, List[int]]: Dictionary mapping cell IDs to lists of adjacent cell IDs
218
+ - Dict[int, List[int]]: Dictionary mapping cell IDs to lists of adjacent cell IDs.
157
219
  - Dict[int, Dict[int, LineString]]: Nested dictionary storing common edges between cells,
158
- where common_edges[cell1][cell2] gives the shared boundary
159
-
160
- Note:
161
- Uses R-tree spatial indexing to efficiently identify potential neighboring cells
162
- before performing more detailed geometric operations.
220
+ where common_edges[cell1][cell2] gives the shared boundary.
163
221
  """
164
- from rtree import index
165
222
  cell_adjacency = defaultdict(list)
166
223
  common_edges = defaultdict(dict)
167
- idx = index.Index()
168
-
169
- for i, geom in enumerate(cell_polygons_gdf.geometry):
170
- idx.insert(i, geom.bounds)
171
-
172
- with tqdm(total=len(cell_polygons_gdf), desc="Processing cell adjacencies") as pbar:
173
- for idx1, row1 in cell_polygons_gdf.iterrows():
174
- cell_id1 = row1['cell_id']
175
- poly1 = row1['geometry']
176
- potential_neighbors = list(idx.intersection(poly1.bounds))
177
-
178
- for idx2 in potential_neighbors:
179
- if idx1 >= idx2:
180
- continue
181
-
182
- row2 = cell_polygons_gdf.iloc[idx2]
183
- cell_id2 = row2['cell_id']
184
- poly2 = row2['geometry']
185
-
186
- if poly1.touches(poly2):
187
- intersection = poly1.intersection(poly2)
188
- if isinstance(intersection, LineString):
189
- cell_adjacency[cell_id1].append(cell_id2)
190
- cell_adjacency[cell_id2].append(cell_id1)
191
- common_edges[cell_id1][cell_id2] = intersection
192
- common_edges[cell_id2][cell_id1] = intersection
193
-
194
- pbar.update(1)
195
224
 
225
+ # Build an edge to cells mapping
226
+ edge_to_cells = defaultdict(set)
227
+
228
+ # Function to generate edge keys
229
+ def edge_key(coords1, coords2, precision=8):
230
+ # Round coordinates
231
+ coords1 = tuple(round(coord, precision) for coord in coords1)
232
+ coords2 = tuple(round(coord, precision) for coord in coords2)
233
+ # Create sorted key to handle edge direction
234
+ return tuple(sorted([coords1, coords2]))
235
+
236
+ # For each polygon, extract edges
237
+ for idx, row in cell_polygons_gdf.iterrows():
238
+ cell_id = row['cell_id']
239
+ geom = row['geometry']
240
+ if geom.is_empty or not geom.is_valid:
241
+ continue
242
+ # Get exterior coordinates
243
+ coords = list(geom.exterior.coords)
244
+ num_coords = len(coords)
245
+ for i in range(num_coords - 1):
246
+ coord1 = coords[i]
247
+ coord2 = coords[i + 1]
248
+ key = edge_key(coord1, coord2)
249
+ edge_to_cells[key].add(cell_id)
250
+
251
+ # Now, process edge_to_cells to build adjacency
252
+ for edge, cells in edge_to_cells.items():
253
+ cells = list(cells)
254
+ if len(cells) >= 2:
255
+ # For all pairs of cells sharing this edge
256
+ for i in range(len(cells)):
257
+ for j in range(i + 1, len(cells)):
258
+ cell1 = cells[i]
259
+ cell2 = cells[j]
260
+ # Update adjacency
261
+ if cell2 not in cell_adjacency[cell1]:
262
+ cell_adjacency[cell1].append(cell2)
263
+ if cell1 not in cell_adjacency[cell2]:
264
+ cell_adjacency[cell2].append(cell1)
265
+ # Store common edge
266
+ common_edge = LineString([edge[0], edge[1]])
267
+ common_edges[cell1][cell2] = common_edge
268
+ common_edges[cell2][cell1] = common_edge
269
+
270
+ logger.info("Cell adjacencies processed successfully.")
196
271
  return cell_adjacency, common_edges
197
272
 
198
273
  @staticmethod
ras_commander/HdfMesh.py CHANGED
@@ -9,15 +9,25 @@ All methods are designed to work with the mesh geometry data stored in
9
9
  HEC-RAS HDF files, providing functionality to retrieve and process various aspects
10
10
  of the 2D flow areas and their associated mesh structures.
11
11
 
12
+
12
13
  List of Functions:
13
14
  -----------------
14
15
  get_mesh_area_names()
15
16
  Returns list of 2D mesh area names
16
17
  get_mesh_areas()
17
18
  Returns 2D flow area perimeter polygons
18
- mesh_cell_polygons()
19
+ get_mesh_cell_polygons()
19
20
  Returns 2D flow mesh cell polygons
20
- [etc...]
21
+ get_mesh_cell_points()
22
+ Returns 2D flow mesh cell center points
23
+ get_mesh_cell_faces()
24
+ Returns 2D flow mesh cell faces
25
+ get_mesh_area_attributes()
26
+ Returns geometry 2D flow area attributes
27
+ get_mesh_face_property_tables()
28
+ Returns Face Property Tables for each Face in all 2D Flow Areas
29
+ get_mesh_cell_property_tables()
30
+ Returns Cell Property Tables for each Cell in all 2D Flow Areas
21
31
 
22
32
  Each function is decorated with @standardize_input and @log_call for consistent
23
33
  input handling and logging functionality.
@@ -148,49 +158,45 @@ class HdfMesh:
148
158
  if not mesh_area_names:
149
159
  return GeoDataFrame()
150
160
 
161
+ # Get face geometries once
151
162
  face_gdf = HdfMesh.get_mesh_cell_faces(hdf_path)
163
+
164
+ # Pre-allocate lists for better memory efficiency
165
+ all_mesh_names = []
166
+ all_cell_ids = []
167
+ all_geometries = []
168
+
169
+ for mesh_name in mesh_area_names:
170
+ # Get cell face info in one read
171
+ cell_face_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"][()]
172
+ cell_face_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"][()][:, 0]
173
+
174
+ # Create face lookup dictionary for this mesh
175
+ mesh_faces_dict = dict(face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]].values)
176
+
177
+ # Process each cell
178
+ for cell_id, (start, length) in enumerate(cell_face_info[:, :2]):
179
+ face_ids = cell_face_values[start:start + length]
180
+ face_geoms = [mesh_faces_dict[face_id] for face_id in face_ids]
181
+
182
+ # Create polygon
183
+ polygons = list(polygonize(face_geoms))
184
+ if polygons:
185
+ all_mesh_names.append(mesh_name)
186
+ all_cell_ids.append(cell_id)
187
+ all_geometries.append(Polygon(polygons[0]))
188
+
189
+ # Create GeoDataFrame in one go
190
+ return GeoDataFrame(
191
+ {
192
+ "mesh_name": all_mesh_names,
193
+ "cell_id": all_cell_ids,
194
+ "geometry": all_geometries
195
+ },
196
+ geometry="geometry",
197
+ crs=HdfBase.get_projection(hdf_file)
198
+ )
152
199
 
153
- cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
154
- for i, mesh_name in enumerate(mesh_area_names):
155
- cell_cnt = hdf_file["Geometry/2D Flow Areas/Cell Info"][()][i][1]
156
- cell_ids = list(range(cell_cnt))
157
- cell_face_info = hdf_file[
158
- "Geometry/2D Flow Areas/{}/Cells Face and Orientation Info".format(mesh_name)
159
- ][()]
160
- cell_face_values = hdf_file[
161
- "Geometry/2D Flow Areas/{}/Cells Face and Orientation Values".format(mesh_name)
162
- ][()][:, 0]
163
- face_id_lists = list(
164
- np.vectorize(
165
- lambda cell_id: str(
166
- cell_face_values[
167
- cell_face_info[cell_id][0] : cell_face_info[cell_id][0]
168
- + cell_face_info[cell_id][1]
169
- ]
170
- )
171
- )(cell_ids)
172
- )
173
- mesh_faces = (
174
- face_gdf[face_gdf.mesh_name == mesh_name][["face_id", "geometry"]]
175
- .set_index("face_id")
176
- .to_numpy()
177
- )
178
- cell_dict["mesh_name"] += [mesh_name] * cell_cnt
179
- cell_dict["cell_id"] += cell_ids
180
- cell_dict["geometry"] += list(
181
- np.vectorize(
182
- lambda face_id_list: (
183
- lambda geom_col: Polygon(list(polygonize(geom_col))[0])
184
- )(
185
- np.ravel(
186
- mesh_faces[
187
- np.array(face_id_list.strip("[]").split()).astype(int)
188
- ]
189
- )
190
- )
191
- )(face_id_lists)
192
- )
193
- return GeoDataFrame(cell_dict, geometry="geometry", crs=HdfBase.get_projection(hdf_file))
194
200
  except Exception as e:
195
201
  logger.error(f"Error reading mesh cell polygons from {hdf_path}: {str(e)}")
196
202
  return GeoDataFrame()
@@ -217,19 +223,32 @@ class HdfMesh:
217
223
  if not mesh_area_names:
218
224
  return GeoDataFrame()
219
225
 
220
- pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
226
+ # Pre-allocate lists
227
+ all_mesh_names = []
228
+ all_cell_ids = []
229
+ all_points = []
230
+
221
231
  for mesh_name in mesh_area_names:
222
- cell_center_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
223
- cell_count = len(cell_center_coords)
232
+ # Get all cell centers in one read
233
+ cell_centers = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Cells Center Coordinate"][()]
234
+ cell_count = len(cell_centers)
224
235
 
225
- pnt_dict["mesh_name"] += [mesh_name] * cell_count
226
- pnt_dict["cell_id"] += range(cell_count)
227
- pnt_dict["geometry"] += list(
228
- np.vectorize(lambda coords: Point(coords), signature="(n)->()")(
229
- cell_center_coords
230
- )
231
- )
232
- return GeoDataFrame(pnt_dict, geometry="geometry", crs=HdfBase.get_projection(hdf_path))
236
+ # Extend lists efficiently
237
+ all_mesh_names.extend([mesh_name] * cell_count)
238
+ all_cell_ids.extend(range(cell_count))
239
+ all_points.extend(Point(coords) for coords in cell_centers)
240
+
241
+ # Create GeoDataFrame in one go
242
+ return GeoDataFrame(
243
+ {
244
+ "mesh_name": all_mesh_names,
245
+ "cell_id": all_cell_ids,
246
+ "geometry": all_points
247
+ },
248
+ geometry="geometry",
249
+ crs=HdfBase.get_projection(hdf_file)
250
+ )
251
+
233
252
  except Exception as e:
234
253
  logger.error(f"Error reading mesh cell points from {hdf_path}: {str(e)}")
235
254
  return GeoDataFrame()
@@ -255,37 +274,45 @@ class HdfMesh:
255
274
  mesh_area_names = HdfMesh.get_mesh_area_names(hdf_path)
256
275
  if not mesh_area_names:
257
276
  return GeoDataFrame()
258
- face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
277
+
278
+ # Pre-allocate lists
279
+ all_mesh_names = []
280
+ all_face_ids = []
281
+ all_geometries = []
282
+
259
283
  for mesh_name in mesh_area_names:
260
- facepoints_index = hdf_file[
261
- "Geometry/2D Flow Areas/{}/Faces FacePoint Indexes".format(mesh_name)
262
- ][()]
263
- facepoints_coordinates = hdf_file[
264
- "Geometry/2D Flow Areas/{}/FacePoints Coordinate".format(mesh_name)
265
- ][()]
266
- faces_perimeter_info = hdf_file[
267
- "Geometry/2D Flow Areas/{}/Faces Perimeter Info".format(mesh_name)
268
- ][()]
269
- faces_perimeter_values = hdf_file[
270
- "Geometry/2D Flow Areas/{}/Faces Perimeter Values".format(mesh_name)
271
- ][()]
272
- face_id = -1
273
- for pnt_a_index, pnt_b_index in facepoints_index:
274
- face_id += 1
275
- face_dict["mesh_name"].append(mesh_name)
276
- face_dict["face_id"].append(face_id)
277
- coordinates = list()
278
- coordinates.append(facepoints_coordinates[pnt_a_index])
279
- starting_row, count = faces_perimeter_info[face_id]
284
+ # Read all data at once
285
+ facepoints_index = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"][()]
286
+ facepoints_coords = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"][()]
287
+ faces_perim_info = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"][()]
288
+ faces_perim_values = hdf_file[f"Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"][()]
289
+
290
+ # Process each face
291
+ for face_id, ((pnt_a_idx, pnt_b_idx), (start_row, count)) in enumerate(zip(facepoints_index, faces_perim_info)):
292
+ coords = [facepoints_coords[pnt_a_idx]]
293
+
280
294
  if count > 0:
281
- coordinates += list(
282
- faces_perimeter_values[starting_row : starting_row + count]
283
- )
284
- coordinates.append(facepoints_coordinates[pnt_b_index])
285
- face_dict["geometry"].append(LineString(coordinates))
286
- return GeoDataFrame(face_dict, geometry="geometry", crs=HdfBase.get_projection(hdf_path))
295
+ coords.extend(faces_perim_values[start_row:start_row + count])
296
+
297
+ coords.append(facepoints_coords[pnt_b_idx])
298
+
299
+ all_mesh_names.append(mesh_name)
300
+ all_face_ids.append(face_id)
301
+ all_geometries.append(LineString(coords))
302
+
303
+ # Create GeoDataFrame in one go
304
+ return GeoDataFrame(
305
+ {
306
+ "mesh_name": all_mesh_names,
307
+ "face_id": all_face_ids,
308
+ "geometry": all_geometries
309
+ },
310
+ geometry="geometry",
311
+ crs=HdfBase.get_projection(hdf_file)
312
+ )
313
+
287
314
  except Exception as e:
288
- self.logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
315
+ logger.error(f"Error reading mesh cell faces from {hdf_path}: {str(e)}")
289
316
  return GeoDataFrame()
290
317
 
291
318
  @staticmethod
ras_commander/HdfPlot.py CHANGED
@@ -22,7 +22,7 @@ class HdfPlot:
22
22
  @staticmethod
23
23
  @log_call
24
24
  def plot_mesh_cells(
25
- cell_polygons_df: pd.DataFrame,
25
+ cell_polygons_df: pd.DataFrame, ## THIS IS A GEODATAFRAME - NEED TO EDIT BOTH ARGUMENT AND USAGE
26
26
  projection: str,
27
27
  title: str = '2D Flow Area Mesh Cells',
28
28
  figsize: Tuple[int, int] = (12, 8)
@@ -74,6 +74,7 @@ from .HdfBase import HdfBase
74
74
  from .HdfUtils import HdfUtils
75
75
  from .Decorators import log_call, standardize_input
76
76
  from .LoggingConfig import setup_logging, get_logger
77
+ import geopandas as gpd
77
78
 
78
79
  logger = get_logger(__name__)
79
80
 
@@ -234,7 +235,7 @@ class HdfResultsMesh:
234
235
  @staticmethod
235
236
  @log_call
236
237
  @standardize_input(file_type='plan_hdf')
237
- def get_mesh_max_ws(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
238
+ def get_mesh_max_ws(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
238
239
  """
239
240
  Get maximum water surface elevation for each mesh cell.
240
241
 
@@ -243,10 +244,7 @@ class HdfResultsMesh:
243
244
  round_to (str): Time rounding specification (default "100ms").
244
245
 
245
246
  Returns:
246
- pd.DataFrame: DataFrame containing maximum water surface elevations.
247
-
248
- Raises:
249
- ValueError: If there's an error processing the maximum water surface data.
247
+ gpd.GeoDataFrame: GeoDataFrame containing maximum water surface elevations with geometry.
250
248
  """
251
249
  try:
252
250
  with h5py.File(hdf_path, 'r') as hdf_file:
@@ -262,7 +260,7 @@ class HdfResultsMesh:
262
260
  @staticmethod
263
261
  @log_call
264
262
  @standardize_input(file_type='plan_hdf')
265
- def get_mesh_min_ws(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
263
+ def get_mesh_min_ws(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
266
264
  """
267
265
  Get minimum water surface elevation for each mesh cell.
268
266
 
@@ -271,7 +269,7 @@ class HdfResultsMesh:
271
269
  round_to (str): Time rounding specification (default "100ms").
272
270
 
273
271
  Returns:
274
- pd.DataFrame: DataFrame containing minimum water surface elevations.
272
+ gpd.GeoDataFrame: GeoDataFrame containing minimum water surface elevations with geometry.
275
273
  """
276
274
  try:
277
275
  with h5py.File(hdf_path, 'r') as hdf_file:
@@ -353,7 +351,7 @@ class HdfResultsMesh:
353
351
  @staticmethod
354
352
  @log_call
355
353
  @standardize_input(file_type='plan_hdf')
356
- def get_mesh_max_iter(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
354
+ def get_mesh_max_iter(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
357
355
  """
358
356
  Get maximum iteration count for each mesh cell.
359
357
 
@@ -362,36 +360,19 @@ class HdfResultsMesh:
362
360
  round_to (str): Time rounding specification (default "100ms").
363
361
 
364
362
  Returns:
365
- pd.DataFrame: DataFrame containing maximum iteration counts with face geometry.
366
-
367
- Raises:
368
- ValueError: If there's an error processing the maximum iteration data.
369
- """
370
- """
371
- Get maximum iteration count for each mesh cell.
372
-
373
- Args:
374
- hdf_path (Path): Path to the HDF file
375
- round_to (str): Time rounding specification (default "100ms").
376
-
377
- Returns:
378
- pd.DataFrame: DataFrame containing maximum iteration counts with columns:
363
+ gpd.GeoDataFrame: GeoDataFrame containing maximum iteration counts with geometry.
364
+ Includes columns:
379
365
  - mesh_name: Name of the mesh
380
366
  - cell_id: ID of the cell
381
367
  - cell_last_iteration: Maximum number of iterations
382
368
  - cell_last_iteration_time: Time when max iterations occurred
383
369
  - geometry: Point geometry representing cell center
384
-
385
- Raises:
386
- ValueError: If there's an error processing the maximum iteration data.
387
-
388
- Note: The Maximum Iteration is labeled as "Cell Last Iteration" in the HDF file
389
370
  """
390
371
  try:
391
372
  with h5py.File(hdf_path, 'r') as hdf_file:
392
373
  return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Cell Last Iteration", round_to)
393
374
  except Exception as e:
394
- logger.error(f"Error in mesh_max_iter: {str(e)}")
375
+ logger.error(f"Error in get_mesh_max_iter: {str(e)}")
395
376
  raise ValueError(f"Failed to get maximum iteration count: {str(e)}")
396
377
 
397
378
 
@@ -612,7 +593,7 @@ class HdfResultsMesh:
612
593
  @staticmethod
613
594
  @log_call
614
595
  @standardize_input(file_type='plan_hdf')
615
- def get_mesh_summary_output(hdf_file: h5py.File, var: str, round_to: str = "100ms") -> pd.DataFrame:
596
+ def get_mesh_summary_output(hdf_file: h5py.File, var: str, round_to: str = "100ms") -> gpd.GeoDataFrame:
616
597
  """
617
598
  Get the summary output data for a given variable from the HDF file.
618
599
 
@@ -627,8 +608,8 @@ class HdfResultsMesh:
627
608
 
628
609
  Returns
629
610
  -------
630
- pd.DataFrame
631
- A DataFrame containing the summary output data with attributes as metadata.
611
+ gpd.GeoDataFrame
612
+ A GeoDataFrame containing the summary output data with attributes as metadata.
632
613
 
633
614
  Raises
634
615
  ------
@@ -642,7 +623,7 @@ class HdfResultsMesh:
642
623
  logger.info(f"Processing summary output for variable: {var}")
643
624
  d2_flow_areas = hdf_file.get("Geometry/2D Flow Areas/Attributes")
644
625
  if d2_flow_areas is None:
645
- return pd.DataFrame()
626
+ return gpd.GeoDataFrame()
646
627
 
647
628
  for d2_flow_area in d2_flow_areas[:]:
648
629
  mesh_name = HdfUtils.convert_ras_string(d2_flow_area[0])
@@ -707,10 +688,18 @@ class HdfResultsMesh:
707
688
  dfs.append(df)
708
689
 
709
690
  if not dfs:
710
- return pd.DataFrame()
691
+ return gpd.GeoDataFrame()
711
692
 
712
693
  result = pd.concat(dfs, ignore_index=True)
713
694
 
695
+ # Convert to GeoDataFrame
696
+ gdf = gpd.GeoDataFrame(result, geometry='geometry')
697
+
698
+ # Get CRS from HdfUtils
699
+ crs = HdfBase.get_projection(hdf_file)
700
+ if crs:
701
+ gdf.set_crs(crs, inplace=True)
702
+
714
703
  # Combine attributes from all meshes
715
704
  combined_attrs = {}
716
705
  for df in dfs:
@@ -720,15 +709,14 @@ class HdfResultsMesh:
720
709
  elif combined_attrs[key] != value:
721
710
  combined_attrs[key] = f"Multiple values: {combined_attrs[key]}, {value}"
722
711
 
723
- result.attrs.update(combined_attrs)
712
+ gdf.attrs.update(combined_attrs)
724
713
 
725
- logger.info(f"Processed {len(result)} rows of summary output data")
726
- return result
714
+ logger.info(f"Processed {len(gdf)} rows of summary output data")
715
+ return gdf
727
716
 
728
717
  except Exception as e:
729
718
  logger.error(f"Error processing summary output data: {e}")
730
719
  raise ValueError(f"Error processing summary output data: {e}")
731
-
732
720
 
733
721
  @staticmethod
734
722
  def get_mesh_summary_output_group(hdf_file: h5py.File, mesh_name: str, var: str) -> Union[h5py.Group, h5py.Dataset]:
@@ -49,9 +49,19 @@ def setup_logging(log_file=None, log_level=logging.INFO):
49
49
 
50
50
  _logging_setup_done = True
51
51
 
52
- def get_logger(name):
53
- """Get a logger for a specific module."""
54
- return logging.getLogger(name)
52
+ def get_logger(name: str) -> logging.Logger:
53
+ """Get a logger instance with the specified name.
54
+
55
+ Args:
56
+ name: The name for the logger, typically __name__ or module path
57
+
58
+ Returns:
59
+ logging.Logger: Configured logger instance
60
+ """
61
+ logger = logging.getLogger(name)
62
+ if not logger.handlers: # Only add handler if none exists
63
+ setup_logging() # Ensure logging is configured
64
+ return logger
55
65
 
56
66
  def log_call(logger=None):
57
67
  """Decorator to log function calls."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ras-commander
3
- Version: 0.49.0
3
+ Version: 0.50.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,10 +61,13 @@ 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 xarray geopandas matplotlib ras-commander ipython psutil shapely fiona pathlib rtree
64
+ pip install h5py numpy pandas requests tqdm scipy xarray geopandas matplotlib ras-commander ipython psutil shapely fiona pathlib rtree rasterstats
65
65
  pip install --upgrade ras-commander
66
66
  ```
67
67
 
68
+ **Tested with Python 3.11**
69
+
70
+
68
71
  If you have dependency issues with pip (especially if you have errors with numpy), try clearing your local pip packages 'C:\Users\your_username\AppData\Roaming\Python\' and then creating a new virtual environment.
69
72
 
70
73
 
@@ -1,21 +1,21 @@
1
1
  ras_commander/Decorators.py,sha256=M5r5cHz_yy9YTHNEoytO9uwWbv_S-YYUk2QHNamJJJs,5848
2
2
  ras_commander/HdfBase.py,sha256=bJtSWdDUP4MYJ8QgkcixBEDFMSlslUdmVFHZxWMGMDM,11603
3
3
  ras_commander/HdfBndry.py,sha256=gcNr22vHNjEiWYigz-4aKUEYUi__3bRl4AeRAQQEmjs,12553
4
- ras_commander/HdfFluvialPluvial.py,sha256=V6RGpKmnX5E6XzAJPcexcPtC2Qfy56XpJi_Vq4KEV7o,10243
4
+ ras_commander/HdfFluvialPluvial.py,sha256=Bscvv0KBGnl05U7hH9RmsGqtPANDdTxgLwo7kXbTmTU,14281
5
5
  ras_commander/HdfInfiltration.py,sha256=QVigQJjYeQNutbazGHhbTmEuIVCb9gIb2f4yM-wyUtQ,15269
6
- ras_commander/HdfMesh.py,sha256=Yyom3i46AQFqI9cvhUyc5vh_g98aKAAsAjx7akuzsXw,18299
6
+ ras_commander/HdfMesh.py,sha256=R_O8tMsQvzbepa3hy1psO9t7TmYyMndnyZSh3602rFY,18683
7
7
  ras_commander/HdfPipe.py,sha256=m-yvPL2GIP23NKt2tcwzOlS7khvgcDPGAshlTPMUAeI,32154
8
8
  ras_commander/HdfPlan.py,sha256=NW6g2kS74y44Ci1P9iMo7IKUfR0eYOaJn1QbzsRM1co,10415
9
- ras_commander/HdfPlot.py,sha256=m4hbVkRbLW5a515jnM-iHSt5CLgtLSn5yxSpzRmgCGk,3337
9
+ ras_commander/HdfPlot.py,sha256=7MNI5T9qIz-Ava1RdlnB6O9oJElE5BEB29QVF5Y2Xuc,3401
10
10
  ras_commander/HdfPump.py,sha256=Vc2ff16kRISR7jwtnaAqxI0p-gfBSuZKzR3rQbBLQoE,12951
11
- ras_commander/HdfResultsMesh.py,sha256=RIwifjvVU3DuO4SpRvPsse7io9TpiJljAuNJe_K82wM,31316
11
+ ras_commander/HdfResultsMesh.py,sha256=T1afgFsJ1NaqmOJEJfMUBm1ZZ5pwbd82aAqeEkHNaLk,30959
12
12
  ras_commander/HdfResultsPlan.py,sha256=HoN3wvhj1wtkW-M20UHH9skntDqEvJEgeYeO_QBLF2w,11974
13
13
  ras_commander/HdfResultsPlot.py,sha256=ylzfT78CfgoDO0XAlRwlgMNRzvNQYBMn9eyXyBfjv_w,7660
14
14
  ras_commander/HdfResultsXsec.py,sha256=-P7nXnbjOLAeUnrdSC_lJQSfzrlWKmDF9Z5gEjmxbJY,13031
15
15
  ras_commander/HdfStruc.py,sha256=yzD4_eZLgwZDS3GdYVWqd85jRpdlRdVdbHHfPYTmwjA,12703
16
16
  ras_commander/HdfUtils.py,sha256=AbfV5RwbFyNR1SFGKO_izmVtRnDx68u_4wep5FK14xU,14814
17
17
  ras_commander/HdfXsec.py,sha256=uuogrJjRFytNHxmluQVuB57X1lILo1Y15wtc_VaHMX8,26809
18
- ras_commander/LoggingConfig.py,sha256=5bYd_5KMlf81bXsiu2mABBlw0USMhcu5uRv8DIYJSFE,2317
18
+ ras_commander/LoggingConfig.py,sha256=p1OJkQj5dsDdyBQqF0HWsvbsU88n9cYOc3YB2MMBYiw,2666
19
19
  ras_commander/RasCmdr.py,sha256=N2PI5n0P3ClLUPOPNcONJHGJYF-fIU5o0GXwHv4VATE,25271
20
20
  ras_commander/RasExamples.py,sha256=eYlRKryCG88FN5p23TnA1-E2Bxuaz3OxjdHPHJSqdB8,17006
21
21
  ras_commander/RasGeo.py,sha256=M0sVNKlWmmbve8iMXLWq25WgbxqLWBo7_1oDg_rALzU,5607
@@ -27,8 +27,8 @@ ras_commander/RasToGo.py,sha256=TKujfaV1xQhFaOddF4g2ogGy6ky-CLlfelSMPD2J3Nk,1223
27
27
  ras_commander/RasUnsteady.py,sha256=NWZbmB3-HT0W00K4-zxFN9OF8H_HlOY64nM72sHicWg,31154
28
28
  ras_commander/RasUtils.py,sha256=P2-aBL61kdRINsjnBpstZVD6VVc7hI_D3RUXqr6ldmc,34863
29
29
  ras_commander/__init__.py,sha256=vhnZQaejmyFVFP5fcYxAc4A562o8KFcnZNkcY6J5xwY,2068
30
- ras_commander-0.49.0.dist-info/LICENSE,sha256=_pbd6qHnlsz1iQ-ozDW_49r86BZT6CRwO2iBtw0iN6M,457
31
- ras_commander-0.49.0.dist-info/METADATA,sha256=EAK_gweDEyFXD8yvAFAywr33gxm7pFrGh37pdTrCrXU,18278
32
- ras_commander-0.49.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
33
- ras_commander-0.49.0.dist-info/top_level.txt,sha256=i76S7eKLFC8doKcXDl3aiOr9RwT06G8adI6YuKbQDaA,14
34
- ras_commander-0.49.0.dist-info/RECORD,,
30
+ ras_commander-0.50.0.dist-info/LICENSE,sha256=_pbd6qHnlsz1iQ-ozDW_49r86BZT6CRwO2iBtw0iN6M,457
31
+ ras_commander-0.50.0.dist-info/METADATA,sha256=Hjc2Tbk_T7EmArxWMbPzQels8FShtQ2ojkjLd73X6jw,18323
32
+ ras_commander-0.50.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
33
+ ras_commander-0.50.0.dist-info/top_level.txt,sha256=i76S7eKLFC8doKcXDl3aiOr9RwT06G8adI6YuKbQDaA,14
34
+ ras_commander-0.50.0.dist-info/RECORD,,