rashdf 0.2.2__py3-none-any.whl → 0.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """rashdf CLI."""
2
2
 
3
- from rashdf import RasGeomHdf
3
+ from rashdf import RasGeomHdf, RasPlanHdf
4
4
  from rashdf.utils import df_datetimes_to_str
5
5
 
6
6
  import fiona
@@ -10,6 +10,7 @@ import argparse
10
10
  from ast import literal_eval
11
11
  from pathlib import Path
12
12
  import sys
13
+ import re
13
14
  from typing import List, Optional
14
15
  import warnings
15
16
 
@@ -108,17 +109,22 @@ def export(args: argparse.Namespace) -> Optional[str]:
108
109
  for driver in fiona_supported_drivers():
109
110
  print(driver)
110
111
  return
111
- if "://" in args.hdf_file:
112
- 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
113
114
  else:
114
- geom_hdf = RasGeomHdf(args.hdf_file)
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)
118
+ else:
119
+ geom_hdf = ras_hdf_class(args.hdf_file)
115
120
  func = getattr(geom_hdf, args.func)
116
121
  gdf: GeoDataFrame = func()
117
122
  kwargs = literal_eval(args.kwargs) if args.kwargs else {}
118
123
  if args.to_crs:
119
124
  gdf = gdf.to_crs(args.to_crs)
120
125
  if not args.output_file:
121
- # 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.
122
128
  gdf = df_datetimes_to_str(gdf)
123
129
  with warnings.catch_warnings():
124
130
  # Squash warnings about converting the CRS to OGC URN format.
@@ -143,9 +149,9 @@ def export(args: argparse.Namespace) -> Optional[str]:
143
149
  output_file_path = Path(args.output_file)
144
150
  output_file_ext = output_file_path.suffix
145
151
  if output_file_ext not in [".gpkg"]:
146
- # unless the user specifies a format that supports datetime,
147
- # convert any datetime columns to string
148
- # 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?
149
155
  gdf = df_datetimes_to_str(gdf)
150
156
  gdf.to_file(args.output_file, **kwargs)
151
157
 
rashdf/geom.py CHANGED
@@ -1,27 +1,27 @@
1
1
  """HEC-RAS Geometry HDF class."""
2
2
 
3
- from .base import RasHdf
4
- from .utils import (
5
- convert_ras_hdf_string,
6
- get_first_hdf_group,
7
- hdf5_attrs_to_dict,
8
- convert_ras_hdf_value,
9
- )
3
+ from typing import Dict, List, Optional
10
4
 
11
5
  import numpy as np
12
6
  import pandas as pd
13
7
  from geopandas import GeoDataFrame
14
8
  from pyproj import CRS
15
9
  from shapely import (
16
- Polygon,
17
- Point,
18
10
  LineString,
19
11
  MultiLineString,
20
12
  MultiPolygon,
13
+ Point,
14
+ Polygon,
21
15
  polygonize_full,
22
16
  )
23
17
 
24
- from typing import Dict, List, Optional
18
+ from .base import RasHdf
19
+ from .utils import (
20
+ convert_ras_hdf_string,
21
+ convert_ras_hdf_value,
22
+ get_first_hdf_group,
23
+ hdf5_attrs_to_dict,
24
+ )
25
25
 
26
26
 
27
27
  class RasGeomHdf(RasHdf):
@@ -66,12 +66,12 @@ class RasGeomHdf(RasHdf):
66
66
  List[str]
67
67
  A list of the 2D mesh area names (str) within the RAS geometry if 2D areas exist.
