ras-commander 0.45.0__py3-none-any.whl → 0.47.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/HdfBndry.py +9 -9
- ras_commander/HdfFluvialPluvial.py +317 -0
- ras_commander/HdfMesh.py +73 -27
- ras_commander/HdfPipe.py +587 -78
- ras_commander/HdfPlan.py +5 -0
- ras_commander/HdfPump.py +25 -11
- ras_commander/HdfResultsMesh.py +135 -62
- ras_commander/HdfResultsXsec.py +126 -297
- ras_commander/HdfStruc.py +148 -50
- ras_commander/HdfUtils.py +51 -0
- ras_commander/HdfXsec.py +467 -136
- ras_commander/RasPlan.py +298 -45
- ras_commander/RasPrj.py +2 -7
- ras_commander/RasToGo.py +21 -0
- ras_commander/RasUnsteady.py +615 -14
- ras_commander/__init__.py +3 -1
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/METADATA +2 -2
- ras_commander-0.47.0.dist-info/RECORD +30 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/WHEEL +1 -1
- ras_commander-0.45.0.dist-info/RECORD +0 -28
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfXsec.py
CHANGED
@@ -13,12 +13,16 @@ import h5py
|
|
13
13
|
import numpy as np
|
14
14
|
import pandas as pd
|
15
15
|
from geopandas import GeoDataFrame
|
16
|
+
import geopandas as gpd
|
16
17
|
from shapely.geometry import LineString, MultiLineString
|
17
18
|
from typing import List # Import List to avoid NameError
|
18
19
|
from .Decorators import standardize_input, log_call
|
19
20
|
from .HdfBase import HdfBase
|
20
21
|
from .HdfUtils import HdfUtils
|
21
22
|
from .LoggingConfig import get_logger
|
23
|
+
import logging
|
24
|
+
|
25
|
+
|
22
26
|
|
23
27
|
logger = get_logger(__name__)
|
24
28
|
|
@@ -37,141 +41,384 @@ class HdfXsec:
|
|
37
41
|
Note:
|
38
42
|
This class is designed to work with HEC-RAS geometry HDF files and requires them to have
|
39
43
|
a specific structure and naming convention for the data groups and attributes.
|
40
|
-
|
44
|
+
"""
|
45
|
+
@staticmethod
|
46
|
+
@log_call
|
47
|
+
def cross_sections(hdf_path: str, datetime_to_str: bool = True, ras_object=None) -> gpd.GeoDataFrame:
|
48
|
+
"""
|
49
|
+
Extract cross sections from HDF geometry file and return as GeoDataFrame
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
hdf_path : str
|
54
|
+
Path to HDF file
|
55
|
+
datetime_to_str : bool, optional
|
56
|
+
Convert datetime objects to strings, by default True
|
57
|
+
ras_object : RasPrj, optional
|
58
|
+
RAS project object, by default None
|
59
|
+
|
60
|
+
Returns
|
61
|
+
-------
|
62
|
+
gpd.GeoDataFrame
|
63
|
+
GeoDataFrame containing cross section geometries and attributes including:
|
64
|
+
- River name
|
65
|
+
- Reach name
|
66
|
+
- River station
|
67
|
+
- Name
|
68
|
+
- Description
|
69
|
+
- Left/Channel/Right lengths
|
70
|
+
- Bank stations
|
71
|
+
- Friction mode
|
72
|
+
- Contraction/Expansion coefficients
|
73
|
+
- Levee information
|
74
|
+
- Hydraulic property parameters
|
75
|
+
- Block modes
|
76
|
+
- Last edited timestamp
|
77
|
+
- Manning's n values and stations
|
78
|
+
- Ineffective blocks data
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
with h5py.File(hdf_path, 'r') as hdf:
|
82
|
+
# Extract datasets
|
83
|
+
poly_info = hdf['/Geometry/Cross Sections/Polyline Info'][:]
|
84
|
+
poly_parts = hdf['/Geometry/Cross Sections/Polyline Parts'][:]
|
85
|
+
poly_points = hdf['/Geometry/Cross Sections/Polyline Points'][:]
|
86
|
+
|
87
|
+
station_info = hdf['/Geometry/Cross Sections/Station Elevation Info'][:]
|
88
|
+
station_values = hdf['/Geometry/Cross Sections/Station Elevation Values'][:]
|
89
|
+
|
90
|
+
# Get attributes for cross sections
|
91
|
+
xs_attrs = hdf['/Geometry/Cross Sections/Attributes'][:]
|
92
|
+
|
93
|
+
# Get Manning's n data
|
94
|
+
mann_info = hdf["/Geometry/Cross Sections/Manning's n Info"][:]
|
95
|
+
mann_values = hdf["/Geometry/Cross Sections/Manning's n Values"][:]
|
96
|
+
|
97
|
+
# Get ineffective blocks data
|
98
|
+
ineff_blocks = hdf['/Geometry/Cross Sections/Ineffective Blocks'][:]
|
99
|
+
ineff_info = hdf['/Geometry/Cross Sections/Ineffective Info'][:]
|
100
|
+
|
101
|
+
# Initialize lists to store data
|
102
|
+
geometries = []
|
103
|
+
station_elevations = []
|
104
|
+
mannings_n = []
|
105
|
+
ineffective_blocks = []
|
106
|
+
|
107
|
+
# Process each cross section
|
108
|
+
for i in range(len(poly_info)):
|
109
|
+
# Extract polyline info
|
110
|
+
point_start_idx = poly_info[i][0]
|
111
|
+
point_count = poly_info[i][1]
|
112
|
+
part_start_idx = poly_info[i][2]
|
113
|
+
part_count = poly_info[i][3]
|
114
|
+
|
115
|
+
# Extract parts for current polyline
|
116
|
+
parts = poly_parts[part_start_idx:part_start_idx + part_count]
|
117
|
+
|
118
|
+
# Collect all points for this cross section
|
119
|
+
xs_points = []
|
120
|
+
for part in parts:
|
121
|
+
part_point_start = point_start_idx + part[0]
|
122
|
+
part_point_count = part[1]
|
123
|
+
points = poly_points[part_point_start:part_point_start + part_point_count]
|
124
|
+
xs_points.extend(points)
|
125
|
+
|
126
|
+
# Create LineString geometry
|
127
|
+
if len(xs_points) >= 2:
|
128
|
+
geometry = LineString(xs_points)
|
129
|
+
geometries.append(geometry)
|
130
|
+
|
131
|
+
# Extract station-elevation data
|
132
|
+
start_idx = station_info[i][0]
|
133
|
+
count = station_info[i][1]
|
134
|
+
station_elev = station_values[start_idx:start_idx + count]
|
135
|
+
station_elevations.append(station_elev)
|
136
|
+
|
137
|
+
# Extract Manning's n data
|
138
|
+
mann_start_idx = mann_info[i][0]
|
139
|
+
mann_count = mann_info[i][1]
|
140
|
+
mann_n_section = mann_values[mann_start_idx:mann_start_idx + mann_count]
|
141
|
+
mann_n_dict = {
|
142
|
+
'Station': mann_n_section[:, 0].tolist(),
|
143
|
+
'Mann n': mann_n_section[:, 1].tolist()
|
144
|
+
}
|
145
|
+
mannings_n.append(mann_n_dict)
|
146
|
+
|
147
|
+
# Extract ineffective blocks data
|
148
|
+
ineff_start_idx = ineff_info[i][0]
|
149
|
+
ineff_count = ineff_info[i][1]
|
150
|
+
if ineff_count > 0:
|
151
|
+
blocks = ineff_blocks[ineff_start_idx:ineff_start_idx + ineff_count]
|
152
|
+
blocks_list = []
|
153
|
+
for block in blocks:
|
154
|
+
block_dict = {
|
155
|
+
'Left Sta': float(block['Left Sta']),
|
156
|
+
'Right Sta': float(block['Right Sta']),
|
157
|
+
'Elevation': float(block['Elevation']),
|
158
|
+
'Permanent': bool(block['Permanent'])
|
159
|
+
}
|
160
|
+
blocks_list.append(block_dict)
|
161
|
+
ineffective_blocks.append(blocks_list)
|
162
|
+
else:
|
163
|
+
ineffective_blocks.append([])
|
164
|
+
|
165
|
+
# Create GeoDataFrame
|
166
|
+
if geometries:
|
167
|
+
# Create DataFrame from attributes
|
168
|
+
data = {
|
169
|
+
'geometry': geometries,
|
170
|
+
'station_elevation': station_elevations,
|
171
|
+
'mannings_n': mannings_n,
|
172
|
+
'ineffective_blocks': ineffective_blocks,
|
173
|
+
'River': [x['River'].decode('utf-8').strip() for x in xs_attrs],
|
174
|
+
'Reach': [x['Reach'].decode('utf-8').strip() for x in xs_attrs],
|
175
|
+
'RS': [x['RS'].decode('utf-8').strip() for x in xs_attrs],
|
176
|
+
'Name': [x['Name'].decode('utf-8').strip() for x in xs_attrs],
|
177
|
+
'Description': [x['Description'].decode('utf-8').strip() for x in xs_attrs],
|
178
|
+
'Len Left': xs_attrs['Len Left'],
|
179
|
+
'Len Channel': xs_attrs['Len Channel'],
|
180
|
+
'Len Right': xs_attrs['Len Right'],
|
181
|
+
'Left Bank': xs_attrs['Left Bank'],
|
182
|
+
'Right Bank': xs_attrs['Right Bank'],
|
183
|
+
'Friction Mode': [x['Friction Mode'].decode('utf-8').strip() for x in xs_attrs],
|
184
|
+
'Contr': xs_attrs['Contr'],
|
185
|
+
'Expan': xs_attrs['Expan'],
|
186
|
+
'Left Levee Sta': xs_attrs['Left Levee Sta'],
|
187
|
+
'Left Levee Elev': xs_attrs['Left Levee Elev'],
|
188
|
+
'Right Levee Sta': xs_attrs['Right Levee Sta'],
|
189
|
+
'Right Levee Elev': xs_attrs['Right Levee Elev'],
|
190
|
+
'HP Count': xs_attrs['HP Count'],
|
191
|
+
'HP Start Elev': xs_attrs['HP Start Elev'],
|
192
|
+
'HP Vert Incr': xs_attrs['HP Vert Incr'],
|
193
|
+
'HP LOB Slices': xs_attrs['HP LOB Slices'],
|
194
|
+
'HP Chan Slices': xs_attrs['HP Chan Slices'],
|
195
|
+
'HP ROB Slices': xs_attrs['HP ROB Slices'],
|
196
|
+
'Ineff Block Mode': xs_attrs['Ineff Block Mode'],
|
197
|
+
'Obstr Block Mode': xs_attrs['Obstr Block Mode'],
|
198
|
+
'Default Centerline': xs_attrs['Default Centerline'],
|
199
|
+
'Last Edited': [x['Last Edited'].decode('utf-8').strip() for x in xs_attrs]
|
200
|
+
}
|
201
|
+
|
202
|
+
gdf = gpd.GeoDataFrame(data)
|
203
|
+
|
204
|
+
# Set CRS if available
|
205
|
+
if 'Projection' in hdf['/Geometry'].attrs:
|
206
|
+
proj = hdf['/Geometry'].attrs['Projection']
|
207
|
+
if isinstance(proj, bytes):
|
208
|
+
proj = proj.decode('utf-8')
|
209
|
+
gdf.set_crs(proj, allow_override=True)
|
210
|
+
|
211
|
+
return gdf
|
212
|
+
|
213
|
+
return gpd.GeoDataFrame()
|
214
|
+
|
215
|
+
except Exception as e:
|
216
|
+
logging.error(f"Error processing cross-section data: {str(e)}")
|
217
|
+
return gpd.GeoDataFrame()
|
218
|
+
|
41
219
|
|
42
220
|
@staticmethod
|
43
221
|
@log_call
|
44
|
-
|
45
|
-
def cross_sections(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
222
|
+
def _get_polylines(hdf_path: Path, path: str, info_name: str = "Polyline Info", parts_name: str = "Polyline Parts", points_name: str = "Polyline Points") -> List[LineString]:
|
46
223
|
"""
|
47
|
-
|
224
|
+
Helper method to extract polylines from HDF file.
|
225
|
+
|
226
|
+
[rest of docstring remains the same]
|
227
|
+
"""
|
228
|
+
try:
|
229
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
230
|
+
polyline_info_path = f"{path}/{info_name}"
|
231
|
+
polyline_parts_path = f"{path}/{parts_name}"
|
232
|
+
polyline_points_path = f"{path}/{points_name}"
|
233
|
+
|
234
|
+
polyline_info = hdf_file[polyline_info_path][()]
|
235
|
+
polyline_parts = hdf_file[polyline_parts_path][()]
|
236
|
+
polyline_points = hdf_file[polyline_points_path][()]
|
237
|
+
|
238
|
+
geoms = []
|
239
|
+
for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
|
240
|
+
points = polyline_points[pnt_start : pnt_start + pnt_cnt]
|
241
|
+
if part_cnt == 1:
|
242
|
+
geoms.append(LineString(points))
|
243
|
+
else:
|
244
|
+
parts = polyline_parts[part_start : part_start + part_cnt]
|
245
|
+
geoms.append(
|
246
|
+
MultiLineString(
|
247
|
+
list(
|
248
|
+
points[part_pnt_start : part_pnt_start + part_pnt_cnt]
|
249
|
+
for part_pnt_start, part_pnt_cnt in parts
|
250
|
+
)
|
251
|
+
)
|
252
|
+
)
|
253
|
+
return geoms
|
254
|
+
except Exception as e:
|
255
|
+
logger.error(f"Error getting polylines: {str(e)}")
|
256
|
+
return []
|
48
257
|
|
49
|
-
|
50
|
-
|
258
|
+
|
259
|
+
@staticmethod
|
260
|
+
@log_call
|
261
|
+
@standardize_input(file_type='geom_hdf')
|
262
|
+
def river_centerlines(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
263
|
+
"""
|
264
|
+
Extract river centerlines from HDF geometry file.
|
51
265
|
|
52
266
|
Parameters
|
53
267
|
----------
|
54
268
|
hdf_path : Path
|
55
|
-
Path to the HEC-RAS geometry HDF file
|
269
|
+
Path to the HEC-RAS geometry HDF file
|
56
270
|
datetime_to_str : bool, optional
|
57
|
-
|
271
|
+
Convert datetime objects to strings, by default False
|
58
272
|
|
59
273
|
Returns
|
60
274
|
-------
|
61
275
|
GeoDataFrame
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
276
|
+
GeoDataFrame containing river centerline geometries and attributes including:
|
277
|
+
- River Name
|
278
|
+
- Reach Name
|
279
|
+
- Upstream Type and Name
|
280
|
+
- Downstream Type and Name
|
281
|
+
- Junction distances
|
282
|
+
- Geometry
|
68
283
|
"""
|
69
284
|
try:
|
70
285
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
71
|
-
|
72
|
-
|
73
|
-
if "Attributes" not in xs_data:
|
74
|
-
logger.warning(f"No 'Attributes' dataset group in {hdf_path}")
|
286
|
+
if "Geometry/River Centerlines" not in hdf_file:
|
287
|
+
logger.warning("No river centerlines found in geometry file")
|
75
288
|
return GeoDataFrame()
|
76
289
|
|
77
|
-
|
290
|
+
centerline_data = hdf_file["Geometry/River Centerlines"]
|
291
|
+
|
292
|
+
# Get attributes
|
293
|
+
attrs = centerline_data["Attributes"][()]
|
78
294
|
v_conv_val = np.vectorize(HdfUtils._convert_ras_hdf_value)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
295
|
+
|
296
|
+
# Create dictionary of attributes
|
297
|
+
centerline_dict = {"centerline_id": range(attrs.shape[0])}
|
298
|
+
centerline_dict.update(
|
299
|
+
{name: v_conv_val(attrs[name]) for name in attrs.dtype.names}
|
83
300
|
)
|
84
301
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
302
|
+
# Get polyline geometries
|
303
|
+
geoms = HdfXsec._get_polylines(
|
304
|
+
hdf_path,
|
305
|
+
"Geometry/River Centerlines",
|
306
|
+
info_name="Polyline Info",
|
307
|
+
parts_name="Polyline Parts",
|
308
|
+
points_name="Polyline Points"
|
309
|
+
)
|
310
|
+
|
311
|
+
# Create GeoDataFrame
|
312
|
+
centerline_gdf = GeoDataFrame(
|
313
|
+
centerline_dict,
|
314
|
+
geometry=geoms,
|
315
|
+
crs=HdfUtils.projection(hdf_path)
|
316
|
+
)
|
317
|
+
|
318
|
+
# Decode string columns after creation
|
319
|
+
str_columns = ['River Name', 'Reach Name', 'US Type',
|
320
|
+
'US Name', 'DS Type', 'DS Name']
|
321
|
+
for col in str_columns:
|
322
|
+
if col in centerline_gdf.columns:
|
323
|
+
centerline_gdf[col] = centerline_gdf[col].apply(
|
324
|
+
lambda x: x.decode('utf-8').strip() if isinstance(x, bytes) else x
|
325
|
+
)
|
326
|
+
|
327
|
+
# Clean up column names and add length calculation
|
328
|
+
if not centerline_gdf.empty:
|
329
|
+
# Calculate length in project units
|
330
|
+
centerline_gdf['length'] = centerline_gdf.geometry.length
|
331
|
+
|
332
|
+
# Clean string columns
|
333
|
+
str_columns = ['River Name', 'Reach Name', 'US Type',
|
334
|
+
'US Name', 'DS Type', 'DS Name']
|
335
|
+
for col in str_columns:
|
336
|
+
if col in centerline_gdf.columns:
|
337
|
+
centerline_gdf[col] = centerline_gdf[col].str.strip()
|
338
|
+
|
339
|
+
return centerline_gdf
|
101
340
|
|
102
|
-
except
|
103
|
-
logger.error(f"Error
|
341
|
+
except Exception as e:
|
342
|
+
logger.error(f"Error reading river centerlines: {str(e)}")
|
104
343
|
return GeoDataFrame()
|
105
344
|
|
345
|
+
|
346
|
+
|
106
347
|
@staticmethod
|
107
348
|
@log_call
|
108
|
-
|
109
|
-
def cross_sections_elevations(hdf_path: Path, round_to: int = 2) -> pd.DataFrame:
|
349
|
+
def get_river_stationing(centerlines_gdf: GeoDataFrame) -> GeoDataFrame:
|
110
350
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
This method extracts cross-section elevation data from the HEC-RAS geometry HDF file,
|
114
|
-
including station-elevation pairs for each cross-section.
|
351
|
+
Calculate river stationing along centerlines.
|
115
352
|
|
116
353
|
Parameters
|
117
354
|
----------
|
118
|
-
|
119
|
-
|
120
|
-
round_to : int, optional
|
121
|
-
Number of decimal places to round to. Default is 2.
|
355
|
+
centerlines_gdf : GeoDataFrame
|
356
|
+
GeoDataFrame containing river centerline geometries from river_centerlines()
|
122
357
|
|
123
358
|
Returns
|
124
359
|
-------
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
If the required datasets are not found in the HDF file.
|
360
|
+
GeoDataFrame
|
361
|
+
Original GeoDataFrame with additional columns:
|
362
|
+
- station_start: Starting station for each reach
|
363
|
+
- station_end: Ending station for each reach
|
364
|
+
- stations: Array of stations along the centerline
|
365
|
+
- points: Array of point geometries along the centerline
|
132
366
|
"""
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
if path not in hdf_file:
|
137
|
-
logger.warning(f"No 'Cross Sections' group found in {hdf_path}")
|
138
|
-
return pd.DataFrame()
|
367
|
+
if centerlines_gdf.empty:
|
368
|
+
logger.warning("Empty centerlines GeoDataFrame provided")
|
369
|
+
return centerlines_gdf
|
139
370
|
|
140
|
-
|
371
|
+
try:
|
372
|
+
# Create copy to avoid modifying original
|
373
|
+
result_gdf = centerlines_gdf.copy()
|
374
|
+
|
375
|
+
# Initialize new columns
|
376
|
+
result_gdf['station_start'] = 0.0
|
377
|
+
result_gdf['station_end'] = 0.0
|
378
|
+
result_gdf['stations'] = None
|
379
|
+
result_gdf['points'] = None
|
380
|
+
|
381
|
+
# Process each centerline
|
382
|
+
for idx, row in result_gdf.iterrows():
|
383
|
+
# Get line geometry
|
384
|
+
line = row.geometry
|
385
|
+
|
386
|
+
# Calculate length
|
387
|
+
total_length = line.length
|
388
|
+
|
389
|
+
# Generate points along the line
|
390
|
+
distances = np.linspace(0, total_length, num=100) # Adjust num for desired density
|
391
|
+
points = [line.interpolate(distance) for distance in distances]
|
141
392
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
elevations.append(xzdata)
|
158
|
-
|
159
|
-
# Create DataFrame with elevation info
|
160
|
-
xs_elev_df = xs_df[
|
161
|
-
["xs_id", "River", "Reach", "RS", "Left Bank", "Right Bank"]
|
162
|
-
].copy()
|
163
|
-
xs_elev_df["Left Bank"] = xs_elev_df["Left Bank"].round(round_to).astype(str)
|
164
|
-
xs_elev_df["Right Bank"] = xs_elev_df["Right Bank"].round(round_to).astype(str)
|
165
|
-
xs_elev_df["elevation info"] = elevations
|
166
|
-
|
167
|
-
return xs_elev_df
|
168
|
-
|
169
|
-
except KeyError as e:
|
170
|
-
logger.error(f"Error accessing cross-section elevation data in {hdf_path}: {str(e)}")
|
171
|
-
return pd.DataFrame()
|
393
|
+
# Store results
|
394
|
+
result_gdf.at[idx, 'station_start'] = 0.0
|
395
|
+
result_gdf.at[idx, 'station_end'] = total_length
|
396
|
+
result_gdf.at[idx, 'stations'] = distances
|
397
|
+
result_gdf.at[idx, 'points'] = points
|
398
|
+
|
399
|
+
# Add stationing direction based on upstream/downstream info
|
400
|
+
if row['upstream_type'] == 'Junction' and row['downstream_type'] != 'Junction':
|
401
|
+
# Reverse stationing if upstream is junction
|
402
|
+
result_gdf.at[idx, 'station_start'] = total_length
|
403
|
+
result_gdf.at[idx, 'station_end'] = 0.0
|
404
|
+
result_gdf.at[idx, 'stations'] = total_length - distances
|
405
|
+
|
406
|
+
return result_gdf
|
407
|
+
|
172
408
|
except Exception as e:
|
173
|
-
logger.error(f"
|
174
|
-
return
|
409
|
+
logger.error(f"Error calculating river stationing: {str(e)}")
|
410
|
+
return centerlines_gdf
|
411
|
+
|
412
|
+
@staticmethod
|
413
|
+
def _interpolate_station(line, distance):
|
414
|
+
"""Helper method to interpolate station along a line"""
|
415
|
+
if distance <= 0:
|
416
|
+
return line.coords[0]
|
417
|
+
elif distance >= line.length:
|
418
|
+
return line.coords[-1]
|
419
|
+
return line.interpolate(distance).coords[0]
|
420
|
+
|
421
|
+
|
175
422
|
|
176
423
|
@staticmethod
|
177
424
|
@log_call
|
@@ -225,58 +472,142 @@ class HdfXsec:
|
|
225
472
|
logger.error(f"Error reading river reaches: {str(e)}")
|
226
473
|
return GeoDataFrame()
|
227
474
|
|
475
|
+
|
228
476
|
@staticmethod
|
229
|
-
|
477
|
+
@log_call
|
478
|
+
@standardize_input(file_type='geom_hdf')
|
479
|
+
def river_edge_lines(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
230
480
|
"""
|
231
|
-
|
232
|
-
|
233
|
-
This method is used internally to extract polyline geometries for various features
|
234
|
-
such as river reaches.
|
481
|
+
Return the model river edge lines.
|
235
482
|
|
236
483
|
Parameters
|
237
484
|
----------
|
238
485
|
hdf_path : Path
|
239
486
|
Path to the HEC-RAS geometry HDF file.
|
240
|
-
|
241
|
-
|
242
|
-
info_name : str, optional
|
243
|
-
Name of the dataset containing polyline info. Default is "Polyline Info".
|
244
|
-
parts_name : str, optional
|
245
|
-
Name of the dataset containing polyline parts. Default is "Polyline Parts".
|
246
|
-
points_name : str, optional
|
247
|
-
Name of the dataset containing polyline points. Default is "Polyline Points".
|
487
|
+
datetime_to_str : bool, optional
|
488
|
+
If True, convert datetime objects to strings. Default is False.
|
248
489
|
|
249
490
|
Returns
|
250
491
|
-------
|
251
|
-
|
252
|
-
A
|
492
|
+
GeoDataFrame
|
493
|
+
A GeoDataFrame containing river edge lines with their attributes and geometries.
|
494
|
+
Each row represents a river bank (left or right) with associated attributes.
|
253
495
|
"""
|
254
496
|
try:
|
255
497
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
256
|
-
|
257
|
-
|
258
|
-
|
498
|
+
if "Geometry/River Edge Lines" not in hdf_file:
|
499
|
+
logger.warning("No river edge lines found in geometry file")
|
500
|
+
return GeoDataFrame()
|
259
501
|
|
260
|
-
|
261
|
-
|
262
|
-
|
502
|
+
edge_data = hdf_file["Geometry/River Edge Lines"]
|
503
|
+
|
504
|
+
# Get attributes if they exist
|
505
|
+
if "Attributes" in edge_data:
|
506
|
+
attrs = edge_data["Attributes"][()]
|
507
|
+
v_conv_val = np.vectorize(HdfUtils._convert_ras_hdf_value)
|
508
|
+
|
509
|
+
# Create dictionary of attributes
|
510
|
+
edge_dict = {"edge_id": range(attrs.shape[0])}
|
511
|
+
edge_dict.update(
|
512
|
+
{name: v_conv_val(attrs[name]) for name in attrs.dtype.names}
|
513
|
+
)
|
514
|
+
|
515
|
+
# Add bank side indicator
|
516
|
+
if edge_dict["edge_id"].size % 2 == 0: # Ensure even number of edges
|
517
|
+
edge_dict["bank_side"] = ["Left", "Right"] * (edge_dict["edge_id"].size // 2)
|
518
|
+
else:
|
519
|
+
edge_dict = {"edge_id": [], "bank_side": []}
|
520
|
+
|
521
|
+
# Get polyline geometries
|
522
|
+
geoms = HdfXsec._get_polylines(
|
523
|
+
hdf_path,
|
524
|
+
"Geometry/River Edge Lines",
|
525
|
+
info_name="Polyline Info",
|
526
|
+
parts_name="Polyline Parts",
|
527
|
+
points_name="Polyline Points"
|
528
|
+
)
|
529
|
+
|
530
|
+
# Create GeoDataFrame
|
531
|
+
edge_gdf = GeoDataFrame(
|
532
|
+
edge_dict,
|
533
|
+
geometry=geoms,
|
534
|
+
crs=HdfUtils.projection(hdf_path)
|
535
|
+
)
|
536
|
+
|
537
|
+
# Convert datetime objects to strings if requested
|
538
|
+
if datetime_to_str and 'Last Edited' in edge_gdf.columns:
|
539
|
+
edge_gdf["Last Edited"] = edge_gdf["Last Edited"].apply(
|
540
|
+
lambda x: pd.Timestamp.isoformat(x) if pd.notnull(x) else None
|
541
|
+
)
|
542
|
+
|
543
|
+
# Add length calculation in project units
|
544
|
+
if not edge_gdf.empty:
|
545
|
+
edge_gdf['length'] = edge_gdf.geometry.length
|
546
|
+
|
547
|
+
return edge_gdf
|
263
548
|
|
264
|
-
geoms = []
|
265
|
-
for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
|
266
|
-
points = polyline_points[pnt_start : pnt_start + pnt_cnt]
|
267
|
-
if part_cnt == 1:
|
268
|
-
geoms.append(LineString(points))
|
269
|
-
else:
|
270
|
-
parts = polyline_parts[part_start : part_start + part_cnt]
|
271
|
-
geoms.append(
|
272
|
-
MultiLineString(
|
273
|
-
list(
|
274
|
-
points[part_pnt_start : part_pnt_start + part_pnt_cnt]
|
275
|
-
for part_pnt_start, part_pnt_cnt in parts
|
276
|
-
)
|
277
|
-
)
|
278
|
-
)
|
279
|
-
return geoms
|
280
549
|
except Exception as e:
|
281
|
-
logger.error(f"Error
|
282
|
-
return
|
550
|
+
logger.error(f"Error reading river edge lines: {str(e)}")
|
551
|
+
return GeoDataFrame()
|
552
|
+
|
553
|
+
@staticmethod
|
554
|
+
@log_call
|
555
|
+
@standardize_input(file_type='geom_hdf')
|
556
|
+
def river_bank_lines(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
557
|
+
"""
|
558
|
+
Extract river bank lines from HDF geometry file.
|
559
|
+
|
560
|
+
Parameters
|
561
|
+
----------
|
562
|
+
hdf_path : Path
|
563
|
+
Path to the HEC-RAS geometry HDF file
|
564
|
+
datetime_to_str : bool, optional
|
565
|
+
Convert datetime objects to strings, by default False
|
566
|
+
|
567
|
+
Returns
|
568
|
+
-------
|
569
|
+
GeoDataFrame
|
570
|
+
GeoDataFrame containing river bank line geometries with attributes:
|
571
|
+
- bank_id: Unique identifier for each bank line
|
572
|
+
- bank_side: Left or Right bank indicator
|
573
|
+
- geometry: LineString geometry of the bank
|
574
|
+
- length: Length of the bank line in project units
|
575
|
+
"""
|
576
|
+
try:
|
577
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
578
|
+
if "Geometry/River Bank Lines" not in hdf_file:
|
579
|
+
logger.warning("No river bank lines found in geometry file")
|
580
|
+
return GeoDataFrame()
|
581
|
+
|
582
|
+
# Get polyline geometries using existing helper method
|
583
|
+
geoms = HdfXsec._get_polylines(
|
584
|
+
hdf_path,
|
585
|
+
"Geometry/River Bank Lines",
|
586
|
+
info_name="Polyline Info",
|
587
|
+
parts_name="Polyline Parts",
|
588
|
+
points_name="Polyline Points"
|
589
|
+
)
|
590
|
+
|
591
|
+
# Create basic attributes
|
592
|
+
bank_dict = {
|
593
|
+
"bank_id": range(len(geoms)),
|
594
|
+
"bank_side": ["Left", "Right"] * (len(geoms) // 2) # Assuming pairs of left/right banks
|
595
|
+
}
|
596
|
+
|
597
|
+
# Create GeoDataFrame
|
598
|
+
bank_gdf = GeoDataFrame(
|
599
|
+
bank_dict,
|
600
|
+
geometry=geoms,
|
601
|
+
crs=HdfUtils.projection(hdf_path)
|
602
|
+
)
|
603
|
+
|
604
|
+
# Add length calculation in project units
|
605
|
+
if not bank_gdf.empty:
|
606
|
+
bank_gdf['length'] = bank_gdf.geometry.length
|
607
|
+
|
608
|
+
return bank_gdf
|
609
|
+
|
610
|
+
except Exception as e:
|
611
|
+
logger.error(f"Error reading river bank lines: {str(e)}")
|
612
|
+
return GeoDataFrame()
|
613
|
+
|