ras-commander 0.48.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.
- ras_commander/Decorators.py +18 -1
- ras_commander/HdfBase.py +307 -197
- ras_commander/HdfBndry.py +94 -287
- ras_commander/HdfFluvialPluvial.py +256 -273
- ras_commander/HdfInfiltration.py +410 -0
- ras_commander/HdfMesh.py +222 -114
- ras_commander/HdfPipe.py +127 -175
- ras_commander/HdfPlan.py +144 -58
- ras_commander/HdfPlot.py +104 -0
- ras_commander/HdfPump.py +76 -28
- ras_commander/HdfResultsMesh.py +190 -183
- ras_commander/HdfResultsPlan.py +76 -220
- ras_commander/HdfResultsPlot.py +182 -0
- ras_commander/HdfResultsXsec.py +185 -145
- ras_commander/HdfStruc.py +65 -35
- ras_commander/HdfUtils.py +435 -518
- ras_commander/HdfXsec.py +137 -127
- ras_commander/LoggingConfig.py +13 -3
- ras_commander/RasCmdr.py +13 -0
- ras_commander/RasExamples.py +14 -0
- ras_commander/RasGeo.py +11 -0
- ras_commander/RasGpt.py +8 -0
- ras_commander/RasMapper.py +105 -0
- ras_commander/RasPlan.py +30 -0
- ras_commander/RasPrj.py +34 -0
- ras_commander/RasToGo.py +16 -0
- ras_commander/RasUnsteady.py +15 -0
- ras_commander/RasUtils.py +31 -0
- ras_commander/__init__.py +10 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/METADATA +77 -9
- ras_commander-0.50.0.dist-info/RECORD +34 -0
- ras_commander-0.48.0.dist-info/RECORD +0 -30
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.48.0.dist-info → ras_commander-0.50.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,29 @@
|
|
1
|
+
"""
|
2
|
+
Class: HdfFluvialPluvial
|
3
|
+
|
4
|
+
All of the methods in this class are static and are designed to be used without instantiation.
|
5
|
+
|
6
|
+
List of Functions in HdfFluvialPluvial:
|
7
|
+
- calculate_fluvial_pluvial_boundary()
|
8
|
+
- _process_cell_adjacencies()
|
9
|
+
- _identify_boundary_edges()
|
10
|
+
|
11
|
+
"""
|
12
|
+
|
1
13
|
from typing import Dict, List, Tuple
|
2
14
|
import pandas as pd
|
3
15
|
import geopandas as gpd
|
4
|
-
import matplotlib.pyplot as plt
|
5
16
|
from collections import defaultdict
|
6
|
-
from shapely.geometry import LineString, MultiLineString
|
17
|
+
from shapely.geometry import LineString, MultiLineString # Added MultiLineString import
|
7
18
|
from tqdm import tqdm
|
8
|
-
|
9
19
|
from .HdfMesh import HdfMesh
|
10
20
|
from .HdfUtils import HdfUtils
|
21
|
+
from .Decorators import standardize_input
|
22
|
+
from .HdfResultsMesh import HdfResultsMesh
|
23
|
+
from .LoggingConfig import get_logger
|
24
|
+
from pathlib import Path
|
25
|
+
|
26
|
+
logger = get_logger(__name__)
|
11
27
|
|
12
28
|
class HdfFluvialPluvial:
|
13
29
|
"""
|
@@ -15,312 +31,279 @@ class HdfFluvialPluvial:
|
|
15
31
|
|
16
32
|
This class provides methods to process and visualize HEC-RAS 2D model outputs,
|
17
33
|
specifically focusing on the delineation of fluvial and pluvial flood areas.
|
18
|
-
It includes functionality for
|
19
|
-
extracting cell polygons, and calculating fluvial-pluvial boundaries based on
|
34
|
+
It includes functionality for calculating fluvial-pluvial boundaries based on
|
20
35
|
the timing of maximum water surface elevations.
|
21
36
|
|
22
|
-
Key
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
37
|
+
Key Concepts:
|
38
|
+
- Fluvial flooding: Flooding from rivers/streams
|
39
|
+
- Pluvial flooding: Flooding from rainfall/surface water
|
40
|
+
- Delta_t: Time threshold (in hours) used to distinguish between fluvial and pluvial cells.
|
41
|
+
Cells with max WSE time differences greater than delta_t are considered boundaries.
|
26
42
|
|
27
43
|
Data Requirements:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
- Requires max_ws_df with 'cell_id' and 'max_wsel_time' columns
|
40
|
-
(can be derived from HdfResultsMesh.mesh_max_ws(hdf_path))
|
41
|
-
|
42
|
-
Usage:
|
43
|
-
To use this class effectively, first initialize a RasPrj object and load the
|
44
|
-
necessary HDF files. Then, use the methods provided to analyze and visualize
|
45
|
-
the fluvial-pluvial characteristics of your 2D model results.
|
46
|
-
|
47
|
-
Example:
|
48
|
-
ras = RasPrj()
|
49
|
-
ras = init_ras_project(project_path, ras_version)
|
50
|
-
hdf_path = ras.get_plan_value(plan_number, 'Results_Output')
|
51
|
-
|
52
|
-
# Get maximum water surface data
|
53
|
-
max_ws_df = HdfResultsMesh.mesh_max_ws(hdf_path)
|
54
|
-
|
55
|
-
# Plot maximum water surface
|
56
|
-
HdfFluvialPluvial.plot_max_water_surface(max_ws_df)
|
57
|
-
|
58
|
-
# Extract cell polygons
|
59
|
-
cell_polygons_df = HdfMesh.mesh_cell_polygons(hdf_path)
|
60
|
-
projection = HdfUtils.projection(hdf_path)
|
61
|
-
cell_polygons_gdf = HdfFluvialPluvial.plot_cell_polygons(cell_polygons_df, projection)
|
62
|
-
|
63
|
-
# Calculate fluvial-pluvial boundary
|
64
|
-
boundary_gdf = HdfFluvialPluvial.calculate_fluvial_pluvial_boundary(cell_polygons_gdf, max_ws_df)
|
65
|
-
|
66
|
-
Note: Ensure that you have the necessary permissions and have initialized
|
67
|
-
the RAS project correctly before attempting to access HDF files.
|
44
|
+
- HEC-RAS plan HDF file containing:
|
45
|
+
- 2D mesh cell geometry (accessed via HdfMesh)
|
46
|
+
- Maximum water surface elevation times (accessed via HdfResultsMesh)
|
47
|
+
|
48
|
+
Usage Example:
|
49
|
+
>>> ras = init_ras_project(project_path, ras_version)
|
50
|
+
>>> hdf_path = Path("path/to/plan.hdf")
|
51
|
+
>>> boundary_gdf = HdfFluvialPluvial.calculate_fluvial_pluvial_boundary(
|
52
|
+
... hdf_path,
|
53
|
+
... delta_t=12
|
54
|
+
... )
|
68
55
|
"""
|
69
|
-
|
70
|
-
|
71
|
-
def plot_max_water_surface(max_ws_df):
|
72
|
-
"""
|
73
|
-
Plots the maximum water surface elevation per cell.
|
74
|
-
|
75
|
-
Parameters:
|
76
|
-
- max_ws_df: DataFrame containing merged data with coordinates and max water surface.
|
77
|
-
|
78
|
-
Returns:
|
79
|
-
- None
|
80
|
-
"""
|
81
|
-
# Extract x and y coordinates from the geometry column
|
82
|
-
max_ws_df['x'] = max_ws_df['geometry'].apply(lambda geom: geom.x if geom is not None else None)
|
83
|
-
max_ws_df['y'] = max_ws_df['geometry'].apply(lambda geom: geom.y if geom is not None else None)
|
84
|
-
|
85
|
-
# Check if 'x' and 'y' columns exist in max_ws_df
|
86
|
-
if 'x' not in max_ws_df.columns or 'y' not in max_ws_df.columns:
|
87
|
-
print("Error: 'x' or 'y' columns not found in the merged dataframe.")
|
88
|
-
print("Available columns:", max_ws_df.columns.tolist())
|
89
|
-
return
|
90
|
-
|
91
|
-
# Create the plot
|
92
|
-
fig, ax = plt.subplots(figsize=(12, 8))
|
93
|
-
scatter = ax.scatter(max_ws_df['x'], max_ws_df['y'], c=max_ws_df['maximum_water_surface'], cmap='viridis', s=10)
|
94
|
-
|
95
|
-
# Customize the plot
|
96
|
-
ax.set_title('Max Water Surface per Cell')
|
97
|
-
ax.set_xlabel('X Coordinate')
|
98
|
-
ax.set_ylabel('Y Coordinate')
|
99
|
-
plt.colorbar(scatter, label='Max Water Surface (ft)')
|
100
|
-
|
101
|
-
# Add grid lines
|
102
|
-
ax.grid(True, linestyle='--', alpha=0.7)
|
103
|
-
|
104
|
-
# Increase font size for better readability
|
105
|
-
plt.rcParams.update({'font.size': 12})
|
106
|
-
|
107
|
-
# Adjust layout to prevent cutting off labels
|
108
|
-
plt.tight_layout()
|
109
|
-
|
110
|
-
# Show the plot
|
111
|
-
plt.show()
|
112
|
-
|
56
|
+
def __init__(self):
|
57
|
+
self.logger = get_logger(__name__) # Initialize logger with module name
|
113
58
|
|
114
|
-
|
115
|
-
|
116
59
|
@staticmethod
|
117
|
-
|
60
|
+
@standardize_input(file_type='plan_hdf')
|
61
|
+
def calculate_fluvial_pluvial_boundary(hdf_path: Path, delta_t: float = 12) -> gpd.GeoDataFrame:
|
118
62
|
"""
|
119
|
-
|
63
|
+
Calculate the fluvial-pluvial boundary based on cell polygons and maximum water surface elevation times.
|
120
64
|
|
121
|
-
|
122
|
-
|
65
|
+
Args:
|
66
|
+
hdf_path (Path): Path to the HEC-RAS plan HDF file
|
67
|
+
delta_t (float): Threshold time difference in hours. Cells with time differences
|
68
|
+
greater than this value are considered boundaries. Default is 12 hours.
|
123
69
|
|
124
70
|
Returns:
|
125
|
-
|
71
|
+
gpd.GeoDataFrame: GeoDataFrame containing the fluvial-pluvial boundaries with:
|
72
|
+
- geometry: LineString features representing boundaries
|
73
|
+
- CRS: Coordinate reference system matching the input HDF file
|
74
|
+
|
75
|
+
Raises:
|
76
|
+
ValueError: If no cell polygons or maximum water surface data found in HDF file
|
77
|
+
Exception: If there are errors during boundary calculation
|
78
|
+
|
79
|
+
Note:
|
80
|
+
The returned boundaries represent locations where the timing of maximum water surface
|
81
|
+
elevation changes significantly (> delta_t), indicating potential transitions between
|
82
|
+
fluvial and pluvial flooding mechanisms.
|
126
83
|
"""
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
84
|
+
try:
|
85
|
+
# Get cell polygons from HdfMesh
|
86
|
+
logger.info("Getting cell polygons from HDF file...")
|
87
|
+
cell_polygons_gdf = HdfMesh.get_mesh_cell_polygons(hdf_path)
|
88
|
+
if cell_polygons_gdf.empty:
|
89
|
+
raise ValueError("No cell polygons found in HDF file")
|
90
|
+
|
91
|
+
# Get max water surface data from HdfResultsMesh
|
92
|
+
logger.info("Getting maximum water surface data from HDF file...")
|
93
|
+
max_ws_df = HdfResultsMesh.get_mesh_max_ws(hdf_path)
|
94
|
+
if max_ws_df.empty:
|
95
|
+
raise ValueError("No maximum water surface data found in HDF file")
|
96
|
+
|
97
|
+
# Convert timestamps using the renamed utility function
|
98
|
+
logger.info("Converting maximum water surface timestamps...")
|
99
|
+
if 'maximum_water_surface_time' in max_ws_df.columns:
|
100
|
+
max_ws_df['maximum_water_surface_time'] = max_ws_df['maximum_water_surface_time'].apply(
|
101
|
+
lambda x: HdfUtils.parse_ras_datetime(x) if isinstance(x, str) else x
|
102
|
+
)
|
103
|
+
|
104
|
+
# Process cell adjacencies
|
105
|
+
logger.info("Processing cell adjacencies...")
|
106
|
+
cell_adjacency, common_edges = HdfFluvialPluvial._process_cell_adjacencies(cell_polygons_gdf)
|
107
|
+
|
108
|
+
# Get cell times from max_ws_df
|
109
|
+
logger.info("Extracting cell times from maximum water surface data...")
|
110
|
+
cell_times = max_ws_df.set_index('cell_id')['maximum_water_surface_time'].to_dict()
|
111
|
+
|
112
|
+
# Identify boundary edges
|
113
|
+
logger.info("Identifying boundary edges...")
|
114
|
+
boundary_edges = HdfFluvialPluvial._identify_boundary_edges(
|
115
|
+
cell_adjacency, common_edges, cell_times, delta_t
|
116
|
+
)
|
117
|
+
|
118
|
+
# Join adjacent LineStrings into simple LineStrings
|
119
|
+
logger.info("Joining adjacent LineStrings into simple LineStrings...")
|
120
|
+
joined_lines = []
|
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))
|
186
|
+
|
187
|
+
# Create final GeoDataFrame with CRS from cell_polygons_gdf
|
188
|
+
logger.info("Creating final GeoDataFrame for boundaries...")
|
189
|
+
boundary_gdf = gpd.GeoDataFrame(
|
190
|
+
geometry=joined_lines,
|
191
|
+
crs=cell_polygons_gdf.crs
|
192
|
+
)
|
193
|
+
|
194
|
+
# Clean up intermediate dataframes
|
195
|
+
logger.info("Cleaning up intermediate dataframes...")
|
196
|
+
del cell_polygons_gdf
|
197
|
+
del max_ws_df
|
198
|
+
|
199
|
+
logger.info("Fluvial-pluvial boundary calculation completed successfully.")
|
200
|
+
return boundary_gdf
|
201
|
+
|
202
|
+
except Exception as e:
|
203
|
+
self.logger.error(f"Error calculating fluvial-pluvial boundary: {str(e)}")
|
204
|
+
return None
|
205
|
+
|
206
|
+
|
156
207
|
@staticmethod
|
157
|
-
def
|
208
|
+
def _process_cell_adjacencies(cell_polygons_gdf: gpd.GeoDataFrame) -> Tuple[Dict[int, List[int]], Dict[int, Dict[int, LineString]]]:
|
158
209
|
"""
|
159
|
-
|
160
|
-
|
210
|
+
Optimized method to process cell adjacencies by extracting shared edges directly.
|
211
|
+
|
161
212
|
Args:
|
162
|
-
|
163
|
-
|
213
|
+
cell_polygons_gdf (gpd.GeoDataFrame): GeoDataFrame containing 2D mesh cell polygons
|
214
|
+
with 'cell_id' and 'geometry' columns.
|
164
215
|
|
165
216
|
Returns:
|
166
|
-
|
217
|
+
Tuple containing:
|
218
|
+
- Dict[int, List[int]]: Dictionary mapping cell IDs to lists of adjacent cell IDs.
|
219
|
+
- Dict[int, Dict[int, LineString]]: Nested dictionary storing common edges between cells,
|
220
|
+
where common_edges[cell1][cell2] gives the shared boundary.
|
167
221
|
"""
|
168
|
-
|
169
|
-
|
170
|
-
return None
|
171
|
-
|
172
|
-
cell_polygons_gdf = HdfFluvialPluvial._convert_to_geodataframe(cell_polygons_df, projection)
|
173
|
-
|
174
|
-
print("Cell Polygons CRS:", cell_polygons_gdf.crs)
|
175
|
-
display(cell_polygons_gdf.head())
|
176
|
-
|
177
|
-
fig, ax = plt.subplots(figsize=(12, 8))
|
178
|
-
cell_polygons_gdf.plot(ax=ax, edgecolor='blue', facecolor='none')
|
179
|
-
ax.set_xlabel('X Coordinate')
|
180
|
-
ax.set_ylabel('Y Coordinate')
|
181
|
-
ax.set_title('2D Flow Area Cell Polygons')
|
182
|
-
ax.grid(True)
|
183
|
-
plt.tight_layout()
|
184
|
-
plt.show()
|
222
|
+
cell_adjacency = defaultdict(list)
|
223
|
+
common_edges = defaultdict(dict)
|
185
224
|
|
186
|
-
|
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.")
|
271
|
+
return cell_adjacency, common_edges
|
187
272
|
|
188
273
|
@staticmethod
|
189
|
-
def
|
274
|
+
def _identify_boundary_edges(cell_adjacency: Dict[int, List[int]],
|
275
|
+
common_edges: Dict[int, Dict[int, LineString]],
|
276
|
+
cell_times: Dict[int, pd.Timestamp],
|
277
|
+
delta_t: float) -> List[LineString]:
|
190
278
|
"""
|
191
|
-
|
279
|
+
Identify boundary edges between cells with significant time differences.
|
192
280
|
|
193
281
|
Args:
|
194
|
-
|
195
|
-
|
196
|
-
|
282
|
+
cell_adjacency (Dict[int, List[int]]): Dictionary of cell adjacencies
|
283
|
+
common_edges (Dict[int, Dict[int, LineString]]): Dictionary of shared edges between cells
|
284
|
+
cell_times (Dict[int, pd.Timestamp]): Dictionary mapping cell IDs to their max WSE times
|
285
|
+
delta_t (float): Time threshold in hours
|
197
286
|
|
198
287
|
Returns:
|
199
|
-
|
200
|
-
|
201
|
-
cell_adjacency, common_edges = HdfFluvialPluvial._process_cell_adjacencies(cell_polygons_gdf)
|
202
|
-
cell_times = max_ws_df.set_index('cell_id')['max_wsel_time'].to_dict()
|
203
|
-
boundary_edges = HdfFluvialPluvial._identify_boundary_edges(cell_adjacency, common_edges, cell_times, delta_t)
|
204
|
-
|
205
|
-
# Join adjacent LineStrings into simple LineStrings
|
206
|
-
joined_lines = []
|
207
|
-
current_line = []
|
208
|
-
|
209
|
-
for edge in boundary_edges:
|
210
|
-
if not current_line:
|
211
|
-
current_line.append(edge)
|
212
|
-
else:
|
213
|
-
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
|
214
|
-
current_line.append(edge)
|
215
|
-
else:
|
216
|
-
# Create a simple LineString from the current line and reset
|
217
|
-
joined_lines.append(LineString([point for line in current_line for point in line.coords]))
|
218
|
-
current_line = [edge]
|
219
|
-
|
220
|
-
# Add the last collected line if exists
|
221
|
-
if current_line:
|
222
|
-
joined_lines.append(LineString([point for line in current_line for point in line.coords]))
|
223
|
-
|
224
|
-
boundary_gdf = gpd.GeoDataFrame(geometry=joined_lines, crs=cell_polygons_gdf.crs)
|
225
|
-
return boundary_gdf
|
226
|
-
|
227
|
-
@staticmethod
|
228
|
-
def _print_max_wsel_info(max_ws_df: pd.DataFrame, min_time: pd.Timestamp) -> None:
|
229
|
-
max_wsel_row = max_ws_df.loc[max_ws_df['maximum_water_surface'].idxmax()]
|
230
|
-
hours_since_start = (max_wsel_row['max_wsel_time'] - min_time).total_seconds() / 3600
|
231
|
-
print(f"\nOverall Maximum WSEL: {max_wsel_row['maximum_water_surface']:.2f} ft")
|
232
|
-
print(f"Time of Overall Maximum WSEL: {max_wsel_row['max_wsel_time']}")
|
233
|
-
print(f"Hours since simulation start: {hours_since_start:.2f} hours")
|
234
|
-
print(f"Location of Overall Maximum WSEL: X={max_wsel_row['x']}, Y={max_wsel_row['y']}")
|
288
|
+
List[LineString]: List of LineString geometries representing boundaries where
|
289
|
+
adjacent cells have time differences greater than delta_t
|
235
290
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
Process cell adjacencies and common edges using R-tree indexing.
|
291
|
+
Note:
|
292
|
+
Boundaries are identified where the absolute time difference between adjacent
|
293
|
+
cells exceeds the specified delta_t threshold.
|
240
294
|
"""
|
241
|
-
# Install rtree using pip install rtree if not already installed
|
242
|
-
from rtree import index
|
243
|
-
cell_adjacency = defaultdict(list)
|
244
|
-
common_edges = defaultdict(dict)
|
245
|
-
idx = index.Index()
|
246
|
-
for i, geom in enumerate(cell_polygons_gdf.geometry):
|
247
|
-
idx.insert(i, geom.bounds)
|
248
|
-
|
249
|
-
with tqdm(total=len(cell_polygons_gdf), desc="Processing cell adjacencies") as pbar:
|
250
|
-
for idx1, row1 in cell_polygons_gdf.iterrows():
|
251
|
-
cell_id1 = row1['cell_id']
|
252
|
-
poly1 = row1['geometry']
|
253
|
-
potential_neighbors = list(idx.intersection(poly1.bounds))
|
254
|
-
|
255
|
-
for idx2 in potential_neighbors:
|
256
|
-
if idx1 >= idx2:
|
257
|
-
continue
|
258
|
-
|
259
|
-
row2 = cell_polygons_gdf.iloc[idx2]
|
260
|
-
cell_id2 = row2['cell_id']
|
261
|
-
poly2 = row2['geometry']
|
262
|
-
|
263
|
-
if poly1.touches(poly2):
|
264
|
-
intersection = poly1.intersection(poly2)
|
265
|
-
if isinstance(intersection, LineString):
|
266
|
-
cell_adjacency[cell_id1].append(cell_id2)
|
267
|
-
cell_adjacency[cell_id2].append(cell_id1)
|
268
|
-
common_edges[cell_id1][cell_id2] = intersection
|
269
|
-
common_edges[cell_id2][cell_id1] = intersection
|
270
|
-
|
271
|
-
pbar.update(1)
|
272
|
-
|
273
|
-
return cell_adjacency, common_edges
|
274
|
-
|
275
|
-
@staticmethod
|
276
|
-
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]:
|
277
295
|
boundary_edges = []
|
278
296
|
with tqdm(total=len(cell_adjacency), desc="Processing cell adjacencies") as pbar:
|
279
297
|
for cell_id, neighbors in cell_adjacency.items():
|
280
298
|
cell_time = cell_times[cell_id]
|
281
|
-
|
299
|
+
|
282
300
|
for neighbor_id in neighbors:
|
283
301
|
neighbor_time = cell_times[neighbor_id]
|
284
302
|
time_diff = abs((cell_time - neighbor_time).total_seconds() / 3600)
|
285
|
-
|
303
|
+
|
286
304
|
if time_diff >= delta_t:
|
287
305
|
boundary_edges.append(common_edges[cell_id][neighbor_id])
|
288
|
-
|
289
|
-
pbar.update(1)
|
290
|
-
return boundary_edges
|
291
|
-
|
292
|
-
@staticmethod
|
293
|
-
def _extract_coordinates(df: pd.DataFrame) -> None:
|
294
|
-
"""
|
295
|
-
Extract x and y coordinates from the 'face_point' column.
|
296
|
-
|
297
|
-
Parameters:
|
298
|
-
- df: DataFrame containing the 'face_point' column.
|
299
|
-
|
300
|
-
Returns:
|
301
|
-
- None (modifies the DataFrame in-place)
|
302
|
-
"""
|
303
|
-
if 'face_point' in df.columns:
|
304
|
-
df[['x', 'y']] = df['face_point'].str.strip('()').str.split(',', expand=True).astype(float)
|
305
|
-
else:
|
306
|
-
print("Warning: 'face_point' column not found in the DataFrame.")
|
307
|
-
|
308
|
-
@staticmethod
|
309
|
-
def _convert_to_geodataframe(df: pd.DataFrame, projection: str) -> gpd.GeoDataFrame:
|
310
|
-
"""
|
311
|
-
Convert a DataFrame to a GeoDataFrame.
|
312
|
-
|
313
|
-
Parameters:
|
314
|
-
- df: DataFrame containing 'geometry' column.
|
315
|
-
- projection: The coordinate reference system to assign to the GeoDataFrame.
|
316
|
-
|
317
|
-
Returns:
|
318
|
-
- GeoDataFrame with the specified projection.
|
319
|
-
"""
|
320
|
-
gdf = gpd.GeoDataFrame(df, geometry='geometry', crs=projection)
|
321
|
-
|
322
|
-
return gdf
|
323
|
-
|
324
|
-
|
325
306
|
|
307
|
+
pbar.update(1)
|
326
308
|
|
309
|
+
return boundary_edges
|