68
68
  """
69
- if "/Geometry/2D Flow Areas" not in self:
69
+ if self.FLOW_AREA_2D_PATH not in self:
70
70
  return list()
71
71
  return list(
72
72
  [
73
73
  convert_ras_hdf_string(n)
74
- for n in self["/Geometry/2D Flow Areas/Attributes"][()]["Name"]
74
+ for n in self[f"{self.FLOW_AREA_2D_PATH}/Attributes"][()]["Name"]
75
75
  ]
76
76
  )
77
77
 
@@ -87,7 +87,7 @@ class RasGeomHdf(RasHdf):
87
87
  if not mesh_area_names:
88
88
  return GeoDataFrame()
89
89
  mesh_area_polygons = [
90
- Polygon(self[f"/Geometry/2D Flow Areas/{n}/Perimeter"][()])
90
+ Polygon(self[f"{self.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
91
91
  for n in mesh_area_names
92
92
  ]
93
93
  return GeoDataFrame(
@@ -112,13 +112,13 @@ class RasGeomHdf(RasHdf):
112
112
 
113
113
  cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
114
114
  for i, mesh_name in enumerate(mesh_area_names):
115
- 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]
116
116
  cell_ids = list(range(cell_cnt))
117
117
  cell_face_info = self[
118
- 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"
119
119
  ][()]
120
120
  cell_face_values = self[
121
- 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"
122
122
  ][()][:, 0]
123
123
  face_id_lists = list(
124
124
  np.vectorize(
@@ -169,8 +169,8 @@ class RasGeomHdf(RasHdf):
169
169
  return GeoDataFrame()
170
170
  pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
171
171
  for i, mesh_name in enumerate(mesh_area_names):
172
- starting_row, count = self["/Geometry/2D Flow Areas/Cell Info"][()][i]
173
- 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"][()][
174
174
  starting_row : starting_row + count
175
175
  ]
176
176
  pnt_dict["mesh_name"] += [mesh_name] * cell_pnt_coords.shape[0]
@@ -196,16 +196,16 @@ class RasGeomHdf(RasHdf):
196
196
  face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
197
197
  for mesh_name in mesh_area_names:
198
198
  facepoints_index = self[
199
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"
199
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
200
200
  ][()]
201
201
  facepoints_coordinates = self[
202
- f"/Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"
202
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
203
203
  ][()]
204
204
  faces_perimeter_info = self[
205
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"
205
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
206
206
  ][()]
207
207
  faces_perimeter_values = self[
208
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"
208
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Values"
209
209
  ][()]
210
210
  face_id = -1
211
211
  for pnt_a_index, pnt_b_index in facepoints_index:
@@ -465,11 +465,97 @@ class RasGeomHdf(RasHdf):
465
465
  def terrain_modifications(self) -> GeoDataFrame: # noqa D102
466
466
  raise NotImplementedError
467
467
 
468
- def cross_sections(self) -> GeoDataFrame: # noqa D102
469
- raise NotImplementedError
468
+ def cross_sections(self, datetime_to_str: bool = False) -> GeoDataFrame:
469
+ """Return the model 1D cross sections.
470
470
 
