rashdf 0.2.1__tar.gz → 0.3.0__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.1
2
2
  Name: rashdf
3
- Version: 0.2.1
3
+ Version: 0.3.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
@@ -16,20 +16,31 @@ License-File: LICENSE
16
16
  Requires-Dist: h5py
17
17
  Requires-Dist: geopandas
18
18
  Requires-Dist: pyarrow
19
+ Requires-Dist: xarray
19
20
  Provides-Extra: dev
20
21
  Requires-Dist: pre-commit; extra == "dev"
21
22
  Requires-Dist: ruff; extra == "dev"
22
23
  Requires-Dist: pytest; extra == "dev"
24
+ Requires-Dist: pytest-cov; extra == "dev"
25
+ Provides-Extra: docs
26
+ Requires-Dist: sphinx; extra == "docs"
27
+ Requires-Dist: numpydoc; extra == "docs"
28
+ Requires-Dist: sphinx_rtd_theme; extra == "docs"
23
29
 
24
30
  # rashdf
25
31
  [![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)
26
32
  [![Release](https://github.com/fema-ffrd/rashdf/actions/workflows/release.yml/badge.svg)](https://github.com/fema-ffrd/rashdf/actions/workflows/release.yml)
27
33
  [![PyPI version](https://badge.fury.io/py/rashdf.svg)](https://badge.fury.io/py/rashdf)
34
+ [![codecov](https://codecov.io/gh/fema-ffrd/rashdf/graph/badge.svg?token=CTIIONEHV1)](https://codecov.io/gh/fema-ffrd/rashdf)
35
+ [![Documentation Status](https://readthedocs.org/projects/rashdf/badge/?version=latest)](https://rashdf.readthedocs.io/en/latest/?badge=latest)
28
36
 
29
37
  Read data from [HEC-RAS](https://www.hec.usace.army.mil/software/hec-ras/) [HDF](https://github.com/HDFGroup/hdf5) files.
30
38
 
31
39
  *Pronunciation: `raz·aitch·dee·eff`*
32
40
 
41
+ ## Documentation
42
+ [rashdf on ReadTheDocs](http://rashdf.readthedocs.io/)
43
+
33
44
  ## Install
34
45
  ```bash
35
46
  $ pip install rashdf
@@ -120,9 +131,6 @@ Example: write structures GeoJSON to `stdout`:
120
131
  $ rashdf structures Potomac.p01.hdf
121
132
  ```
122
133
 
123
- ## Documentation
124
- Coming soon.
125
-
126
134
  ## Developer Setup
127
135
  Create a virtual environment in the project directory:
128
136
  ```
@@ -2,11 +2,16 @@
2
2
  [![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)
3
3
  [![Release](https://github.com/fema-ffrd/rashdf/actions/workflows/release.yml/badge.svg)](https://github.com/fema-ffrd/rashdf/actions/workflows/release.yml)
4
4
  [![PyPI version](https://badge.fury.io/py/rashdf.svg)](https://badge.fury.io/py/rashdf)
5
+ [![codecov](https://codecov.io/gh/fema-ffrd/rashdf/graph/badge.svg?token=CTIIONEHV1)](https://codecov.io/gh/fema-ffrd/rashdf)
6
+ [![Documentation Status](https://readthedocs.org/projects/rashdf/badge/?version=latest)](https://rashdf.readthedocs.io/en/latest/?badge=latest)
5
7
 
6
8
  Read data from [HEC-RAS](https://www.hec.usace.army.mil/software/hec-ras/) [HDF](https://github.com/HDFGroup/hdf5) files.
7
9
 
8
10
  *Pronunciation: `raz·aitch·dee·eff`*
9
11
 
12
+ ## Documentation
13
+ [rashdf on ReadTheDocs](http://rashdf.readthedocs.io/)
14
+
10
15
  ## Install
11
16
  ```bash
12
17
  $ pip install rashdf
@@ -97,9 +102,6 @@ Example: write structures GeoJSON to `stdout`:
97
102
  $ rashdf structures Potomac.p01.hdf
98
103
  ```
99
104
 
100
- ## Documentation
101
- Coming soon.
102
-
103
105
  ## Developer Setup
104
106
  Create a virtual environment in the project directory:
105
107
  ```
@@ -12,11 +12,12 @@ classifiers = [
12
12
  "Programming Language :: Python :: 3.11",
13
13
  "Programming Language :: Python :: 3.12",
14
14
  ]
15
- version = "0.2.1"
16
- dependencies = ["h5py", "geopandas", "pyarrow"]
15
+ version = "0.3.0"
16
+ dependencies = ["h5py", "geopandas", "pyarrow", "xarray"]
17
17
 
18
18
  [project.optional-dependencies]
19
- dev = ["pre-commit", "ruff", "pytest"]
19
+ dev = ["pre-commit", "ruff", "pytest", "pytest-cov"]
20
+ docs = ["sphinx", "numpydoc", "sphinx_rtd_theme"]
20
21
 
21
22
  [project.urls]
22
23
  repository = "https://github.com/fema-ffrd/rashdf"
@@ -28,10 +29,12 @@ rashdf = "cli:main"
28
29
  pythonpath = "src"
29
30
  testpaths = "tests"
30
31
 
31
- # TODO: linting for docstrings.
32
- # https://github.com/fema-ffrd/rashdf/issues/15
33
- # [tool.ruff.lint]
34
- # select = ["D"]
32
+ [tool.ruff.lint]
33
+ select = ["D"]
35
34
 
36
- # [tool.ruff.lint.pydocstyle]
37
- # convention = "numpy"
35
+ [tool.ruff.lint.pydocstyle]
36
+ convention = "numpy"
37
+
38
+ [tool.ruff.lint.per-file-ignores]
39
+ "tests/**" = ["D"]
40
+ "docs/**" = ["D"]
@@ -1,4 +1,6 @@
1
- from rashdf import RasGeomHdf
1
+ """rashdf CLI."""
2
+
3
+ from rashdf import RasGeomHdf, RasPlanHdf
2
4
  from rashdf.utils import df_datetimes_to_str
3
5
 
4
6
  import fiona
@@ -8,6 +10,7 @@ import argparse
8
10
  from ast import literal_eval
9
11
  from pathlib import Path
10
12
  import sys
13
+ import re
11
14
  from typing import List, Optional
12
15
  import warnings
13
16
 
@@ -60,6 +63,7 @@ def fiona_supported_drivers() -> List[str]:
60
63
 
61
64
 
62
65
  def parse_args(args: str) -> argparse.Namespace:
66
+ """Parse command-line arguments."""
63
67
  parser = argparse.ArgumentParser(description="Extract data from HEC-RAS HDF files.")
64
68
  parser.add_argument(
65
69
  "--fiona-drivers",
@@ -100,21 +104,27 @@ def parse_args(args: str) -> argparse.Namespace:
100
104
 
101
105
 
102
106
  def export(args: argparse.Namespace) -> Optional[str]:
107
+ """Act on parsed arguments to extract data from HEC-RAS HDF files."""
103
108
  if args.fiona_drivers:
104
109
  for driver in fiona_supported_drivers():
105
110
  print(driver)
106
111
  return
107
- if "://" in args.hdf_file:
108
- geom_hdf = RasGeomHdf.open_uri(args.hdf_file)
112
+ if re.match(r"^.*\.p\d\d\.hdf$", args.hdf_file):
113
+ ras_hdf_class = RasPlanHdf
114
+ else:
115
+ ras_hdf_class = RasGeomHdf
116
+ if re.match(r"^\w+://", args.hdf_file):
117
+ geom_hdf = ras_hdf_class.open_uri(args.hdf_file)
109
118
  else:
110
- geom_hdf = RasGeomHdf(args.hdf_file)
119
+ geom_hdf = ras_hdf_class(args.hdf_file)
111
120
  func = getattr(geom_hdf, args.func)
112
121
  gdf: GeoDataFrame = func()
113
122
  kwargs = literal_eval(args.kwargs) if args.kwargs else {}
114
123
  if args.to_crs:
115
124
  gdf = gdf.to_crs(args.to_crs)
116
125
  if not args.output_file:
117
- # convert any datetime columns to strings
126
+ # If an output file path isn't provided, write the GeoDataFrame to stdout
127
+ # as GeoJSON. Convert any datetime columns to strings.
118
128
  gdf = df_datetimes_to_str(gdf)
119
129
  with warnings.catch_warnings():
120
130
  # Squash warnings about converting the CRS to OGC URN format.
@@ -139,14 +149,15 @@ def export(args: argparse.Namespace) -> Optional[str]:
139
149
  output_file_path = Path(args.output_file)
140
150
  output_file_ext = output_file_path.suffix
141
151
  if output_file_ext not in [".gpkg"]:
142
- # unless the user specifies a format that supports datetime,
143
- # convert any datetime columns to string
144
- # TODO: besides Geopackage, which of the standard Fiona formats allow datetime?
152
+ # Unless the user specifies a format that supports datetime,
153
+ # convert any datetime columns to string.
154
+ # TODO: besides Geopackage, which of the standard Fiona drivers allow datetime?
145
155
  gdf = df_datetimes_to_str(gdf)
146
156
  gdf.to_file(args.output_file, **kwargs)
147
157
 
148
158
 
149
159
  def main():
160
+ """Entry point for the rashdf CLI."""
150
161
  args = parse_args(sys.argv[1:])
151
162
  export(args)
152
163
 
@@ -1,3 +1,5 @@
1
+ """rashdf package."""
2
+
1
3
  from .base import RasHdf
2
4
  from .geom import RasGeomHdf
3
5
  from .plan import RasPlanHdf
@@ -1,3 +1,5 @@
1
+ """Base class for reading HEC-RAS HDF files."""
2
+
1
3
  import h5py
2
4
  from .utils import hdf5_attrs_to_dict
3
5
  from typing import Dict
@@ -68,7 +70,7 @@ class RasHdf(h5py.File):
68
70
  return {}
69
71
 
70
72
  def get_root_attrs(self):
71
- """Returns attributes at root level of HEC-RAS HDF file.
73
+ """Return attributes at root level of HEC-RAS HDF file.
72
74
 
73
75
  Returns
74
76
  -------
@@ -1,3 +1,5 @@
1
+ """HEC-RAS Geometry HDF class."""
2
+
1
3
  from .base import RasHdf
2
4
  from .utils import (
3
5
  convert_ras_hdf_string,
@@ -16,27 +18,37 @@ from shapely import (
16
18
  LineString,
17
19
  MultiLineString,
18
20
  MultiPolygon,
19
- polygonize,
21
+ polygonize_full,
20
22
  )
21
23
 
22
- from typing import List, Optional
24
+ from typing import Dict, List, Optional
23
25
 
24
26
 
25
27
  class RasGeomHdf(RasHdf):
28
+ """HEC-RAS Geometry HDF class."""
29
+
26
30
  GEOM_PATH = "Geometry"
27
31
  GEOM_STRUCTURES_PATH = f"{GEOM_PATH}/Structures"
28
32
  FLOW_AREA_2D_PATH = f"{GEOM_PATH}/2D Flow Areas"
29
33
 
30
34
  def __init__(self, name: str, **kwargs):
35
+ """Open a HEC-RAS Geometry HDF file.
36
+
37
+ Parameters
38
+ ----------
39
+ name : str
40
+ The path to the RAS Geometry HDF file.
41
+ kwargs : dict
42
+ Additional keyword arguments to pass to h5py.File
43
+ """
31
44
  super().__init__(name, **kwargs)
32
45
 
33
46
  def projection(self) -> Optional[CRS]:
34
- """Return the projection of the RAS geometry as a
35
- pyproj.CRS object.
47
+ """Return the projection of the RAS geometry as a pyproj.CRS object.
36
48
 
37
49
  Returns
38
50
  -------
39
- CRS
51
+ pyproj.CRS or None
40
52
  The projection of the RAS geometry.
41
53
  """
42
54
  proj_wkt = self.attrs.get("Projection")
@@ -47,20 +59,19 @@ class RasGeomHdf(RasHdf):
47
59
  return CRS.from_wkt(proj_wkt)
48
60
 
49
61
  def mesh_area_names(self) -> List[str]:
50
- """Return a list of the 2D mesh area names of
51
- the RAS geometry.
62
+ """Return a list of the 2D mesh area names of the RAS geometry.
52
63
 
53
64
  Returns
54
65
  -------
55
- list
66
+ List[str]
56
67
  A list of the 2D mesh area names (str) within the RAS geometry if 2D areas exist.
57
68
  """
58
- if "/Geometry/2D Flow Areas" not in self:
69
+ if self.FLOW_AREA_2D_PATH not in self:
59
70
  return list()
60
71
  return list(
61
72
  [
62
73
  convert_ras_hdf_string(n)
63
- for n in self["/Geometry/2D Flow Areas/Attributes"][()]["Name"]
74
+ for n in self[f"{self.FLOW_AREA_2D_PATH}/Attributes"][()]["Name"]
64
75
  ]
65
76
  )
66
77
 
@@ -76,7 +87,7 @@ class RasGeomHdf(RasHdf):
76
87
  if not mesh_area_names:
77
88
  return GeoDataFrame()
78
89
  mesh_area_polygons = [
79
- Polygon(self[f"/Geometry/2D Flow Areas/{n}/Perimeter"][()])
90
+ Polygon(self[f"{self.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
80
91
  for n in mesh_area_names
81
92
  ]
82
93
  return GeoDataFrame(
@@ -101,13 +112,13 @@ class RasGeomHdf(RasHdf):
101
112
 
102
113
  cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
103
114
  for i, mesh_name in enumerate(mesh_area_names):
104
- cell_cnt = self["/Geometry/2D Flow Areas/Cell Info"][()][i][1]
115
+ cell_cnt = self[f"{self.FLOW_AREA_2D_PATH}/Cell Info"][()][i][1]
105
116
  cell_ids = list(range(cell_cnt))
106
117
  cell_face_info = self[
107
- f"/Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"
118
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Info"
108
119
  ][()]
109
120
  cell_face_values = self[
110
- f"/Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"
121
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Values"
111
122
  ][()][:, 0]
112
123
  face_id_lists = list(
113
124
  np.vectorize(
@@ -128,13 +139,19 @@ class RasGeomHdf(RasHdf):
128
139
  cell_dict["cell_id"] += cell_ids
129
140
  cell_dict["geometry"] += list(
130
141
  np.vectorize(
131
- lambda face_id_list: polygonize(
132
- np.ravel(
133
- mesh_faces[
134
- np.array(face_id_list.strip("[]").split()).astype(int)
135
- ]
142
+ lambda face_id_list: (
143
+ lambda geom_col: Polygon((geom_col[0] or geom_col[3]).geoms[0])
144
+ )(
145
+ polygonize_full(
146
+ np.ravel(
147
+ mesh_faces[
148
+ np.array(face_id_list.strip("[]").split()).astype(
149
+ int
150
+ )
151
+ ]
152
+ )
136
153
  )
137
- ).geoms[0]
154
+ )
138
155
  )(face_id_lists)
139
156
  )
140
157
  return GeoDataFrame(cell_dict, geometry="geometry", crs=self.projection())
@@ -152,8 +169,8 @@ class RasGeomHdf(RasHdf):
152
169
  return GeoDataFrame()
153
170
  pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
154
171
  for i, mesh_name in enumerate(mesh_area_names):
155
- starting_row, count = self["/Geometry/2D Flow Areas/Cell Info"][()][i]
156
- cell_pnt_coords = self["/Geometry/2D Flow Areas/Cell Points"][()][
172
+ starting_row, count = self[f"{self.FLOW_AREA_2D_PATH}/Cell Info"][()][i]
173
+ cell_pnt_coords = self[f"{self.FLOW_AREA_2D_PATH}/Cell Points"][()][
157
174
  starting_row : starting_row + count
158
175
  ]
159
176
  pnt_dict["mesh_name"] += [mesh_name] * cell_pnt_coords.shape[0]
@@ -179,16 +196,16 @@ class RasGeomHdf(RasHdf):
179
196
  face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
180
197
  for mesh_name in mesh_area_names:
181
198
  facepoints_index = self[
182
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"
199
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
183
200
  ][()]
184
201
  facepoints_coordinates = self[
185
- f"/Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"
202
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
186
203
  ][()]
187
204
  faces_perimeter_info = self[
188
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"
205
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
189
206
  ][()]
190
207
  faces_perimeter_values = self[
191
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"
208
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Values"
192
209
  ][()]
193
210
  face_id = -1
194
211
  for pnt_a_index, pnt_b_index in facepoints_index:
@@ -206,8 +223,8 @@ class RasGeomHdf(RasHdf):
206
223
  face_dict["geometry"].append(LineString(coordinates))
207
224
  return GeoDataFrame(face_dict, geometry="geometry", crs=self.projection())
208
225
 
209
- def get_geom_attrs(self):
210
- """Returns base geometry attributes from a HEC-RAS HDF file.
226
+ def get_geom_attrs(self) -> Dict:
227
+ """Return base geometry attributes from a HEC-RAS HDF file.
211
228
 
212
229
  Returns
213
230
  -------
@@ -216,8 +233,8 @@ class RasGeomHdf(RasHdf):
216
233
  """
217
234
  return self.get_attrs(self.GEOM_PATH)
218
235
 
219
- def get_geom_structures_attrs(self):
220
- """Returns geometry structures attributes from a HEC-RAS HDF file.
236
+ def get_geom_structures_attrs(self) -> Dict:
237
+ """Return geometry structures attributes from a HEC-RAS HDF file.
221
238
 
222
239
  Returns
223
240
  -------
@@ -226,8 +243,8 @@ class RasGeomHdf(RasHdf):
226
243
  """
227
244
  return self.get_attrs(self.GEOM_STRUCTURES_PATH)
228
245
 
229
- def get_geom_2d_flow_area_attrs(self):
230
- """Returns geometry 2d flow area attributes from a HEC-RAS HDF file.
246
+ def get_geom_2d_flow_area_attrs(self) -> Dict:
247
+ """Return geometry 2d flow area attributes from a HEC-RAS HDF file.
231
248
 
232
249
  Returns
233
250
  -------
@@ -373,6 +390,11 @@ class RasGeomHdf(RasHdf):
373
390
  def structures(self, datetime_to_str: bool = False) -> GeoDataFrame:
374
391
  """Return the model structures.
375
392
 
393
+ Parameters
394
+ ----------
395
+ datetime_to_str : bool, optional
396
+ If True, convert datetime values to string format (default: False).
397
+
376
398
  Returns
377
399
  -------
378
400
  GeoDataFrame
@@ -419,50 +441,50 @@ class RasGeomHdf(RasHdf):
419
441
  )
420
442
  return struct_gdf
421
443
 
422
- def connections(self) -> GeoDataFrame:
444
+ def connections(self) -> GeoDataFrame: # noqa D102
423
445
  raise NotImplementedError
424
446
 
425
- def ic_points(self) -> GeoDataFrame:
447
+ def ic_points(self) -> GeoDataFrame: # noqa D102
426
448
  raise NotImplementedError
427
449
 
428
- def reference_lines(self) -> GeoDataFrame:
450
+ def reference_lines(self) -> GeoDataFrame: # noqa D102
429
451
  raise NotImplementedError
430
452
 
431
- def reference_points(self) -> GeoDataFrame:
453
+ def reference_points(self) -> GeoDataFrame: # noqa D102
432
454
  raise NotImplementedError
433
455
 
434
- def pump_stations(self) -> GeoDataFrame:
456
+ def pump_stations(self) -> GeoDataFrame: # noqa D102
435
457
  raise NotImplementedError
436
458
 
437
- def mannings_calibration_regions(self) -> GeoDataFrame:
459
+ def mannings_calibration_regions(self) -> GeoDataFrame: # noqa D102
438
460
  raise NotImplementedError
439
461
 
440
- def classification_polygons(self) -> GeoDataFrame:
462
+ def classification_polygons(self) -> GeoDataFrame: # noqa D102
441
463
  raise NotImplementedError
442
464
 
443
- def terrain_modifications(self) -> GeoDataFrame:
465
+ def terrain_modifications(self) -> GeoDataFrame: # noqa D102
444
466
  raise NotImplementedError
445
467
 
446
- def cross_sections(self) -> GeoDataFrame:
468
+ def cross_sections(self) -> GeoDataFrame: # noqa D102
447
469
  raise NotImplementedError
448
470
 
449
- def river_reaches(self) -> GeoDataFrame:
471
+ def river_reaches(self) -> GeoDataFrame: # noqa D102
450
472
  raise NotImplementedError
451
473
 
452
- def flowpaths(self) -> GeoDataFrame:
474
+ def flowpaths(self) -> GeoDataFrame: # noqa D102
453
475
  raise NotImplementedError
454
476
 
455
- def bank_points(self) -> GeoDataFrame:
477
+ def bank_points(self) -> GeoDataFrame: # noqa D102
456
478
  raise NotImplementedError
457
479
 
458
- def bank_lines(self) -> GeoDataFrame:
480
+ def bank_lines(self) -> GeoDataFrame: # noqa D102
459
481
  raise NotImplementedError
460
482
 
461
- def ineffective_areas(self) -> GeoDataFrame:
483
+ def ineffective_areas(self) -> GeoDataFrame: # noqa D102
462
484
  raise NotImplementedError
463
485
 
464
- def ineffective_points(self) -> GeoDataFrame:
486
+ def ineffective_points(self) -> GeoDataFrame: # noqa D102
465
487
  raise NotImplementedError
466
488
 
467
- def blocked_obstructions(self) -> GeoDataFrame:
489
+ def blocked_obstructions(self) -> GeoDataFrame: # noqa D102
468
490
  raise NotImplementedError