rashdf 0.8.4__py3-none-any.whl → 0.9.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.
- cli.py +1 -0
- rashdf/geom.py +63 -0
- rashdf/plan.py +52 -1
- rashdf/utils.py +160 -0
- {rashdf-0.8.4.dist-info → rashdf-0.9.0.dist-info}/METADATA +2 -2
- rashdf-0.9.0.dist-info/RECORD +12 -0
- rashdf-0.8.4.dist-info/RECORD +0 -12
- {rashdf-0.8.4.dist-info → rashdf-0.9.0.dist-info}/WHEEL +0 -0
- {rashdf-0.8.4.dist-info → rashdf-0.9.0.dist-info}/entry_points.txt +0 -0
- {rashdf-0.8.4.dist-info → rashdf-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {rashdf-0.8.4.dist-info → rashdf-0.9.0.dist-info}/top_level.txt +0 -0
cli.py
CHANGED
rashdf/geom.py
CHANGED
|
@@ -27,6 +27,8 @@ from .utils import (
|
|
|
27
27
|
convert_ras_hdf_value,
|
|
28
28
|
get_first_hdf_group,
|
|
29
29
|
hdf5_attrs_to_dict,
|
|
30
|
+
copy_lines_parallel,
|
|
31
|
+
experimental,
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
|
|
@@ -474,6 +476,67 @@ class RasGeomHdf(RasHdf):
|
|
|
474
476
|
def connections(self) -> GeoDataFrame: # noqa D102
|
|
475
477
|
raise NotImplementedError
|
|
476
478
|
|
|
479
|
+
@experimental
|
|
480
|
+
def generate_bridge_xs_lines(self, datetime_to_str: bool = False) -> GeoDataFrame:
|
|
481
|
+
"""[EXPERIMENTAL] Attempt to return the 2D bridge cross-section lines.
|
|
482
|
+
|
|
483
|
+
This method attempts to generate the cross-section lines for bridges modeled
|
|
484
|
+
within 2D mesh areas. It should be noted that these lines are not explicitly
|
|
485
|
+
stored within the HEC-RAS Geometry HDF file, and are instead generated based
|
|
486
|
+
on the bridge attributes and centerline geometry. As such, the accuracy of
|
|
487
|
+
these lines may vary depending on the complexity of the bridge geometry and
|
|
488
|
+
output from this method should be reviewed for accuracy.
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
datetime_to_str : bool, optional
|
|
493
|
+
If True, convert datetime values to string format (default: False).
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
GeoDataFrame
|
|
498
|
+
A GeoDataFrame containing the 2D bridge cross-section lines if they exist.
|
|
499
|
+
"""
|
|
500
|
+
profile_info = self.get(self.GEOM_STRUCTURES_PATH + "/Table Info")
|
|
501
|
+
structs = self.structures().merge(
|
|
502
|
+
pd.DataFrame(profile_info[()] if profile_info is not None else None),
|
|
503
|
+
left_index=True,
|
|
504
|
+
right_index=True,
|
|
505
|
+
)
|
|
506
|
+
if structs.empty:
|
|
507
|
+
return GeoDataFrame()
|
|
508
|
+
|
|
509
|
+
bridges = structs[structs["Mode"] == "Bridge Opening"].copy()
|
|
510
|
+
if bridges.empty:
|
|
511
|
+
return GeoDataFrame()
|
|
512
|
+
|
|
513
|
+
inside_buffer_widths = bridges["Weir Width"] / 2
|
|
514
|
+
inside_bridge_xs = copy_lines_parallel(
|
|
515
|
+
bridges, inside_buffer_widths.values, "struct_id"
|
|
516
|
+
)
|
|
517
|
+
inside_bridge_xs["level"] = "inside"
|
|
518
|
+
|
|
519
|
+
outside_buffer_widths = inside_buffer_widths + bridges["Upstream Distance"]
|
|
520
|
+
outside_bridge_xs = copy_lines_parallel(
|
|
521
|
+
bridges, outside_buffer_widths.values, "struct_id"
|
|
522
|
+
)
|
|
523
|
+
outside_bridge_xs["level"] = "outside"
|
|
524
|
+
|
|
525
|
+
br_xs = GeoDataFrame(
|
|
526
|
+
pd.concat([inside_bridge_xs, outside_bridge_xs], ignore_index=True),
|
|
527
|
+
geometry="geometry",
|
|
528
|
+
)
|
|
529
|
+
br_xs.side = br_xs.side.apply(
|
|
530
|
+
lambda x: "upstream" if x == "right" else "downstream"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if datetime_to_str:
|
|
534
|
+
br_xs[self.LAST_EDITED_COLUMN] = br_xs[self.LAST_EDITED_COLUMN].apply(
|
|
535
|
+
lambda x: pd.Timestamp.isoformat(x)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return br_xs
|
|
539
|
+
|
|
477
540
|
def ic_points(self) -> GeoDataFrame: # noqa D102
|
|
478
541
|
"""Return initial conditions points.
|
|
479
542
|
|
rashdf/plan.py
CHANGED
|
@@ -1178,7 +1178,7 @@ class RasPlanHdf(RasGeomHdf):
|
|
|
1178
1178
|
xr.Dataset
|
|
1179
1179
|
An xarray Dataset with timeseries output data for boundary conditions lines.
|
|
1180
1180
|
"""
|
|
1181
|
-
df_bc_lines =
|
|
1181
|
+
df_bc_lines = super().bc_lines()
|
|
1182
1182
|
bc_lines_names = df_bc_lines["name"]
|
|
1183
1183
|
datasets = []
|
|
1184
1184
|
for bc_line_name in bc_lines_names:
|
|
@@ -1197,6 +1197,57 @@ class RasPlanHdf(RasGeomHdf):
|
|
|
1197
1197
|
)
|
|
1198
1198
|
return ds
|
|
1199
1199
|
|
|
1200
|
+
def bc_lines(
|
|
1201
|
+
self, include_output: bool = True, datetime_to_str: bool = False
|
|
1202
|
+
) -> GeoDataFrame:
|
|
1203
|
+
"""Return the boundary condition lines from a HEC-RAS HDF plan file.
|
|
1204
|
+
|
|
1205
|
+
Optionally include summary output data for each boundary condition line.
|
|
1206
|
+
|
|
1207
|
+
Parameters
|
|
1208
|
+
----------
|
|
1209
|
+
include_output : bool, optional
|
|
1210
|
+
If True, include summary output data in the GeoDataFrame. (default: True)
|
|
1211
|
+
datetime_to_str : bool, optional
|
|
1212
|
+
If True, convert datetime columns to strings. (default: False)
|
|
1213
|
+
|
|
1214
|
+
Returns
|
|
1215
|
+
-------
|
|
1216
|
+
GeoDataFrame
|
|
1217
|
+
A GeoDataFrame with boundary condition line geometry and summary output data.
|
|
1218
|
+
"""
|
|
1219
|
+
gdf = super().bc_lines()
|
|
1220
|
+
if include_output is False:
|
|
1221
|
+
return gdf
|
|
1222
|
+
|
|
1223
|
+
ds = self.bc_lines_timeseries_output()
|
|
1224
|
+
summary = {
|
|
1225
|
+
"bc_line_id": ds.coords["bc_line_id"].values,
|
|
1226
|
+
"name": ds.coords["bc_line_name"].values,
|
|
1227
|
+
"mesh_name": ds.coords["mesh_name"].values,
|
|
1228
|
+
"type": ds.coords["bc_line_type"].values,
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
for var in ds.data_vars:
|
|
1232
|
+
abbrev = "q" if var.lower() == "flow" else "ws"
|
|
1233
|
+
summary[f"max_{abbrev}"] = ds[var].max(dim="time").values
|
|
1234
|
+
summary[f"max_{abbrev}_time"] = (
|
|
1235
|
+
ds[var].time[ds[var].argmax(dim="time")].values
|
|
1236
|
+
)
|
|
1237
|
+
summary[f"min_{abbrev}"] = ds[var].min(dim="time").values
|
|
1238
|
+
summary[f"min_{abbrev}_time"] = (
|
|
1239
|
+
ds[var].time[ds[var].argmin(dim="time")].values
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
gdf_with_output = gdf.merge(
|
|
1243
|
+
pd.DataFrame(summary),
|
|
1244
|
+
on=["bc_line_id", "name", "mesh_name", "type"],
|
|
1245
|
+
how="left",
|
|
1246
|
+
)
|
|
1247
|
+
return (
|
|
1248
|
+
df_datetimes_to_str(gdf_with_output) if datetime_to_str else gdf_with_output
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1200
1251
|
def observed_timeseries_input(self, vartype: str = "Flow") -> xr.DataArray:
|
|
1201
1252
|
"""Return observed timeseries input data for reference lines and points from a HEC-RAS HDF plan file.
|
|
1202
1253
|
|
rashdf/utils.py
CHANGED
|
@@ -8,6 +8,38 @@ from datetime import datetime, timedelta
|
|
|
8
8
|
import re
|
|
9
9
|
from typing import Any, Callable, List, Tuple, Union, Optional
|
|
10
10
|
import warnings
|
|
11
|
+
from shapely import LineString, MultiLineString
|
|
12
|
+
import geopandas as gpd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def experimental(func) -> Callable:
|
|
16
|
+
"""
|
|
17
|
+
Declare a function to be experimental.
|
|
18
|
+
|
|
19
|
+
This is a decorator which can be used to mark functions as experimental.
|
|
20
|
+
It will result in a warning being emitted when the function is used.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
func: The function to be declared experimental.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
The decorated function.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def new_func(*args, **kwargs):
|
|
32
|
+
warnings.warn(
|
|
33
|
+
f"{func.__name__} is experimental and could change in the future. Please review output carefully.",
|
|
34
|
+
category=UserWarning,
|
|
35
|
+
stacklevel=2,
|
|
36
|
+
)
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
new_func.__name__ = func.__name__
|
|
40
|
+
new_func.__doc__ = func.__doc__
|
|
41
|
+
new_func.__dict__.update(func.__dict__)
|
|
42
|
+
return new_func
|
|
11
43
|
|
|
12
44
|
|
|
13
45
|
def deprecated(func) -> Callable:
|
|
@@ -354,3 +386,131 @@ def ras_timesteps_to_datetimes(
|
|
|
354
386
|
start_time + pd.Timedelta(timestep, unit=time_unit).round(round_to)
|
|
355
387
|
for timestep in timesteps.astype(np.float64)
|
|
356
388
|
]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def remove_line_ends(
|
|
392
|
+
geom: Union[LineString, MultiLineString],
|
|
393
|
+
) -> Union[LineString, MultiLineString]:
|
|
394
|
+
"""
|
|
395
|
+
Remove endpoints from a LineString or each LineString in a MultiLineString if longer than 3 points.
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
geom : LineString or MultiLineString
|
|
400
|
+
The geometry to trim.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
LineString or MultiLineString
|
|
405
|
+
The trimmed geometry, or original if not enough points to trim.
|
|
406
|
+
"""
|
|
407
|
+
if isinstance(geom, LineString):
|
|
408
|
+
coords = list(geom.coords)
|
|
409
|
+
if len(coords) > 3:
|
|
410
|
+
return LineString(coords[1:-1])
|
|
411
|
+
return geom
|
|
412
|
+
elif isinstance(geom, MultiLineString):
|
|
413
|
+
trimmed = []
|
|
414
|
+
for line in geom.geoms:
|
|
415
|
+
coords = list(line.coords)
|
|
416
|
+
if len(coords) > 3:
|
|
417
|
+
trimmed.append(LineString(coords[1:-1]))
|
|
418
|
+
else:
|
|
419
|
+
trimmed.append(line)
|
|
420
|
+
return MultiLineString(trimmed)
|
|
421
|
+
return geom
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def reverse_line(
|
|
425
|
+
line: Union[LineString, MultiLineString],
|
|
426
|
+
) -> Union[LineString, MultiLineString]:
|
|
427
|
+
"""
|
|
428
|
+
Reverse the order of coordinates in a LineString or each LineString in a MultiLineString.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
line : LineString or MultiLineString
|
|
433
|
+
The geometry to reverse.
|
|
434
|
+
|
|
435
|
+
Returns
|
|
436
|
+
-------
|
|
437
|
+
LineString or MultiLineString
|
|
438
|
+
The reversed geometry.
|
|
439
|
+
"""
|
|
440
|
+
return (
|
|
441
|
+
MultiLineString([LineString(list(line.coords)[::-1]) for line in line.geoms])
|
|
442
|
+
if isinstance(line, MultiLineString)
|
|
443
|
+
else LineString(list(line.coords)[::-1])
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def copy_lines_parallel(
|
|
448
|
+
lines: gpd.GeoDataFrame,
|
|
449
|
+
offset_ft: Union[np.ndarray, float],
|
|
450
|
+
id_col: str = "id",
|
|
451
|
+
) -> gpd.GeoDataFrame:
|
|
452
|
+
"""
|
|
453
|
+
Create parallel copies of line geometries offset to the left and right, then trim and erase overlaps.
|
|
454
|
+
|
|
455
|
+
Parameters
|
|
456
|
+
----------
|
|
457
|
+
lines : gpd.GeoDataFrame
|
|
458
|
+
GeoDataFrame containing line geometries.
|
|
459
|
+
offset_ft : float or np.ndarray
|
|
460
|
+
Offset distance (in feet) for parallel lines.
|
|
461
|
+
id_col : str
|
|
462
|
+
Name of the column containing unique structure IDs. Default is "id".
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
gpd.GeoDataFrame
|
|
467
|
+
GeoDataFrame with trimmed, parallel left and right offset lines.
|
|
468
|
+
"""
|
|
469
|
+
# Offset lines to the left
|
|
470
|
+
left = lines.copy()
|
|
471
|
+
offset_ft = offset_ft.astype(float)
|
|
472
|
+
left.geometry = lines.buffer(
|
|
473
|
+
offset_ft, cap_style="flat", single_sided=True, resolution=3
|
|
474
|
+
).boundary
|
|
475
|
+
left["side"] = "left"
|
|
476
|
+
|
|
477
|
+
# Offset lines to the right (reverse direction first)
|
|
478
|
+
reversed_lines = lines.copy()
|
|
479
|
+
reversed_lines.geometry = reversed_lines.geometry.apply(reverse_line)
|
|
480
|
+
right = lines.copy()
|
|
481
|
+
right.geometry = reversed_lines.buffer(
|
|
482
|
+
offset_ft, cap_style="flat", single_sided=True, resolution=3
|
|
483
|
+
).boundary.apply(reverse_line)
|
|
484
|
+
right["side"] = "right"
|
|
485
|
+
|
|
486
|
+
# Combine left and right boundaries
|
|
487
|
+
boundaries = pd.concat([left, right], ignore_index=True)
|
|
488
|
+
boundaries_gdf = gpd.GeoDataFrame(boundaries, crs=lines.crs, geometry="geometry")
|
|
489
|
+
|
|
490
|
+
# Erase buffer caps
|
|
491
|
+
erase_buffer = 0.1
|
|
492
|
+
cleaned_list = []
|
|
493
|
+
eraser = gpd.GeoDataFrame(
|
|
494
|
+
{
|
|
495
|
+
id_col: lines[id_col],
|
|
496
|
+
"geometry": lines.buffer(
|
|
497
|
+
offset_ft - erase_buffer, cap_style="square", resolution=3
|
|
498
|
+
),
|
|
499
|
+
},
|
|
500
|
+
crs=lines.crs,
|
|
501
|
+
)
|
|
502
|
+
for id in lines[id_col].unique():
|
|
503
|
+
cleaned_list.append(
|
|
504
|
+
gpd.overlay(
|
|
505
|
+
boundaries_gdf[boundaries_gdf[id_col] == id],
|
|
506
|
+
eraser[eraser[id_col] == id],
|
|
507
|
+
how="difference",
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
cleaned = gpd.GeoDataFrame(
|
|
511
|
+
pd.concat(cleaned_list, ignore_index=True), crs=lines.crs, geometry="geometry"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# trim ends
|
|
515
|
+
cleaned["geometry"] = cleaned["geometry"].apply(remove_line_ends)
|
|
516
|
+
return cleaned
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rashdf
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Read data from HEC-RAS HDF files.
|
|
5
5
|
Project-URL: repository, https://github.com/fema-ffrd/rashdf
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
7
7
|
Classifier: Intended Audience :: Developers
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: h5py
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cli.py,sha256=SlMHkoosQKO2BDsF34CeYSHjD221AzGYGaKd0T2sOig,6866
|
|
2
|
+
rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
|
|
3
|
+
rashdf/base.py,sha256=cAQJX1aeBJKb3MJ06ltpbRTUaZX5NkuxpR1J4f7FyTU,2507
|
|
4
|
+
rashdf/geom.py,sha256=qieDgMxKrPcUqyVQUbCpRfrDo8VoOqh9Rx7OXOC_nRI,31192
|
|
5
|
+
rashdf/plan.py,sha256=r5_rXqBmJKQ0obm-iEBYpB28eper9jw4PwBcXkwEvSo,65483
|
|
6
|
+
rashdf/utils.py,sha256=-p8nQzotN5jakUCzpmKSYJOvYned8cD13WFjQhX3q1M,15845
|
|
7
|
+
rashdf-0.9.0.dist-info/licenses/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
|
|
8
|
+
rashdf-0.9.0.dist-info/METADATA,sha256=yLj9yYCZdDUJC5_ZyluCMVxVKhzAzp6uWADXPi82c_s,6073
|
|
9
|
+
rashdf-0.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
rashdf-0.9.0.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
|
|
11
|
+
rashdf-0.9.0.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
|
|
12
|
+
rashdf-0.9.0.dist-info/RECORD,,
|
rashdf-0.8.4.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
cli.py,sha256=NtO1bHaiM8GBW5Gv9eSF8Iu-ekwTIQAfV_hd7DBXyLQ,6834
|
|
2
|
-
rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
|
|
3
|
-
rashdf/base.py,sha256=cAQJX1aeBJKb3MJ06ltpbRTUaZX5NkuxpR1J4f7FyTU,2507
|
|
4
|
-
rashdf/geom.py,sha256=O2PMYY7w7fdW2U4u0rsbWeEDKAmsUh4-49ro-xUMc4A,28755
|
|
5
|
-
rashdf/plan.py,sha256=ctkfLBqocF2TpU6wYygXkxE2voCJa8WyVGYlimsyxS4,63612
|
|
6
|
-
rashdf/utils.py,sha256=I23Zij1EFi9v99UXFIJVGaZ2JN48-q5TK1aNsKwL4vE,11113
|
|
7
|
-
rashdf-0.8.4.dist-info/licenses/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
|
|
8
|
-
rashdf-0.8.4.dist-info/METADATA,sha256=ZHbiO37k11MIami2y_JyuRzlLNk_qsgiLCx2Gi6TDKs,6072
|
|
9
|
-
rashdf-0.8.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
rashdf-0.8.4.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
|
|
11
|
-
rashdf-0.8.4.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
|
|
12
|
-
rashdf-0.8.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|