471
- def river_reaches(self) -> GeoDataFrame: # noqa D102
472
- raise NotImplementedError
471
+ Returns
472
+ -------
473
+ GeoDataFrame
474
+ A GeoDataFrame containing the model 1D cross sections if they exist.
475
+ """
476
+ if "/Geometry/Cross Sections" not in self:
477
+ return GeoDataFrame()
478
+
479
+ xs_data = self["/Geometry/Cross Sections"]
480
+ v_conv_val = np.vectorize(convert_ras_hdf_value)
481
+ xs_attrs = xs_data["Attributes"][()]
482
+ xs_dict = {"xs_id": range(xs_attrs.shape[0])}
483
+ xs_dict.update(
484
+ {name: v_conv_val(xs_attrs[name]) for name in xs_attrs.dtype.names}
485
+ )
486
+ geoms = list()
487
+ for pnt_start, pnt_cnt, part_start, part_cnt in xs_data["Polyline Info"][()]:
488
+ points = xs_data["Polyline Points"][()][pnt_start : pnt_start + pnt_cnt]
489
+ if part_cnt == 1:
490
+ geoms.append(LineString(points))
491
+ else:
492
+ parts = xs_data["Polyline Parts"][()][
493
+ part_start : part_start + part_cnt
494
+ ]
495
+ geoms.append(
496
+ MultiLineString(
497
+ list(
498
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
499
+ for part_pnt_start, part_pnt_cnt in parts
500
+ )
501
+ )
502
+ )
503
+ xs_gdf = GeoDataFrame(
504
+ xs_dict,
505
+ geometry=geoms,
506
+ crs=self.projection(),
507
+ )
508
+ if datetime_to_str:
509
+ xs_gdf["Last Edited"] = xs_gdf["Last Edited"].apply(
510
+ lambda x: pd.Timestamp.isoformat(x)
511
+ )
512
+ return xs_gdf
513
+
514
+ def river_reaches(self, datetime_to_str: bool = False) -> GeoDataFrame:
515
+ """Return the model 1D river reach lines.
516
+
517
+ Returns
518
+ -------
519
+ GeoDataFrame
520
+ A GeoDataFrame containing the model 1D river reach lines if they exist.
521
+ """
522
+ if "/Geometry/River Centerlines" not in self:
523
+ return GeoDataFrame()
524
+
525
+ river_data = self["/Geometry/River Centerlines"]
526
+ v_conv_val = np.vectorize(convert_ras_hdf_value)
527
+ river_attrs = river_data["Attributes"][()]
528
+ river_dict = {"river_id": range(river_attrs.shape[0])}
529
+ river_dict.update(
530
+ {name: v_conv_val(river_attrs[name]) for name in river_attrs.dtype.names}
531
+ )
532
+ geoms = list()
533
+ for pnt_start, pnt_cnt, part_start, part_cnt in river_data["Polyline Info"][()]:
534
+ points = river_data["Polyline Points"][()][pnt_start : pnt_start + pnt_cnt]
535
+ if part_cnt == 1:
536
+ geoms.append(LineString(points))
537
+ else:
538
+ parts = river_data["Polyline Parts"][()][
539
+ part_start : part_start + part_cnt
540
+ ]
541
+ geoms.append(
542
+ MultiLineString(
543
+ list(
544
+ points[part_pnt_start : part_pnt_start + part_pnt_cnt]
545
+ for part_pnt_start, part_pnt_cnt in parts
546
+ )
547
+ )
548
+ )
549
+ river_gdf = GeoDataFrame(
550
+ river_dict,
551
+ geometry=geoms,
552
+ crs=self.projection(),
553
+ )
554
+ if datetime_to_str:
555
+ river_gdf["Last Edited"] = river_gdf["Last Edited"].apply(
556
+ lambda x: pd.Timestamp.isoformat(x)
557
+ )
558
+ return river_gdf
473
559
 
474
560
  def flowpaths(self) -> GeoDataFrame: # noqa D102
475
561
  raise NotImplementedError
@@ -488,3 +574,33 @@ class RasGeomHdf(RasHdf):
488
574
 
489
575
  def blocked_obstructions(self) -> GeoDataFrame: # noqa D102
490
576
  raise NotImplementedError
577
+
578
+ def cross_sections_elevations(self, round_to: int = 2) -> pd.DataFrame:
579
+ """Return the model cross section elevation information.
580
+
581
+ Returns
582
+ -------
583
+ DataFrame
584
+ A DataFrame containing the model cross section elevation information if they exist.
585
+ """
586
+ path = "/Geometry/Cross Sections"
587
+ if path not in self:
588
+ return pd.DataFrame()
589
+
590
+ xselev_data = self[path]
591
+ xs_df = self.cross_sections()
592
+ elevations = list()
593
+ for part_start, part_cnt in xselev_data["Station Elevation Info"][()]:
594
+ xzdata = xselev_data["Station Elevation Values"][()][
595
+ part_start : part_start + part_cnt
596
+ ]
597
+ elevations.append(xzdata)
598
+
599
+ xs_elev_df = xs_df[
600
+ ["xs_id", "River", "Reach", "RS", "Left Bank", "Right Bank"]
601
+ ].copy()
602
+ xs_elev_df["Left Bank"] = xs_elev_df["Left Bank"].round(round_to).astype(str)
603
+ xs_elev_df["Right Bank"] = xs_elev_df["Right Bank"].round(round_to).astype(str)
604
+ xs_elev_df["elevation info"] = elevations
605
+
606
+ return xs_elev_df