rashdf 0.7.2__py3-none-any.whl → 0.8.1__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 CHANGED
@@ -21,6 +21,7 @@ COMMANDS = [
21
21
  "mesh_cell_faces",
22
22
  "refinement_regions",
23
23
  "bc_lines",
24
+ "ic_points",
24
25
  "breaklines",
25
26
  "reference_lines",
26
27
  "reference_points",
rashdf/geom.py CHANGED
@@ -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
 
@@ -41,7 +43,9 @@ class RasGeomHdf(RasHdf):
41
43
  GEOM_STRUCTURES_PATH = f"{GEOM_PATH}/Structures"
42
44
  FLOW_AREA_2D_PATH = f"{GEOM_PATH}/2D Flow Areas"
43
45
  BC_LINES_PATH = f"{GEOM_PATH}/Boundary Condition Lines"
46
+ IC_POINTS_PATH = f"{GEOM_PATH}/IC Points"
44
47
  BREAKLINES_PATH = f"{GEOM_PATH}/2D Flow Area Break Lines"
48
+ REFINEMENT_REGIONS_PATH = f"{GEOM_PATH}/2D Flow Area Refinement Regions"
45
49
  REFERENCE_LINES_PATH = f"{GEOM_PATH}/Reference Lines"
46
50
  REFERENCE_POINTS_PATH = f"{GEOM_PATH}/Reference Points"
47
51
  CROSS_SECTIONS = f"{GEOM_PATH}/Cross Sections"
@@ -294,19 +298,28 @@ class RasGeomHdf(RasHdf):
294
298
  polyline_points = self[polyline_points_path][()]
295
299
 
296
300
  geoms = []
297
- for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
298
- points = polyline_points[pnt_start : pnt_start + pnt_cnt]
299
- if part_cnt == 1:
300
- geoms.append(LineString(points))
301
- else:
302
- parts = polyline_parts[part_start : part_start + part_cnt]
303
- geoms.append(
304
- MultiLineString(
305
- list(
306
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
307
- for part_pnt_start, part_pnt_cnt in parts
301
+ for i, (pnt_start, pnt_cnt, part_start, part_cnt) in enumerate(polyline_info):
302
+ try:
303
+ points = polyline_points[pnt_start : pnt_start + pnt_cnt]
304
+ if part_cnt == 1:
305
+ geoms.append(LineString(points))
306
+ else: # pragma: no cover | TODO: add test coverage for this
307
+ parts = polyline_parts[part_start : part_start + part_cnt]
308
+ geoms.append(
309
+ MultiLineString(
310
+ list(
311
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
312
+ for part_pnt_start, part_pnt_cnt in parts
313
+ )
308
314
  )
309
315
  )
316
+ except (
317
+ Exception
318
+ ) as e: # pragma: no cover | TODO: add test coverage for this
319
+ geoms.append(None)
320
+ warn(
321
+ f"Feature ID {i} within '{Path(path).name}' layer set to null due to invalid geometry. {e}",
322
+ UserWarning,
310
323
  )
311
324
  return geoms
312
325
 
@@ -369,25 +382,38 @@ class RasGeomHdf(RasHdf):
369
382
  GeoDataFrame
370
383
  A GeoDataFrame containing the 2D mesh area refinement regions if they exist.
371
384
  """
372
- if "/Geometry/2D Flow Area Refinement Regions" not in self:
385
+ if self.REFINEMENT_REGIONS_PATH not in self:
373
386
  return GeoDataFrame()
374
- rr_data = self["/Geometry/2D Flow Area Refinement Regions"]
387
+ rr_data = self[self.REFINEMENT_REGIONS_PATH]
375
388
  rr_ids = range(rr_data["Attributes"][()].shape[0])
376
389
  names = np.vectorize(convert_ras_hdf_string)(rr_data["Attributes"][()]["Name"])
377
390
  geoms = list()
378
- for pnt_start, pnt_cnt, part_start, part_cnt in rr_data["Polygon Info"][()]:
379
- points = rr_data["Polygon Points"][()][pnt_start : pnt_start + pnt_cnt]
380
- if part_cnt == 1:
381
- geoms.append(Polygon(points))
382
- else:
383
- parts = rr_data["Polygon Parts"][()][part_start : part_start + part_cnt]
384
- geoms.append(
385
- MultiPolygon(
386
- list(
387
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
388
- for part_pnt_start, part_pnt_cnt in parts
391
+ for i, (pnt_start, pnt_cnt, part_start, part_cnt) in enumerate(
392
+ rr_data["Polygon Info"][()]
393
+ ):
394
+ try:
395
+ points = rr_data["Polygon Points"][()][pnt_start : pnt_start + pnt_cnt]
396
+ if part_cnt == 1:
397
+ geoms.append(Polygon(points))
398
+ else: # pragma: no cover | TODO: add test coverage for this
399
+ parts = rr_data["Polygon Parts"][()][
400
+ part_start : part_start + part_cnt
401
+ ]
402
+ geoms.append(
403
+ MultiPolygon(
404
+ list(
405
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
406
+ for part_pnt_start, part_pnt_cnt in parts
407
+ )
389
408
  )
390
409
  )
410
+ except (
411
+ Exception
412
+ ) as e: # pragma: no cover | TODO: add test coverage for this
413
+ geoms.append(None)
414
+ warn(
415
+ f"Feature ID {i} within '{Path(self.REFINEMENT_REGIONS_PATH).name}' layer set to null due to invalid geometry. {e}",
416
+ UserWarning,
391
417
  )
392
418
  return GeoDataFrame(
393
419
  {"rr_id": rr_ids, "name": names, "geometry": geoms},
@@ -408,7 +434,10 @@ class RasGeomHdf(RasHdf):
408
434
  GeoDataFrame
409
435
  A GeoDataFrame containing the model structures if they exist.
410
436
  """
411
- if self.GEOM_STRUCTURES_PATH not in self:
437
+ if (
438
+ self.GEOM_STRUCTURES_PATH not in self
439
+ or f"{self.GEOM_STRUCTURES_PATH}/Attributes" not in self
440
+ ):
412
441
  return GeoDataFrame()
413
442
  struct_data = self[self.GEOM_STRUCTURES_PATH]
414
443
  v_conv_val = np.vectorize(convert_ras_hdf_value)
@@ -438,7 +467,32 @@ class RasGeomHdf(RasHdf):
438
467
  raise NotImplementedError
439
468
 
440
469
  def ic_points(self) -> GeoDataFrame: # noqa D102
441
- raise NotImplementedError
470
+ """Return initial conditions points.
471
+
472
+ Returns
473
+ -------
474
+ GeoDataFrame
475
+ A GeoDataFrame containing the initial condition points if they exist.
476
+ """
477
+ if self.IC_POINTS_PATH not in self:
478
+ return GeoDataFrame()
479
+ ic_data = self[self.IC_POINTS_PATH]
480
+ v_conv_str = np.vectorize(convert_ras_hdf_string)
481
+ names = v_conv_str(ic_data["Attributes"][()]["Name"])
482
+ mesh_names = v_conv_str(ic_data["Attributes"][()]["SA/2D"])
483
+ cell_ids = ic_data["Attributes"][()]["Cell Index"]
484
+ points = ic_data["Points"][()]
485
+ return GeoDataFrame(
486
+ {
487
+ "icpt_id": range(len(names)),
488
+ "icpt_name": names,
489
+ "mesh_name": mesh_names,
490
+ "cell_id": cell_ids,
491
+ "geometry": list(map(Point, points)),
492
+ },
493
+ geometry="geometry",
494
+ crs=self.projection(),
495
+ )
442
496
 
443
497
  def _reference_lines_points_names(
444
498
  self, reftype: str = "lines", mesh_name: Optional[str] = None
rashdf/plan.py CHANGED
@@ -168,6 +168,7 @@ class RasPlanHdf(RasGeomHdf):
168
168
  UNSTEADY_TIME_SERIES_PATH = f"{BASE_OUTPUT_PATH}/Unsteady Time Series"
169
169
  REFERENCE_LINES_OUTPUT_PATH = f"{UNSTEADY_TIME_SERIES_PATH}/Reference Lines"
170
170
  REFERENCE_POINTS_OUTPUT_PATH = f"{UNSTEADY_TIME_SERIES_PATH}/Reference Points"
171
+ BOUNDARY_CONDITIONS_OUTPUT_PATH = f"{UNSTEADY_TIME_SERIES_PATH}/Boundary Conditions"
171
172
  OBS_FLOW_OUTPUT_PATH = f"{OBS_DATA_PATH}/Flow"
172
173
  OBS_STAGE_OUTPUT_PATH = f"{OBS_DATA_PATH}/Stage"
173
174
 
@@ -1121,6 +1122,81 @@ class RasPlanHdf(RasGeomHdf):
1121
1122
  """
1122
1123
  return self.reference_timeseries_output(reftype="lines")
1123
1124
 
1125
+ def bc_line_timeseries_output(self, bc_line_name: str) -> xr.Dataset:
1126
+ """Return timeseries output data for a specific boundary condition line from a HEC-RAS HDF plan file.
1127
+
1128
+ Parameters
1129
+ ----------
1130
+ bc_line_name : str
1131
+ The name of the boundary condition line.
1132
+
1133
+ Returns
1134
+ -------
1135
+ xr.Dataset
1136
+ An xarray Dataset with timeseries output data for the specified boundary condition line.
1137
+ """
1138
+ path = f"{self.BOUNDARY_CONDITIONS_OUTPUT_PATH}/{bc_line_name}"
1139
+ dataset = self.get(path)
1140
+ if dataset is None:
1141
+ raise RasPlanHdfError(
1142
+ f"Could not find HDF group at path '{path}'."
1143
+ f" Does the Plan HDF file contain boundary condition output data for '{bc_line_name}'?"
1144
+ )
1145
+ columns = [c.decode("utf-8") for c in dataset.attrs["Columns"]]
1146
+ ds = xr.Dataset()
1147
+ try:
1148
+ import dask.array as da
1149
+
1150
+ # TODO: user-specified chunks?
1151
+ values = da.from_array(dataset, chunks=dataset.chunks)
1152
+ except ImportError:
1153
+ values = dataset[:]
1154
+ for i, col in enumerate(columns):
1155
+ units = dataset.attrs.get(col, None)
1156
+ if units is not None:
1157
+ units = units.decode("utf-8")
1158
+ da = xr.DataArray(
1159
+ values[:, i],
1160
+ name=col,
1161
+ dims=["time"],
1162
+ coords={
1163
+ "time": self.unsteady_datetimes(),
1164
+ },
1165
+ attrs={
1166
+ "units": units,
1167
+ "hdf_path": f"{path}",
1168
+ },
1169
+ )
1170
+ ds[col] = da
1171
+ return ds
1172
+
1173
+ def bc_lines_timeseries_output(self) -> xr.Dataset:
1174
+ """Return timeseries output data for boundary conditions lines from a HEC-RAS HDF plan file.
1175
+
1176
+ Returns
1177
+ -------
1178
+ xr.Dataset
1179
+ An xarray Dataset with timeseries output data for boundary conditions lines.
1180
+ """
1181
+ df_bc_lines = self.bc_lines()
1182
+ bc_lines_names = df_bc_lines["name"]
1183
+ datasets = []
1184
+ for bc_line_name in bc_lines_names:
1185
+ ds_bc_line = self.bc_line_timeseries_output(bc_line_name)
1186
+ datasets.append(ds_bc_line)
1187
+ bc_line_ids = df_bc_lines["bc_line_id"].values
1188
+ ds: xr.Dataset = xr.concat(
1189
+ datasets, dim=pd.Index(bc_line_ids, name="bc_line_id")
1190
+ )
1191
+ ds = ds.assign_coords(
1192
+ {
1193
+ "bc_line_name": ("bc_line_id", bc_lines_names),
1194
+ "bc_line_type": ("bc_line_id", df_bc_lines["type"]),
1195
+ "mesh_name": ("bc_line_id", df_bc_lines["mesh_name"]),
1196
+ }
1197
+ )
1198
+ return ds
1199
+
1124
1200
  def observed_timeseries_input(self, vartype: str = "Flow") -> xr.DataArray:
1125
1201
  """Return observed timeseries input data for reference lines and points from a HEC-RAS HDF plan file.
1126
1202
 
@@ -1381,10 +1457,49 @@ class RasPlanHdf(RasGeomHdf):
1381
1457
  """
1382
1458
  return self.get_attrs(self.VOLUME_ACCOUNTING_PATH)
1383
1459
 
1384
- def enroachment_points(self) -> GeoDataFrame: # noqa: D102
1385
- raise NotImplementedError
1460
+ def encroachment_points(self, profile_name: str) -> GeoDataFrame:
1461
+ """Return encroachment points from a HEC-RAS plan HDF file based on a user-specified flow profile.
1462
+
1463
+ Returns
1464
+ -------
1465
+ GeoDataframe
1466
+ A GeoDataFrame with cross-section encroachments represented as Point geometry features along with pertinent attributes.
1467
+ """
1468
+ cross_sections = self.cross_sections()
1469
+ cross_sections["Enc_Profile"] = profile_name
1470
+
1471
+ leftmost_sta = self.cross_sections_elevations()["elevation info"].apply(
1472
+ lambda x: x[0][0]
1473
+ )
1474
+ left_enc_sta = self.cross_sections_additional_enc_station_left()[profile_name]
1475
+ left_enc_points = GeoDataFrame(
1476
+ pd.concat(
1477
+ [
1478
+ cross_sections[["River", "Reach", "RS", "Enc_Profile"]],
1479
+ left_enc_sta.rename("Enc_Sta", inplace=False),
1480
+ ],
1481
+ axis=1,
1482
+ ),
1483
+ geometry=cross_sections.geometry.interpolate(left_enc_sta - leftmost_sta),
1484
+ )
1485
+ left_enc_points["Side"] = "Left"
1486
+
1487
+ right_enc_sta = self.cross_sections_additional_enc_station_right()[profile_name]
1488
+ right_enc_points = GeoDataFrame(
1489
+ pd.concat(
1490
+ [
1491
+ cross_sections[["River", "Reach", "RS", "Enc_Profile"]],
1492
+ right_enc_sta.rename("Enc_Sta", inplace=False),
1493
+ ],
1494
+ axis=1,
1495
+ ),
1496
+ geometry=cross_sections.geometry.interpolate(right_enc_sta - leftmost_sta),
1497
+ )
1498
+ right_enc_points["Side"] = "Right"
1499
+
1500
+ return GeoDataFrame(pd.concat([left_enc_points, right_enc_points]))
1386
1501
 
1387
- def steady_flow_names(self) -> list:
1502
+ def steady_flow_names(self) -> List[str]:
1388
1503
  """Return the profile information for each steady flow event.
1389
1504
 
1390
1505
  Returns
@@ -1393,7 +1508,7 @@ class RasPlanHdf(RasGeomHdf):
1393
1508
  A Dataframe containing the profile names for each event
1394
1509
  """
1395
1510
  if self.STEADY_PROFILES_PATH not in self:
1396
- return pd.DataFrame()
1511
+ return []
1397
1512
 
1398
1513
  profile_data = self[self.STEADY_PROFILES_PATH]
1399
1514
  profile_attrs = profile_data["Profile Names"][()]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: rashdf
3
- Version: 0.7.2
3
+ Version: 0.8.1
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"
@@ -28,10 +28,12 @@ Requires-Dist: dask; extra == "dev"
28
28
  Requires-Dist: fsspec; extra == "dev"
29
29
  Requires-Dist: s3fs; extra == "dev"
30
30
  Requires-Dist: fiona==1.9.6; extra == "dev"
31
+ Requires-Dist: numcodecs<0.16; extra == "dev"
31
32
  Provides-Extra: docs
32
33
  Requires-Dist: sphinx; extra == "docs"
33
34
  Requires-Dist: numpydoc; extra == "docs"
34
35
  Requires-Dist: sphinx_rtd_theme; extra == "docs"
36
+ Dynamic: license-file
35
37
 
36
38
  # rashdf
37
39
  [![CI](https://github.com/fema-ffrd/rashdf/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/fema-ffrd/rashdf/actions/workflows/continuous-integration.yml)
@@ -0,0 +1,12 @@
1
+ cli.py,sha256=HbyrdUVKfrmtU2T9ljKTPQ-ugomJqYbCA26ghGJDJh0,6588
2
+ rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
3
+ rashdf/base.py,sha256=cAQJX1aeBJKb3MJ06ltpbRTUaZX5NkuxpR1J4f7FyTU,2507
4
+ rashdf/geom.py,sha256=-GmHmddcdIcfOn-SFS940WyDLUilW9inrp_nuZ8aTHo,28306
5
+ rashdf/plan.py,sha256=d8YhpC6cV8rhh3qf1o12TbhUvo_4pMh75vIdDkcAvjE,63971
6
+ rashdf/utils.py,sha256=Cba6sULF0m0jg6CQass4bPm2oxTd_avoe1pRQxq082c,10896
7
+ rashdf-0.8.1.dist-info/licenses/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
+ rashdf-0.8.1.dist-info/METADATA,sha256=7h2fJs_IYE81euocfUfqCtY0qSPoMOJ-yV5kKdZ9Zco,6072
9
+ rashdf-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ rashdf-0.8.1.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
+ rashdf-0.8.1.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
+ rashdf-0.8.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- cli.py,sha256=yItWmCxnYLcuOpJVRpUsfv_NLS9IxLjojZB9GrxfKAU,6571
2
- rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
3
- rashdf/base.py,sha256=cAQJX1aeBJKb3MJ06ltpbRTUaZX5NkuxpR1J4f7FyTU,2507
4
- rashdf/geom.py,sha256=2aTfj6mqZGP6rysflQ5L8FeItlYJsknO00sKHo-yaTw,26090
5
- rashdf/plan.py,sha256=vGqxQssz6ObxIO48Vej-d7EtBJBZ90zCrt7gC5pHATE,59558
6
- rashdf/utils.py,sha256=Cba6sULF0m0jg6CQass4bPm2oxTd_avoe1pRQxq082c,10896
7
- rashdf-0.7.2.dist-info/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
- rashdf-0.7.2.dist-info/METADATA,sha256=KkdJpptV8_IoCQwIa9ErfEGIJIz8rLMUvBZkdXuhDAQ,5994
9
- rashdf-0.7.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
10
- rashdf-0.7.2.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
- rashdf-0.7.2.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
- rashdf-0.7.2.dist-info/RECORD,,