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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rashdf
3
- Version: 0.8.0
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.0"
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 Plan HDF error class."""
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
- mesh_area_polygons = [
107
- Polygon(self[f"{self.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
108
- for n in mesh_area_names
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
- points = polyline_points[pnt_start : pnt_start + pnt_cnt]
300
- if part_cnt == 1:
301
- geoms.append(LineString(points))
302
- else:
303
- parts = polyline_parts[part_start : part_start + part_cnt]
304
- geoms.append(
305
- MultiLineString(
306
- list(
307
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
308
- for part_pnt_start, part_pnt_cnt in parts
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 "/Geometry/2D Flow Area Refinement Regions" not in self:
390
+ if self.REFINEMENT_REGIONS_PATH not in self:
374
391
  return GeoDataFrame()
375
- rr_data = self["/Geometry/2D Flow Area Refinement Regions"]
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 rr_data["Polygon Info"][()]:
380
- points = rr_data["Polygon Points"][()][pnt_start : pnt_start + pnt_cnt]
381
- if part_cnt == 1:
382
- geoms.append(Polygon(points))
383
- else:
384
- parts = rr_data["Polygon Parts"][()][part_start : part_start + part_cnt]
385
- geoms.append(
386
- MultiPolygon(
387
- list(
388
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
389
- for part_pnt_start, part_pnt_cnt in parts
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.0
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"
@@ -1,7 +1,7 @@
1
1
  h5py
2
2
  geopandas<2.0,>=1.0
3
3
  pyarrow
4
- xarray
4
+ xarray<=2025.4.0
5
5
 
6
6
  [dev]
7
7
  pre-commit
@@ -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