rashdf 0.8.0__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rashdf-0.8.0 → rashdf-0.8.2}/PKG-INFO +2 -2
- {rashdf-0.8.0 → rashdf-0.8.2}/pyproject.toml +2 -2
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf/geom.py +59 -29
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/PKG-INFO +2 -2
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/requires.txt +1 -1
- {rashdf-0.8.0 → rashdf-0.8.2}/tests/test_geom.py +70 -1
- {rashdf-0.8.0 → rashdf-0.8.2}/LICENSE +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/README.md +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/setup.cfg +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/cli.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf/__init__.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf/base.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf/plan.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf/utils.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/SOURCES.txt +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/dependency_links.txt +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/entry_points.txt +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/src/rashdf.egg-info/top_level.txt +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/tests/test_base.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/tests/test_cli.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/tests/test_plan.py +0 -0
- {rashdf-0.8.0 → rashdf-0.8.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rashdf
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
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
|
|
@@ -16,7 +16,7 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: h5py
|
|
17
17
|
Requires-Dist: geopandas<2.0,>=1.0
|
|
18
18
|
Requires-Dist: pyarrow
|
|
19
|
-
Requires-Dist: xarray
|
|
19
|
+
Requires-Dist: xarray<=2025.4.0
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: pre-commit; extra == "dev"
|
|
22
22
|
Requires-Dist: ruff; extra == "dev"
|
|
@@ -12,8 +12,8 @@ classifiers = [
|
|
|
12
12
|
"Programming Language :: Python :: 3.11",
|
|
13
13
|
"Programming Language :: Python :: 3.12",
|
|
14
14
|
]
|
|
15
|
-
version = "0.8.
|
|
16
|
-
dependencies = ["h5py", "geopandas>=1.0,<2.0", "pyarrow", "xarray"]
|
|
15
|
+
version = "0.8.2"
|
|
16
|
+
dependencies = ["h5py", "geopandas>=1.0,<2.0", "pyarrow", "xarray<=2025.4.0"]
|
|
17
17
|
|
|
18
18
|
[project.optional-dependencies]
|
|
19
19
|
dev = [
|
|
@@ -17,6 +17,8 @@ from shapely import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from typing import Dict, List, Optional, Union
|
|
20
|
+
from warnings import warn
|
|
21
|
+
from pathlib import Path
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
from .base import RasHdf
|
|
@@ -29,7 +31,7 @@ from .utils import (
|
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class RasGeomHdfError(Exception):
|
|
32
|
-
"""HEC-RAS
|
|
34
|
+
"""HEC-RAS Geometry HDF error class."""
|
|
33
35
|
|
|
34
36
|
pass
|
|
35
37
|
|
|
@@ -43,6 +45,7 @@ class RasGeomHdf(RasHdf):
|
|
|
43
45
|
BC_LINES_PATH = f"{GEOM_PATH}/Boundary Condition Lines"
|
|
44
46
|
IC_POINTS_PATH = f"{GEOM_PATH}/IC Points"
|
|
45
47
|
BREAKLINES_PATH = f"{GEOM_PATH}/2D Flow Area Break Lines"
|
|
48
|
+
REFINEMENT_REGIONS_PATH = f"{GEOM_PATH}/2D Flow Area Refinement Regions"
|
|
46
49
|
REFERENCE_LINES_PATH = f"{GEOM_PATH}/Reference Lines"
|
|
47
50
|
REFERENCE_POINTS_PATH = f"{GEOM_PATH}/Reference Points"
|
|
48
51
|
CROSS_SECTIONS = f"{GEOM_PATH}/Cross Sections"
|
|
@@ -103,10 +106,15 @@ class RasGeomHdf(RasHdf):
|
|
|
103
106
|
mesh_area_names = self.mesh_area_names()
|
|
104
107
|
if not mesh_area_names:
|
|
105
108
|
return GeoDataFrame()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
|
|
110
|
+
mesh_area_polygons = []
|
|
111
|
+
for n in mesh_area_names:
|
|
112
|
+
try:
|
|
113
|
+
mesh_area_polygons.append(
|
|
114
|
+
Polygon(self[f"{self.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
|
|
115
|
+
)
|
|
116
|
+
except KeyError as e:
|
|
117
|
+
raise RasGeomHdfError(f"Data for mesh '{n}' not found.") from e
|
|
110
118
|
return GeoDataFrame(
|
|
111
119
|
{"mesh_name": mesh_area_names, "geometry": mesh_area_polygons},
|
|
112
120
|
geometry="geometry",
|
|
@@ -295,19 +303,28 @@ class RasGeomHdf(RasHdf):
|
|
|
295
303
|
polyline_points = self[polyline_points_path][()]
|
|
296
304
|
|
|
297
305
|
geoms = []
|
|
298
|
-
for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
for i, (pnt_start, pnt_cnt, part_start, part_cnt) in enumerate(polyline_info):
|
|
307
|
+
try:
|
|
308
|
+
points = polyline_points[pnt_start : pnt_start + pnt_cnt]
|
|
309
|
+
if part_cnt == 1:
|
|
310
|
+
geoms.append(LineString(points))
|
|
311
|
+
else: # pragma: no cover | TODO: add test coverage for this
|
|
312
|
+
parts = polyline_parts[part_start : part_start + part_cnt]
|
|
313
|
+
geoms.append(
|
|
314
|
+
MultiLineString(
|
|
315
|
+
list(
|
|
316
|
+
points[part_pnt_start : part_pnt_start + part_pnt_cnt]
|
|
317
|
+
for part_pnt_start, part_pnt_cnt in parts
|
|
318
|
+
)
|
|
309
319
|
)
|
|
310
320
|
)
|
|
321
|
+
except (
|
|
322
|
+
Exception
|
|
323
|
+
) as e: # pragma: no cover | TODO: add test coverage for this
|
|
324
|
+
geoms.append(None)
|
|
325
|
+
warn(
|
|
326
|
+
f"Feature ID {i} within '{Path(path).name}' layer set to null due to invalid geometry. {e}",
|
|
327
|
+
UserWarning,
|
|
311
328
|
)
|
|
312
329
|
return geoms
|
|
313
330
|
|
|
@@ -370,25 +387,38 @@ class RasGeomHdf(RasHdf):
|
|
|
370
387
|
GeoDataFrame
|
|
371
388
|
A GeoDataFrame containing the 2D mesh area refinement regions if they exist.
|
|
372
389
|
"""
|
|
373
|
-
if
|
|
390
|
+
if self.REFINEMENT_REGIONS_PATH not in self:
|
|
374
391
|
return GeoDataFrame()
|
|
375
|
-
rr_data = self[
|
|
392
|
+
rr_data = self[self.REFINEMENT_REGIONS_PATH]
|
|
376
393
|
rr_ids = range(rr_data["Attributes"][()].shape[0])
|
|
377
394
|
names = np.vectorize(convert_ras_hdf_string)(rr_data["Attributes"][()]["Name"])
|
|
378
395
|
geoms = list()
|
|
379
|
-
for pnt_start, pnt_cnt, part_start, part_cnt in
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
396
|
+
for i, (pnt_start, pnt_cnt, part_start, part_cnt) in enumerate(
|
|
397
|
+
rr_data["Polygon Info"][()]
|
|
398
|
+
):
|
|
399
|
+
try:
|
|
400
|
+
points = rr_data["Polygon Points"][()][pnt_start : pnt_start + pnt_cnt]
|
|
401
|
+
if part_cnt == 1:
|
|
402
|
+
geoms.append(Polygon(points))
|
|
403
|
+
else: # pragma: no cover | TODO: add test coverage for this
|
|
404
|
+
parts = rr_data["Polygon Parts"][()][
|
|
405
|
+
part_start : part_start + part_cnt
|
|
406
|
+
]
|
|
407
|
+
geoms.append(
|
|
408
|
+
MultiPolygon(
|
|
409
|
+
list(
|
|
410
|
+
points[part_pnt_start : part_pnt_start + part_pnt_cnt]
|
|
411
|
+
for part_pnt_start, part_pnt_cnt in parts
|
|
412
|
+
)
|
|
390
413
|
)
|
|
391
414
|
)
|
|
415
|
+
except (
|
|
416
|
+
Exception
|
|
417
|
+
) as e: # pragma: no cover | TODO: add test coverage for this
|
|
418
|
+
geoms.append(None)
|
|
419
|
+
warn(
|
|
420
|
+
f"Feature ID {i} within '{Path(self.REFINEMENT_REGIONS_PATH).name}' layer set to null due to invalid geometry. {e}",
|
|
421
|
+
UserWarning,
|
|
392
422
|
)
|
|
393
423
|
return GeoDataFrame(
|
|
394
424
|
{"rr_id": rr_ids, "name": names, "geometry": geoms},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rashdf
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
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
|
|
@@ -16,7 +16,7 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: h5py
|
|
17
17
|
Requires-Dist: geopandas<2.0,>=1.0
|
|
18
18
|
Requires-Dist: pyarrow
|
|
19
|
-
Requires-Dist: xarray
|
|
19
|
+
Requires-Dist: xarray<=2025.4.0
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: pre-commit; extra == "dev"
|
|
22
22
|
Requires-Dist: ruff; extra == "dev"
|
|
@@ -2,8 +2,10 @@ import geopandas as gpd
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import h5py
|
|
4
4
|
from pyproj import CRS
|
|
5
|
-
from src.rashdf import RasGeomHdf
|
|
5
|
+
from src.rashdf.geom import RasGeomHdf, RasGeomHdfError
|
|
6
6
|
from pandas.testing import assert_frame_equal
|
|
7
|
+
import pytest
|
|
8
|
+
import numpy as np
|
|
7
9
|
|
|
8
10
|
from . import _create_hdf_with_group_attrs, _gdf_matches_json, _gdf_matches_json_alt
|
|
9
11
|
|
|
@@ -35,30 +37,85 @@ def test_mesh_area_names():
|
|
|
35
37
|
assert ghdf.mesh_area_names() == ["2D Interior Area", "Perimeter_NW"]
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
def test_invalid_mesh_area_names(tmp_path):
|
|
41
|
+
test_hdf = tmp_path / "test.hdf"
|
|
42
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
43
|
+
# Test the empty Mesh Area names
|
|
44
|
+
with RasGeomHdf(test_hdf) as ghdf:
|
|
45
|
+
assert ghdf.mesh_area_names() == []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_missing_mesh_in_mesh_area(tmp_path):
|
|
49
|
+
test_hdf = tmp_path / "test.hdf"
|
|
50
|
+
mesh_name = "blw-west-fork"
|
|
51
|
+
with h5py.File(test_hdf, "w") as f:
|
|
52
|
+
dtype = np.dtype([("Name", h5py.string_dtype("utf-8"))])
|
|
53
|
+
data = np.array([(mesh_name,)], dtype=dtype)
|
|
54
|
+
f.create_dataset(f"{RasGeomHdf.FLOW_AREA_2D_PATH}/Attributes", data=data)
|
|
55
|
+
|
|
56
|
+
ras_hdf = RasGeomHdf(test_hdf)
|
|
57
|
+
expected_error_message = f"Data for mesh '{mesh_name}' not found."
|
|
58
|
+
with pytest.raises(RasGeomHdfError, match=expected_error_message):
|
|
59
|
+
ras_hdf.mesh_areas()
|
|
60
|
+
|
|
61
|
+
|
|
38
62
|
def test_mesh_areas():
|
|
39
63
|
mesh_areas_json = TEST_JSON / "mesh_areas.json"
|
|
40
64
|
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
41
65
|
assert _gdf_matches_json(ghdf.mesh_areas(), mesh_areas_json)
|
|
42
66
|
|
|
43
67
|
|
|
68
|
+
def test_invalid_mesh_areas(tmp_path):
|
|
69
|
+
test_hdf = tmp_path / "test.hdf"
|
|
70
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
71
|
+
# Test the empty Mesh Areas
|
|
72
|
+
with RasGeomHdf(test_hdf) as ghdf:
|
|
73
|
+
assert ghdf.mesh_areas().empty
|
|
74
|
+
|
|
75
|
+
|
|
44
76
|
def test_mesh_cell_faces():
|
|
45
77
|
mesh_cell_faces_json = TEST_JSON / "mesh_cell_faces.json"
|
|
46
78
|
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
47
79
|
assert _gdf_matches_json(ghdf.mesh_cell_faces(), mesh_cell_faces_json)
|
|
48
80
|
|
|
49
81
|
|
|
82
|
+
def test_invalid_mesh_faces(tmp_path):
|
|
83
|
+
test_hdf = tmp_path / "test.hdf"
|
|
84
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
85
|
+
# Test the empty Mesh Faces
|
|
86
|
+
with RasGeomHdf(test_hdf) as ghdf:
|
|
87
|
+
assert ghdf.mesh_cell_faces().empty
|
|
88
|
+
|
|
89
|
+
|
|
50
90
|
def test_mesh_cell_points():
|
|
51
91
|
mesh_cell_points_json = TEST_JSON / "mesh_cell_points.json"
|
|
52
92
|
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
53
93
|
assert _gdf_matches_json(ghdf.mesh_cell_points(), mesh_cell_points_json)
|
|
54
94
|
|
|
55
95
|
|
|
96
|
+
def test_invalid_mesh_cell_points(tmp_path):
|
|
97
|
+
test_hdf = tmp_path / "test.hdf"
|
|
98
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
99
|
+
# Test the empty Mesh Cell Points
|
|
100
|
+
with RasGeomHdf(test_hdf) as ghdf:
|
|
101
|
+
assert ghdf.mesh_cell_points().empty
|
|
102
|
+
|
|
103
|
+
|
|
56
104
|
def test_mesh_cell_polygons():
|
|
57
105
|
mesh_cell_polygons_json = TEST_JSON / "mesh_cell_polygons.json"
|
|
58
106
|
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
59
107
|
assert _gdf_matches_json(ghdf.mesh_cell_polygons(), mesh_cell_polygons_json)
|
|
60
108
|
|
|
61
109
|
|
|
110
|
+
def test_invalid_mesh_cell_polygons(tmp_path):
|
|
111
|
+
# Create a dummy HDF file
|
|
112
|
+
test_hdf = tmp_path / "test.hdf"
|
|
113
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
114
|
+
# Test the empty Mesh Cell Polygons
|
|
115
|
+
with RasGeomHdf(test_hdf) as ghdf:
|
|
116
|
+
assert ghdf.mesh_cell_polygons().empty
|
|
117
|
+
|
|
118
|
+
|
|
62
119
|
def test_mesh_cell_polygons_coal():
|
|
63
120
|
"""Test with the mesh from the Coal River model.
|
|
64
121
|
|
|
@@ -118,6 +175,18 @@ def test_get_geom_2d_flow_area_attrs(tmp_path):
|
|
|
118
175
|
assert ras_hdf.get_geom_2d_flow_area_attrs() == TEST_ATTRS
|
|
119
176
|
|
|
120
177
|
|
|
178
|
+
def test_invalid_get_geom_2d_flow_area_attrs(tmp_path):
|
|
179
|
+
test_hdf = tmp_path / "test.hdf"
|
|
180
|
+
_create_hdf_with_group_attrs(test_hdf, RasGeomHdf.GEOM_PATH, TEST_ATTRS)
|
|
181
|
+
ras_hdf = RasGeomHdf(test_hdf)
|
|
182
|
+
|
|
183
|
+
with pytest.raises(
|
|
184
|
+
AttributeError,
|
|
185
|
+
match=f"Unable to get 2D Flow Area; {RasGeomHdf.FLOW_AREA_2D_PATH} group not found in HDF5 file.",
|
|
186
|
+
):
|
|
187
|
+
ras_hdf.get_geom_2d_flow_area_attrs()
|
|
188
|
+
|
|
189
|
+
|
|
121
190
|
def test_structs():
|
|
122
191
|
structs_json = TEST_JSON / "structures.json"
|
|
123
192
|
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|