rashdf 0.4.0__py3-none-any.whl → 0.6.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
@@ -3,7 +3,6 @@
3
3
  from rashdf import RasGeomHdf, RasPlanHdf
4
4
  from rashdf.utils import df_datetimes_to_str
5
5
 
6
- import fiona
7
6
  from geopandas import GeoDataFrame
8
7
 
9
8
  import argparse
@@ -23,6 +22,8 @@ COMMANDS = [
23
22
  "refinement_regions",
24
23
  "bc_lines",
25
24
  "breaklines",
25
+ "reference_lines",
26
+ "reference_points",
26
27
  "structures",
27
28
  ]
28
29
 
@@ -50,6 +51,20 @@ def docstring_to_help(docstring: Optional[str]) -> str:
50
51
  return help_text
51
52
 
52
53
 
54
+ def pyogrio_supported_drivers() -> List[str]:
55
+ """Return a list of drivers supported by pyogrio for writing output files.
56
+
57
+ Returns
58
+ -------
59
+ list
60
+ A list of drivers supported by pyogrio for writing output files.
61
+ """
62
+ import pyogrio
63
+
64
+ drivers = pyogrio.list_drivers(write=True)
65
+ return sorted(drivers)
66
+
67
+
53
68
  def fiona_supported_drivers() -> List[str]:
54
69
  """Return a list of drivers supported by Fiona for writing output files.
55
70
 
@@ -58,18 +73,34 @@ def fiona_supported_drivers() -> List[str]:
58
73
  list
59
74
  A list of drivers supported by Fiona for writing output files.
60
75
  """
76
+ import fiona
77
+
61
78
  drivers = [d for d, s in fiona.supported_drivers.items() if "w" in s]
62
- return drivers
79
+ return sorted(drivers)
63
80
 
64
81
 
65
82
  def parse_args(args: str) -> argparse.Namespace:
66
83
  """Parse command-line arguments."""
67
84
  parser = argparse.ArgumentParser(description="Extract data from HEC-RAS HDF files.")
68
85
  parser.add_argument(
69
- "--fiona-drivers",
86
+ "--pyogrio-drivers",
70
87
  action="store_true",
71
- help="List the drivers supported by Fiona for writing output files.",
88
+ help="List the drivers supported by pyogrio for writing output files.",
72
89
  )
90
+ fiona_installed = False
91
+ engines = ["pyogrio"]
92
+ try:
93
+ import fiona
94
+
95
+ fiona_installed = True
96
+ engines.append("fiona")
97
+ parser.add_argument(
98
+ "--fiona-drivers",
99
+ action="store_true",
100
+ help="List the drivers supported by Fiona for writing output files.",
101
+ )
102
+ except ImportError:
103
+ pass
73
104
  subparsers = parser.add_subparsers(help="Sub-command help")
74
105
  for command in COMMANDS:
75
106
  f = getattr(RasGeomHdf, command)
@@ -91,6 +122,13 @@ def parse_args(args: str) -> argparse.Namespace:
91
122
  output_group.add_argument(
92
123
  "--feather", action="store_true", help="Output as Feather."
93
124
  )
125
+ output_group.add_argument(
126
+ "--engine",
127
+ type=str,
128
+ choices=engines,
129
+ default="pyogrio",
130
+ help="Engine for writing output data.",
131
+ )
94
132
  subparser.add_argument(
95
133
  "--kwargs",
96
134
  type=str,
@@ -105,7 +143,11 @@ def parse_args(args: str) -> argparse.Namespace:
105
143
 
106
144
  def export(args: argparse.Namespace) -> Optional[str]:
107
145
  """Act on parsed arguments to extract data from HEC-RAS HDF files."""
108
- if args.fiona_drivers:
146
+ if args.pyogrio_drivers:
147
+ for driver in pyogrio_supported_drivers():
148
+ print(driver)
149
+ return
150
+ if hasattr(args, "fiona_drivers") and args.fiona_drivers:
109
151
  for driver in fiona_supported_drivers():
110
152
  print(driver)
111
153
  return
@@ -138,6 +180,7 @@ def export(args: argparse.Namespace) -> Optional[str]:
138
180
  ),
139
181
  )
140
182
  result = gdf.to_json(**kwargs)
183
+ print("No output file!")
141
184
  print(result)
142
185
  return result
143
186
  elif args.parquet:
@@ -153,7 +196,7 @@ def export(args: argparse.Namespace) -> Optional[str]:
153
196
  # convert any datetime columns to string.
154
197
  # TODO: besides Geopackage, which of the standard Fiona drivers allow datetime?
155
198
  gdf = df_datetimes_to_str(gdf)
156
- gdf.to_file(args.output_file, **kwargs)
199
+ gdf.to_file(args.output_file, engine=args.engine, **kwargs)
157
200
 
158
201
 
159
202
  def main():
rashdf/base.py CHANGED
@@ -19,6 +19,7 @@ class RasHdf(h5py.File):
19
19
  Additional keyword arguments to pass to h5py.File
20
20
  """
21
21
  super().__init__(name, mode="r", **kwargs)
22
+ self._loc = name
22
23
 
23
24
  @classmethod
24
25
  def open_uri(
@@ -49,7 +50,9 @@ class RasHdf(h5py.File):
49
50
  import fsspec
50
51
 
51
52
  remote_file = fsspec.open(uri, mode="rb", **fsspec_kwargs)
52
- return cls(remote_file.open(), **h5py_kwargs)
53
+ result = cls(remote_file.open(), **h5py_kwargs)
54
+ result._loc = uri
55
+ return result
53
56
 
54
57
  def get_attrs(self, attr_path: str) -> Dict:
55
58
  """Convert attributes from a HEC-RAS HDF file into a Python dictionary for a given attribute path.
rashdf/geom.py CHANGED
@@ -1,12 +1,13 @@
1
1
  """HEC-RAS Geometry HDF class."""
2
2
 
3
- from typing import Dict, List, Optional
4
-
5
3
  import numpy as np
6
4
  import pandas as pd
7
5
  from geopandas import GeoDataFrame
8
6
  from pyproj import CRS
9
7
  from shapely import (
8
+ Geometry,
9
+ Polygon,
10
+ Point,
10
11
  LineString,
11
12
  MultiLineString,
12
13
  MultiPolygon,
@@ -15,6 +16,9 @@ from shapely import (
15
16
  polygonize_full,
16
17
  )
17
18
 
19
+ from typing import Dict, List, Optional, Union
20
+
21
+
18
22
  from .base import RasHdf
19
23
  from .utils import (
20
24
  convert_ras_hdf_string,
@@ -24,12 +28,24 @@ from .utils import (
24
28
  )
25
29
 
26
30
 
31
+ class RasGeomHdfError(Exception):
32
+ """HEC-RAS Plan HDF error class."""
33
+
34
+ pass
35
+
36
+
27
37
  class RasGeomHdf(RasHdf):
28
38
  """HEC-RAS Geometry HDF class."""
29
39
 
30
40
  GEOM_PATH = "Geometry"
31
41
  GEOM_STRUCTURES_PATH = f"{GEOM_PATH}/Structures"
32
42
  FLOW_AREA_2D_PATH = f"{GEOM_PATH}/2D Flow Areas"
43
+ BC_LINES_PATH = f"{GEOM_PATH}/Boundary Condition Lines"
44
+ BREAKLINES_PATH = f"{GEOM_PATH}/2D Flow Area Break Lines"
45
+ REFERENCE_LINES_PATH = f"{GEOM_PATH}/Reference Lines"
46
+ REFERENCE_POINTS_PATH = f"{GEOM_PATH}/Reference Points"
47
+ CROSS_SECTIONS = f"{GEOM_PATH}/Cross Sections"
48
+ RIVER_CENTERLINES = f"{GEOM_PATH}/River Centerlines"
33
49
 
34
50
  def __init__(self, name: str, **kwargs):
35
51
  """Open a HEC-RAS Geometry HDF file.
@@ -262,6 +278,38 @@ class RasGeomHdf(RasHdf):
262
278
 
263
279
  return d2_flow_area_attrs
264
280
 
281
+ def _get_polylines(
282
+ self,
283
+ path: str,
284
+ info_name: str = "Polyline Info",
285
+ parts_name: str = "Polyline Parts",
286
+ points_name: str = "Polyline Points",
287
+ ) -> List[Geometry]:
288
+ polyline_info_path = f"{path}/{info_name}"
289
+ polyline_parts_path = f"{path}/{parts_name}"
290
+ polyline_points_path = f"{path}/{points_name}"
291
+
292
+ polyline_info = self[polyline_info_path][()]
293
+ polyline_parts = self[polyline_parts_path][()]
294
+ polyline_points = self[polyline_points_path][()]
295
+
296
+ 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
308
+ )
309
+ )
310
+ )
311
+ return geoms
312
+
265
313
  def bc_lines(self) -> GeoDataFrame:
266
314
  """Return 2D mesh area boundary condition lines.
267
315
 
@@ -270,35 +318,15 @@ class RasGeomHdf(RasHdf):
270
318
  GeoDataFrame
271
319
  A GeoDataFrame containing the 2D mesh area boundary condition lines if they exist.
272
320
  """
273
- if "/Geometry/Boundary Condition Lines" not in self:
321
+ if self.BC_LINES_PATH not in self:
274
322
  return GeoDataFrame()
275
- bc_line_data = self["/Geometry/Boundary Condition Lines"]
323
+ bc_line_data = self[self.BC_LINES_PATH]
276
324
  bc_line_ids = range(bc_line_data["Attributes"][()].shape[0])
277
325
  v_conv_str = np.vectorize(convert_ras_hdf_string)
278
326
  names = v_conv_str(bc_line_data["Attributes"][()]["Name"])
279
327
  mesh_names = v_conv_str(bc_line_data["Attributes"][()]["SA-2D"])
280
328
  types = v_conv_str(bc_line_data["Attributes"][()]["Type"])
281
- geoms = list()
282
- for pnt_start, pnt_cnt, part_start, part_cnt in bc_line_data["Polyline Info"][
283
- ()
284
- ]:
285
- points = bc_line_data["Polyline Points"][()][
286
- pnt_start : pnt_start + pnt_cnt
287
- ]
288
- if part_cnt == 1:
289
- geoms.append(LineString(points))
290
- else:
291
- parts = bc_line_data["Polyline Parts"][()][
292
- part_start : part_start + part_cnt
293
- ]
294
- geoms.append(
295
- MultiLineString(
296
- list(
297
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
298
- for part_pnt_start, part_pnt_cnt in parts
299
- )
300
- )
301
- )
329
+ geoms = self._get_polylines(self.BC_LINES_PATH)
302
330
  return GeoDataFrame(
303
331
  {
304
332
  "bc_line_id": bc_line_ids,
@@ -319,34 +347,14 @@ class RasGeomHdf(RasHdf):
319
347
  GeoDataFrame
320
348
  A GeoDataFrame containing the 2D mesh area breaklines if they exist.
321
349
  """
322
- if "/Geometry/2D Flow Area Break Lines" not in self:
350
+ if self.BREAKLINES_PATH not in self:
323
351
  return GeoDataFrame()
324
- bl_line_data = self["/Geometry/2D Flow Area Break Lines"]
352
+ bl_line_data = self[self.BREAKLINES_PATH]
325
353
  bl_line_ids = range(bl_line_data["Attributes"][()].shape[0])
326
354
  names = np.vectorize(convert_ras_hdf_string)(
327
355
  bl_line_data["Attributes"][()]["Name"]
328
356
  )
329
- geoms = list()
330
- for pnt_start, pnt_cnt, part_start, part_cnt in bl_line_data["Polyline Info"][
331
- ()
332
- ]:
333
- points = bl_line_data["Polyline Points"][()][
334
- pnt_start : pnt_start + pnt_cnt
335
- ]
336
- if part_cnt == 1:
337
- geoms.append(LineString(points))
338
- else:
339
- parts = bl_line_data["Polyline Parts"][()][
340
- part_start : part_start + part_cnt
341
- ]
342
- geoms.append(
343
- MultiLineString(
344
- list(
345
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
346
- for part_pnt_start, part_pnt_cnt in parts
347
- )
348
- )
349
- )
357
+ geoms = self._get_polylines(self.BREAKLINES_PATH)
350
358
  return GeoDataFrame(
351
359
  {"bl_id": bl_line_ids, "name": names, "geometry": geoms},
352
360
  geometry="geometry",
@@ -400,36 +408,21 @@ class RasGeomHdf(RasHdf):
400
408
  GeoDataFrame
401
409
  A GeoDataFrame containing the model structures if they exist.
402
410
  """
403
- if "/Geometry/Structures" not in self:
411
+ if self.GEOM_STRUCTURES_PATH not in self:
404
412
  return GeoDataFrame()
405
- struct_data = self["/Geometry/Structures"]
413
+ struct_data = self[self.GEOM_STRUCTURES_PATH]
406
414
  v_conv_val = np.vectorize(convert_ras_hdf_value)
407
415
  sd_attrs = struct_data["Attributes"][()]
408
416
  struct_dict = {"struct_id": range(sd_attrs.shape[0])}
409
417
  struct_dict.update(
410
418
  {name: v_conv_val(sd_attrs[name]) for name in sd_attrs.dtype.names}
411
419
  )
412
- geoms = list()
413
- for pnt_start, pnt_cnt, part_start, part_cnt in struct_data["Centerline Info"][
414
- ()
415
- ]:
416
- points = struct_data["Centerline Points"][()][
417
- pnt_start : pnt_start + pnt_cnt
418
- ]
419
- if part_cnt == 1:
420
- geoms.append(LineString(points))
421
- else:
422
- parts = struct_data["Centerline Parts"][()][
423
- part_start : part_start + part_cnt
424
- ]
425
- geoms.append(
426
- MultiLineString(
427
- list(
428
- points[part_pnt_start : part_pnt_start + part_pnt_cnt]
429
- for part_pnt_start, part_pnt_cnt in parts
430
- )
431
- )
432
- )
420
+ geoms = self._get_polylines(
421
+ self.GEOM_STRUCTURES_PATH,
422
+ info_name="Centerline Info",
423
+ parts_name="Centerline Parts",
424
+ points_name="Centerline Points",
425
+ )
433
426
  struct_gdf = GeoDataFrame(
434
427
  struct_dict,
435
428
  geometry=geoms,
@@ -447,11 +440,153 @@ class RasGeomHdf(RasHdf):
447
440
  def ic_points(self) -> GeoDataFrame: # noqa D102
448
441
  raise NotImplementedError
449
442
 
450
- def reference_lines(self) -> GeoDataFrame: # noqa D102
451
- raise NotImplementedError
443
+ def _reference_lines_points_names(
444
+ self, reftype: str = "lines", mesh_name: Optional[str] = None
445
+ ) -> Union[Dict[str, List[str]], List[str]]:
446
+ """Return reference line names.
452
447
 
453
- def reference_points(self) -> GeoDataFrame: # noqa D102
454
- raise NotImplementedError
448
+ If a mesh name is provided, return a list of the reference line names for that mesh area.
449
+ If no mesh name is provided, return a dictionary of mesh names and their reference line names.
450
+
451
+ Parameters
452
+ ----------
453
+ mesh_name : str, optional
454
+ The name of the mesh area for which to return reference line names.
455
+
456
+ Returns
457
+ -------
458
+ Union[Dict[str, List[str]], List[str]]
459
+ A dictionary of mesh names and their reference line names if mesh_name is None.
460
+ A list of reference line names for the specified mesh area if mesh_name is not None.
461
+ """
462
+ if reftype == "lines":
463
+ path = self.REFERENCE_LINES_PATH
464
+ sa_2d_field = "SA-2D"
465
+ elif reftype == "points":
466
+ path = self.REFERENCE_POINTS_PATH
467
+ sa_2d_field = "SA/2D"
468
+ else:
469
+ raise RasGeomHdfError(
470
+ f"Invalid reference type: {reftype} -- must be 'lines' or 'points'."
471
+ )
472
+ attributes_path = f"{path}/Attributes"
473
+ if mesh_name is None and attributes_path not in self:
474
+ return {m: [] for m in self.mesh_area_names()}
475
+ if mesh_name is not None and attributes_path not in self:
476
+ return []
477
+ attributes = self[attributes_path][()]
478
+ v_conv_str = np.vectorize(convert_ras_hdf_string)
479
+ names = np.vectorize(convert_ras_hdf_string)(attributes["Name"])
480
+ if mesh_name is not None:
481
+ return names[v_conv_str(attributes[sa_2d_field]) == mesh_name].tolist()
482
+ mesh_names = np.vectorize(convert_ras_hdf_string)(attributes[sa_2d_field])
483
+ return {m: names[mesh_names == m].tolist() for m in np.unique(mesh_names)}
484
+
485
+ def reference_lines_names(
486
+ self, mesh_name: Optional[str] = None
487
+ ) -> Union[Dict[str, List[str]], List[str]]:
488
+ """Return reference line names.
489
+
490
+ If a mesh name is provided, return a list of the reference line names for that mesh area.
491
+ If no mesh name is provided, return a dictionary of mesh names and their reference line names.
492
+
493
+ Parameters
494
+ ----------
495
+ mesh_name : str, optional
496
+ The name of the mesh area for which to return reference line names.
497
+
498
+ Returns
499
+ -------
500
+ Union[Dict[str, List[str]], List[str]]
501
+ A dictionary of mesh names and their reference line names if mesh_name is None.
502
+ A list of reference line names for the specified mesh area if mesh_name is not None.
503
+ """
504
+ return self._reference_lines_points_names("lines", mesh_name)
505
+
506
+ def reference_points_names(
507
+ self, mesh_name: Optional[str] = None
508
+ ) -> Union[Dict[str, List[str]], List[str]]:
509
+ """Return reference point names.
510
+
511
+ If a mesh name is provided, return a list of the reference point names for that mesh area.
512
+ If no mesh name is provided, return a dictionary of mesh names and their reference point names.
513
+
514
+ Parameters
515
+ ----------
516
+ mesh_name : str, optional
517
+ The name of the mesh area for which to return reference point names.
518
+
519
+ Returns
520
+ -------
521
+ Union[Dict[str, List[str]], List[str]]
522
+ A dictionary of mesh names and their reference point names if mesh_name is None.
523
+ A list of reference point names for the specified mesh area if mesh_name is not None.
524
+ """
525
+ return self._reference_lines_points_names("points", mesh_name)
526
+
527
+ def reference_lines(self) -> GeoDataFrame:
528
+ """Return the reference lines geometry and attributes.
529
+
530
+ Returns
531
+ -------
532
+ GeoDataFrame
533
+ A GeoDataFrame containing the reference lines if they exist.
534
+ """
535
+ attributes_path = f"{self.REFERENCE_LINES_PATH}/Attributes"
536
+ if attributes_path not in self:
537
+ return GeoDataFrame()
538
+ attributes = self[attributes_path][()]
539
+ refline_ids = range(attributes.shape[0])
540
+ v_conv_str = np.vectorize(convert_ras_hdf_string)
541
+ names = v_conv_str(attributes["Name"])
542
+ mesh_names = v_conv_str(attributes["SA-2D"])
543
+ try:
544
+ types = v_conv_str(attributes["Type"])
545
+ except ValueError:
546
+ # "Type" field doesn't exist -- observed in some RAS HDF files
547
+ types = np.array([""] * attributes.shape[0])
548
+ geoms = self._get_polylines(self.REFERENCE_LINES_PATH)
549
+ return GeoDataFrame(
550
+ {
551
+ "refln_id": refline_ids,
552
+ "refln_name": names,
553
+ "mesh_name": mesh_names,
554
+ "type": types,
555
+ "geometry": geoms,
556
+ },
557
+ geometry="geometry",
558
+ crs=self.projection(),
559
+ )
560
+
561
+ def reference_points(self) -> GeoDataFrame:
562
+ """Return the reference points geometry and attributes.
563
+
564
+ Returns
565
+ -------
566
+ GeoDataFrame
567
+ A GeoDataFrame containing the reference points if they exist.
568
+ """
569
+ attributes_path = f"{self.REFERENCE_POINTS_PATH}/Attributes"
570
+ if attributes_path not in self:
571
+ return GeoDataFrame()
572
+ ref_points_group = self[self.REFERENCE_POINTS_PATH]
573
+ attributes = ref_points_group["Attributes"][:]
574
+ v_conv_str = np.vectorize(convert_ras_hdf_string)
575
+ names = v_conv_str(attributes["Name"])
576
+ mesh_names = v_conv_str(attributes["SA/2D"])
577
+ cell_id = attributes["Cell Index"]
578
+ points = ref_points_group["Points"][()]
579
+ return GeoDataFrame(
580
+ {
581
+ "refpt_id": range(attributes.shape[0]),
582
+ "refpt_name": names,
583
+ "mesh_name": mesh_names,
584
+ "cell_id": cell_id,
585
+ "geometry": list(map(Point, points)),
586
+ },
587
+ geometry="geometry",
588
+ crs=self.projection(),
589
+ )
455
590
 
456
591
  def pump_stations(self) -> GeoDataFrame: # noqa D102
457
592
  raise NotImplementedError
@@ -473,33 +608,17 @@ class RasGeomHdf(RasHdf):
473
608
  GeoDataFrame
474
609
  A GeoDataFrame containing the model 1D cross sections if they exist.
475
610
  """
476
- if "/Geometry/Cross Sections" not in self:
611
+ if self.CROSS_SECTIONS not in self:
477
612
  return GeoDataFrame()
478
613
 
479
- xs_data = self["/Geometry/Cross Sections"]
614
+ xs_data = self[self.CROSS_SECTIONS]
480
615
  v_conv_val = np.vectorize(convert_ras_hdf_value)
481
616
  xs_attrs = xs_data["Attributes"][()]
482
617
  xs_dict = {"xs_id": range(xs_attrs.shape[0])}
483
618
  xs_dict.update(
484
619
  {name: v_conv_val(xs_attrs[name]) for name in xs_attrs.dtype.names}
485
620
  )
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
- )
621
+ geoms = self._get_polylines(self.CROSS_SECTIONS)
503
622
  xs_gdf = GeoDataFrame(
504
623
  xs_dict,
505
624
  geometry=geoms,
@@ -519,10 +638,10 @@ class RasGeomHdf(RasHdf):
519
638
  GeoDataFrame
520
639
  A GeoDataFrame containing the model 1D river reach lines if they exist.
521
640
  """
522
- if "/Geometry/River Centerlines" not in self:
641
+ if self.RIVER_CENTERLINES not in self:
523
642
  return GeoDataFrame()
524
643
 
525
- river_data = self["/Geometry/River Centerlines"]
644
+ river_data = self[self.RIVER_CENTERLINES]
526
645
  v_conv_val = np.vectorize(convert_ras_hdf_value)
527
646
  river_attrs = river_data["Attributes"][()]
528
647
  river_dict = {"river_id": range(river_attrs.shape[0])}
@@ -530,22 +649,7 @@ class RasGeomHdf(RasHdf):
530
649
  {name: v_conv_val(river_attrs[name]) for name in river_attrs.dtype.names}
531
650
  )
532
651
  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
- )
652
+ geoms = self._get_polylines(self.RIVER_CENTERLINES)
549
653
  river_gdf = GeoDataFrame(
550
654
  river_dict,
551
655
  geometry=geoms,
rashdf/plan.py CHANGED
@@ -5,6 +5,7 @@ from .utils import (
5
5
  df_datetimes_to_str,
6
6
  ras_timesteps_to_datetimes,
7
7
  parse_ras_datetime_ms,
8
+ deprecated,
8
9
  )
9
10
 
10
11
  from geopandas import GeoDataFrame
@@ -163,6 +164,8 @@ class RasPlanHdf(RasGeomHdf):
163
164
  f"{BASE_OUTPUT_PATH}/Summary Output/2D Flow Areas"
164
165
  )
165
166
  UNSTEADY_TIME_SERIES_PATH = f"{BASE_OUTPUT_PATH}/Unsteady Time Series"
167
+ REFERENCE_LINES_OUTPUT_PATH = f"{UNSTEADY_TIME_SERIES_PATH}/Reference Lines"
168
+ REFERENCE_POINTS_OUTPUT_PATH = f"{UNSTEADY_TIME_SERIES_PATH}/Reference Points"
166
169
 
167
170
  RESULTS_STEADY_PATH = "Results/Steady"
168
171
  BASE_STEADY_PATH = f"{RESULTS_STEADY_PATH}/Output/Output Blocks/Base Output"
@@ -583,7 +586,8 @@ class RasPlanHdf(RasGeomHdf):
583
586
  Returns
584
587
  -------
585
588
  DataFrame
586
- A DataFrame with columns 'mesh_name', 'cell_id' or 'face_id', a value column, and a time column.
589
+ A DataFrame with columns 'mesh_name', 'cell_id' or 'face_id', a value column,
590
+ and a time column if the value corresponds to a specific time.
587
591
  """
588
592
  methods_with_times = {
589
593
  SummaryOutputVar.MAXIMUM_WATER_SURFACE: self.mesh_max_ws,
@@ -602,6 +606,76 @@ class RasPlanHdf(RasGeomHdf):
602
606
  df = other_methods[var]()
603
607
  return df
604
608
 
609
+ def _mesh_summary_outputs_df(
610
+ self,
611
+ cells_or_faces: str,
612
+ output_vars: Optional[List[SummaryOutputVar]] = None,
613
+ round_to: str = "0.1 s",
614
+ ) -> DataFrame:
615
+ if cells_or_faces == "cells":
616
+ feature_id_field = "cell_id"
617
+ elif cells_or_faces == "faces":
618
+ feature_id_field = "face_id"
619
+ else:
620
+ raise ValueError('cells_or_faces must be either "cells" or "faces".')
621
+ if output_vars is None:
622
+ summary_output_vars = self._summary_output_vars(
623
+ cells_or_faces=cells_or_faces
624
+ )
625
+ elif isinstance(output_vars, list):
626
+ summary_output_vars = []
627
+ for var in output_vars:
628
+ if not isinstance(var, SummaryOutputVar):
629
+ var = SummaryOutputVar(var)
630
+ summary_output_vars.append(var)
631
+ else:
632
+ raise ValueError(
633
+ "include_output must be a boolean or a list of SummaryOutputVar values."
634
+ )
635
+ df = self.mesh_summary_output(summary_output_vars[0], round_to=round_to)
636
+ for var in summary_output_vars[1:]:
637
+ df_var = self.mesh_summary_output(var, round_to=round_to)
638
+ df = df.merge(df_var, on=["mesh_name", feature_id_field], how="left")
639
+ return df
640
+
641
+ def mesh_cells_summary_output(self, round_to: str = "0.1 s") -> DataFrame:
642
+ """
643
+ Return a DataFrame with summary output data for each mesh cell in the model.
644
+
645
+ Parameters
646
+ ----------
647
+ round_to : str, optional
648
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
649
+ See Pandas documentation for valid time units:
650
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
651
+
652
+ Returns
653
+ -------
654
+ DataFrame
655
+ A DataFrame with columns 'mesh_name', 'cell_id', and columns for each
656
+ summary output variable.
657
+ """
658
+ return self._mesh_summary_outputs_df("cells", round_to=round_to)
659
+
660
+ def mesh_faces_summary_output(self, round_to: str = "0.1 s") -> DataFrame:
661
+ """
662
+ Return a DataFrame with summary output data for each mesh face in the model.
663
+
664
+ Parameters
665
+ ----------
666
+ round_to : str, optional
667
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
668
+ See Pandas documentation for valid time units:
669
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
670
+
671
+ Returns
672
+ -------
673
+ DataFrame
674
+ A DataFrame with columns 'mesh_name', 'face_id', and columns for each
675
+ summary output variable.
676
+ """
677
+ return self._mesh_summary_outputs_df("faces", round_to=round_to)
678
+
605
679
  def _summary_output_vars(
606
680
  self, cells_or_faces: Optional[str] = None
607
681
  ) -> List[SummaryOutputVar]:
@@ -810,7 +884,7 @@ class RasPlanHdf(RasGeomHdf):
810
884
  mesh_name: str,
811
885
  var: TimeSeriesOutputVar,
812
886
  ) -> Tuple[np.ndarray, str]:
813
- path = f"{self.UNSTEADY_TIME_SERIES_PATH}/2D Flow Areas/{mesh_name}/{var.value}"
887
+ path = self._mesh_timeseries_output_path(mesh_name, var.value)
814
888
  group = self.get(path)
815
889
  try:
816
890
  import dask.array as da
@@ -828,6 +902,7 @@ class RasPlanHdf(RasGeomHdf):
828
902
  self,
829
903
  mesh_name: str,
830
904
  var: Union[str, TimeSeriesOutputVar],
905
+ truncate: bool = True,
831
906
  ) -> xr.DataArray:
832
907
  """Return the time series output data for a given variable.
833
908
 
@@ -837,6 +912,8 @@ class RasPlanHdf(RasGeomHdf):
837
912
  The name of the 2D flow area mesh.
838
913
  var : TimeSeriesOutputVar
839
914
  The time series output variable to retrieve.
915
+ truncate : bool, optional
916
+ If True, truncate the number of cells to the listed cell count.
840
917
 
841
918
  Returns
842
919
  -------
@@ -854,7 +931,10 @@ class RasPlanHdf(RasGeomHdf):
854
931
  values, units = self._mesh_timeseries_output_values_units(mesh_name, var)
855
932
  if var in TIME_SERIES_OUTPUT_VARS_CELLS:
856
933
  cell_count = mesh_names_counts[mesh_name]
857
- values = values[:, :cell_count]
934
+ if truncate:
935
+ values = values[:, :cell_count]
936
+ else:
937
+ values = values[:, :]
858
938
  id_coord = "cell_id"
859
939
  elif var in TIME_SERIES_OUTPUT_VARS_FACES:
860
940
  id_coord = "face_id"
@@ -872,24 +952,28 @@ class RasPlanHdf(RasGeomHdf):
872
952
  "mesh_name": mesh_name,
873
953
  "variable": var.value,
874
954
  "units": units,
955
+ "hdf_path": self._mesh_timeseries_output_path(mesh_name, var.value),
875
956
  },
876
957
  )
877
958
  return da
878
959
 
960
+ def _mesh_timeseries_output_path(self, mesh_name: str, var_name: str) -> str:
961
+ return f"{self.UNSTEADY_TIME_SERIES_PATH}/2D Flow Areas/{mesh_name}/{var_name}"
962
+
879
963
  def _mesh_timeseries_outputs(
880
- self, mesh_name: str, vars: List[TimeSeriesOutputVar]
964
+ self, mesh_name: str, vars: List[TimeSeriesOutputVar], truncate: bool = True
881
965
  ) -> xr.Dataset:
882
966
  datasets = {}
883
967
  for var in vars:
884
968
  var_path = f"{self.UNSTEADY_TIME_SERIES_PATH}/2D Flow Areas/{mesh_name}/{var.value}"
885
969
  if self.get(var_path) is None:
886
970
  continue
887
- da = self.mesh_timeseries_output(mesh_name, var)
971
+ da = self.mesh_timeseries_output(mesh_name, var, truncate=truncate)
888
972
  datasets[var.value] = da
889
973
  ds = xr.Dataset(datasets, attrs={"mesh_name": mesh_name})
890
974
  return ds
891
975
 
892
- def mesh_timeseries_output_cells(self, mesh_name: str) -> xr.Dataset:
976
+ def mesh_cells_timeseries_output(self, mesh_name: str) -> xr.Dataset:
893
977
  """Return the time series output data for cells in a 2D flow area mesh.
894
978
 
895
979
  Parameters
@@ -905,7 +989,25 @@ class RasPlanHdf(RasGeomHdf):
905
989
  ds = self._mesh_timeseries_outputs(mesh_name, TIME_SERIES_OUTPUT_VARS_CELLS)
906
990
  return ds
907
991
 
908
- def mesh_timeseries_output_faces(self, mesh_name: str) -> xr.Dataset:
992
+ @deprecated
993
+ def mesh_timeseries_output_cells(self, mesh_name: str) -> xr.Dataset:
994
+ """Return the time series output data for cells in a 2D flow area mesh.
995
+
996
+ Deprecated: use mesh_cells_timeseries_output instead.
997
+
998
+ Parameters
999
+ ----------
1000
+ mesh_name : str
1001
+ The name of the 2D flow area mesh.
1002
+
1003
+ Returns
1004
+ -------
1005
+ xr.Dataset
1006
+ An xarray Dataset with DataArrays for each time series output variable.
1007
+ """
1008
+ return self.mesh_cells_timeseries_output(mesh_name)
1009
+
1010
+ def mesh_faces_timeseries_output(self, mesh_name: str) -> xr.Dataset:
909
1011
  """Return the time series output data for faces in a 2D flow area mesh.
910
1012
 
911
1013
  Parameters
@@ -921,6 +1023,232 @@ class RasPlanHdf(RasGeomHdf):
921
1023
  ds = self._mesh_timeseries_outputs(mesh_name, TIME_SERIES_OUTPUT_VARS_FACES)
922
1024
  return ds
923
1025
 
1026
+ @deprecated
1027
+ def mesh_timeseries_output_faces(self, mesh_name: str) -> xr.Dataset:
1028
+ """Return the time series output data for faces in a 2D flow area mesh.
1029
+
1030
+ Deprecated: use mesh_faces_timeseries_output instead.
1031
+
1032
+ Parameters
1033
+ ----------
1034
+ mesh_name : str
1035
+ The name of the 2D flow area mesh.
1036
+
1037
+ Returns
1038
+ -------
1039
+ xr.Dataset
1040
+ An xarray Dataset with DataArrays for each time series output variable.
1041
+ """
1042
+ return self.mesh_faces_timeseries_output(mesh_name)
1043
+
1044
+ def reference_timeseries_output(self, reftype: str = "lines") -> xr.Dataset:
1045
+ """Return timeseries output data for reference lines or points from a HEC-RAS HDF plan file.
1046
+
1047
+ Parameters
1048
+ ----------
1049
+ reftype : str, optional
1050
+ The type of reference data to retrieve. Must be either "lines" or "points".
1051
+ (default: "lines")
1052
+
1053
+ Returns
1054
+ -------
1055
+ xr.Dataset
1056
+ An xarray Dataset with reference line timeseries data.
1057
+ """
1058
+ if reftype == "lines":
1059
+ output_path = self.REFERENCE_LINES_OUTPUT_PATH
1060
+ abbrev = "refln"
1061
+ elif reftype == "points":
1062
+ output_path = self.REFERENCE_POINTS_OUTPUT_PATH
1063
+ abbrev = "refpt"
1064
+ else:
1065
+ raise ValueError('reftype must be either "lines" or "points".')
1066
+ reference_group = self.get(output_path)
1067
+ if reference_group is None:
1068
+ raise RasPlanHdfError(
1069
+ f"Could not find HDF group at path '{output_path}'."
1070
+ f" Does the Plan HDF file contain reference {reftype[:-1]} output data?"
1071
+ )
1072
+ reference_names = reference_group["Name"][:]
1073
+ names = []
1074
+ mesh_areas = []
1075
+ for s in reference_names:
1076
+ name, mesh_area = s.decode("utf-8").split("|")
1077
+ names.append(name)
1078
+ mesh_areas.append(mesh_area)
1079
+
1080
+ times = self.unsteady_datetimes()
1081
+
1082
+ das = {}
1083
+ for var in ["Flow", "Velocity", "Water Surface"]:
1084
+ group = reference_group.get(var)
1085
+ if group is None:
1086
+ continue
1087
+ try:
1088
+ import dask.array as da
1089
+
1090
+ # TODO: user-specified chunks?
1091
+ values = da.from_array(group, chunks=group.chunks)
1092
+ except ImportError:
1093
+ values = group[:]
1094
+ units = group.attrs["Units"].decode("utf-8")
1095
+ da = xr.DataArray(
1096
+ values,
1097
+ name=var,
1098
+ dims=["time", f"{abbrev}_id"],
1099
+ coords={
1100
+ "time": times,
1101
+ f"{abbrev}_id": range(values.shape[1]),
1102
+ f"{abbrev}_name": (f"{abbrev}_id", names),
1103
+ "mesh_name": (f"{abbrev}_id", mesh_areas),
1104
+ },
1105
+ attrs={"units": units, "hdf_path": f"{output_path}/{var}"},
1106
+ )
1107
+ das[var] = da
1108
+ return xr.Dataset(das)
1109
+
1110
+ def reference_lines_timeseries_output(self) -> xr.Dataset:
1111
+ """Return timeseries output data for reference lines from a HEC-RAS HDF plan file.
1112
+
1113
+ Returns
1114
+ -------
1115
+ xr.Dataset
1116
+ An xarray Dataset with timeseries output data for reference lines.
1117
+ """
1118
+ return self.reference_timeseries_output(reftype="lines")
1119
+
1120
+ def reference_points_timeseries_output(self) -> xr.Dataset:
1121
+ """Return timeseries output data for reference points from a HEC-RAS HDF plan file.
1122
+
1123
+ Returns
1124
+ -------
1125
+ xr.Dataset
1126
+ An xarray Dataset with timeseries output data for reference points.
1127
+ """
1128
+ return self.reference_timeseries_output(reftype="points")
1129
+
1130
+ def reference_summary_output(self, reftype: str = "lines") -> DataFrame:
1131
+ """Return summary output data for reference lines or points from a HEC-RAS HDF plan file.
1132
+
1133
+ Returns
1134
+ -------
1135
+ DataFrame
1136
+ A DataFrame with reference line summary output data.
1137
+ """
1138
+ if reftype == "lines":
1139
+ abbrev = "refln"
1140
+ elif reftype == "points":
1141
+ abbrev = "refpt"
1142
+ else:
1143
+ raise ValueError('reftype must be either "lines" or "points".')
1144
+ ds = self.reference_timeseries_output(reftype=reftype)
1145
+ result = {
1146
+ f"{abbrev}_id": ds[f"{abbrev}_id"],
1147
+ f"{abbrev}_name": ds[f"{abbrev}_name"],
1148
+ "mesh_name": ds.mesh_name,
1149
+ }
1150
+ vars = {
1151
+ "Flow": "q",
1152
+ "Water Surface": "ws",
1153
+ "Velocity": "v",
1154
+ }
1155
+ for var, abbrev in vars.items():
1156
+ if var not in ds:
1157
+ continue
1158
+ max_var = ds[var].max(dim="time")
1159
+ max_time = ds[var].time[ds[var].argmax(dim="time")]
1160
+ min_var = ds[var].min(dim="time")
1161
+ min_time = ds[var].time[ds[var].argmin(dim="time")]
1162
+ result[f"max_{abbrev}"] = max_var
1163
+ result[f"max_{abbrev}_time"] = max_time
1164
+ result[f"min_{abbrev}"] = min_var
1165
+ result[f"min_{abbrev}_time"] = min_time
1166
+ return DataFrame(result)
1167
+
1168
+ def _reference_lines_points(
1169
+ self,
1170
+ reftype: str = "lines",
1171
+ include_output: bool = True,
1172
+ datetime_to_str: bool = False,
1173
+ ) -> GeoDataFrame:
1174
+ if reftype == "lines":
1175
+ abbrev = "refln"
1176
+ gdf = super().reference_lines()
1177
+ elif reftype == "points":
1178
+ abbrev = "refpt"
1179
+ gdf = super().reference_points()
1180
+ else:
1181
+ raise ValueError('reftype must be either "lines" or "points".')
1182
+ if include_output is False:
1183
+ return gdf
1184
+ summary_output = self.reference_summary_output(reftype=reftype)
1185
+ gdf = gdf.merge(
1186
+ summary_output,
1187
+ on=[f"{abbrev}_id", f"{abbrev}_name", "mesh_name"],
1188
+ how="left",
1189
+ )
1190
+ if datetime_to_str:
1191
+ gdf = df_datetimes_to_str(gdf)
1192
+ return gdf
1193
+
1194
+ def reference_lines(
1195
+ self, include_output: bool = True, datetime_to_str: bool = False
1196
+ ) -> GeoDataFrame:
1197
+ """Return the reference lines from a HEC-RAS HDF plan file.
1198
+
1199
+ Includes summary output data for each reference line:
1200
+ - Maximum flow & time (max_q, max_q_time)
1201
+ - Minimum flow & time (min_q, min_q_time)
1202
+ - Maximum water surface elevation & time (max_ws, max_ws_time)
1203
+ - Minimum water surface elevation & time (min_ws, min_ws_time)
1204
+
1205
+ Parameters
1206
+ ----------
1207
+ include_output : bool, optional
1208
+ If True, include summary output data in the GeoDataFrame. (default: True)
1209
+ datetime_to_str : bool, optional
1210
+ If True, convert datetime columns to strings. (default: False)
1211
+
1212
+ Returns
1213
+ -------
1214
+ GeoDataFrame
1215
+ A GeoDataFrame with reference line geometry and summary output data.
1216
+ """
1217
+ return self._reference_lines_points(
1218
+ reftype="lines",
1219
+ include_output=include_output,
1220
+ datetime_to_str=datetime_to_str,
1221
+ )
1222
+
1223
+ def reference_points(
1224
+ self, include_output: bool = True, datetime_to_str: bool = False
1225
+ ) -> GeoDataFrame:
1226
+ """Return the reference points from a HEC-RAS HDF plan file.
1227
+
1228
+ Parameters
1229
+ ----------
1230
+ include_output : bool, optional
1231
+ If True, include summary output data in the GeoDataFrame. (default: True)
1232
+ datetime_to_str : bool, optional
1233
+ If True, convert datetime columns to strings. (default: False)
1234
+
1235
+ Includes summary output data for each reference point:
1236
+ - Maximum flow & time (max_q, max_q_time)
1237
+ - Minimum flow & time (min_q, min_q_time)
1238
+ - Maximum water surface elevation & time (max_ws, max_ws_time)
1239
+ - Minimum water surface elevation & time (min_ws, min_ws_time)
1240
+
1241
+ Returns
1242
+ -------
1243
+ GeoDataFrame
1244
+ A GeoDataFrame with reference point geometry and summary output data.
1245
+ """
1246
+ return self._reference_lines_points(
1247
+ reftype="points",
1248
+ include_output=include_output,
1249
+ datetime_to_str=datetime_to_str,
1250
+ )
1251
+
924
1252
  def get_plan_info_attrs(self) -> Dict:
925
1253
  """Return plan information attributes from a HEC-RAS HDF plan file.
926
1254
 
@@ -1107,3 +1435,113 @@ class RasPlanHdf(RasGeomHdf):
1107
1435
  A DataFrame containing the velocity inside the cross sections
1108
1436
  """
1109
1437
  return self.steady_profile_xs_output(XsSteadyOutputVar.VELOCITY_TOTAL)
1438
+
1439
+ def _zmeta(self, ds: xr.Dataset) -> Dict:
1440
+ """Given a xarray Dataset, return kerchunk-style zarr reference metadata."""
1441
+ from kerchunk.hdf import SingleHdf5ToZarr
1442
+ import zarr
1443
+ import base64
1444
+
1445
+ encoding = {}
1446
+ chunk_meta = {}
1447
+
1448
+ # Loop through each variable / DataArray in the Dataset
1449
+ for var, da in ds.data_vars.items():
1450
+ # The "hdf_path" attribute is the path within the HDF5 file
1451
+ # that the DataArray was read from. This is attribute is inserted
1452
+ # by rashdf (see "mesh_timeseries_output" method).
1453
+ hdf_ds_path = da.attrs["hdf_path"]
1454
+ hdf_ds = self.get(hdf_ds_path)
1455
+ if hdf_ds is None:
1456
+ # If we don't know where in the HDF5 the data came from, we
1457
+ # have to skip it, because we won't be able to generate the
1458
+ # correct metadata for it.
1459
+ continue
1460
+ # Get the filters and storage info for the HDF5 dataset.
1461
+ # Calling private methods from Kerchunk here because
1462
+ # there's not a nice public API for this part. This is hacky
1463
+ # and a bit risky because these private methods are more likely
1464
+ # to change, but short of reimplementing these functions ourselves
1465
+ # it's the best way to get the metadata we need.
1466
+ # TODO: raise an issue in Kerchunk to expose this functionality?
1467
+ filters = SingleHdf5ToZarr._decode_filters(None, hdf_ds)
1468
+ encoding[var] = {"compressor": None, "filters": filters}
1469
+ storage_info = SingleHdf5ToZarr._storage_info(None, hdf_ds)
1470
+ # Generate chunk metadata for the DataArray
1471
+ for key, value in storage_info.items():
1472
+ chunk_number = ".".join([str(k) for k in key])
1473
+ chunk_key = f"{var}/{chunk_number}"
1474
+ chunk_meta[chunk_key] = [str(self._loc), value["offset"], value["size"]]
1475
+ # "Write" the Dataset to a temporary in-memory zarr store (which
1476
+ # is the same a Python dictionary)
1477
+ zarr_tmp = zarr.MemoryStore()
1478
+ # Use compute=False here because we don't _actually_ want to write
1479
+ # the data to the zarr store, we just want to generate the metadata.
1480
+ ds.to_zarr(zarr_tmp, mode="w", compute=False, encoding=encoding)
1481
+ zarr_meta = {"version": 1, "refs": {}}
1482
+ # Loop through the in-memory Zarr store, decode the data to strings,
1483
+ # and add it to the final metadata dictionary.
1484
+ for key, value in zarr_tmp.items():
1485
+ try:
1486
+ value_str = value.decode("utf-8")
1487
+ except UnicodeDecodeError:
1488
+ value_str = "base64:" + base64.b64encode(value).decode("utf-8")
1489
+ zarr_meta["refs"][key] = value_str
1490
+ zarr_meta["refs"].update(chunk_meta)
1491
+ return zarr_meta
1492
+
1493
+ def zmeta_mesh_cells_timeseries_output(self, mesh_name: str) -> Dict:
1494
+ """Return kerchunk-style zarr reference metadata.
1495
+
1496
+ Requires the 'zarr' and 'kerchunk' packages.
1497
+
1498
+ Returns
1499
+ -------
1500
+ dict
1501
+ Dictionary of kerchunk-style zarr reference metadata.
1502
+ """
1503
+ ds = self._mesh_timeseries_outputs(
1504
+ mesh_name, TIME_SERIES_OUTPUT_VARS_CELLS, truncate=False
1505
+ )
1506
+ return self._zmeta(ds)
1507
+
1508
+ def zmeta_mesh_faces_timeseries_output(self, mesh_name: str) -> Dict:
1509
+ """Return kerchunk-style zarr reference metadata.
1510
+
1511
+ Requires the 'zarr' and 'kerchunk' packages.
1512
+
1513
+ Returns
1514
+ -------
1515
+ dict
1516
+ Dictionary of kerchunk-style zarr reference metadata.
1517
+ """
1518
+ ds = self._mesh_timeseries_outputs(
1519
+ mesh_name, TIME_SERIES_OUTPUT_VARS_FACES, truncate=False
1520
+ )
1521
+ return self._zmeta(ds)
1522
+
1523
+ def zmeta_reference_lines_timeseries_output(self) -> Dict:
1524
+ """Return kerchunk-style zarr reference metadata.
1525
+
1526
+ Requires the 'zarr' and 'kerchunk' packages.
1527
+
1528
+ Returns
1529
+ -------
1530
+ dict
1531
+ Dictionary of kerchunk-style zarr reference metadata.
1532
+ """
1533
+ ds = self.reference_lines_timeseries_output()
1534
+ return self._zmeta(ds)
1535
+
1536
+ def zmeta_reference_points_timeseries_output(self) -> Dict:
1537
+ """Return kerchunk-style zarr reference metadata.
1538
+
1539
+ Requires the 'zarr' and 'kerchunk' packages.
1540
+
1541
+ Returns
1542
+ -------
1543
+ dict
1544
+ Dictionary of kerchunk-style zarr reference metadata.
1545
+ """
1546
+ ds = self.reference_points_timeseries_output()
1547
+ return self._zmeta(ds)
rashdf/utils.py CHANGED
@@ -6,8 +6,8 @@ import pandas as pd
6
6
 
7
7
  from datetime import datetime, timedelta
8
8
  import re
9
- from typing import Any, List, Tuple, Union, Optional
10
- from shapely import LineString, Polygon, polygonize_full
9
+ from typing import Any, Callable, List, Tuple, Union, Optional
10
+ import warnings
11
11
 
12
12
 
13
13
  def parse_ras_datetime_ms(datetime_str: str) -> datetime:
@@ -308,3 +308,33 @@ def ras_timesteps_to_datetimes(
308
308
  start_time + pd.Timedelta(timestep, unit=time_unit).round(round_to)
309
309
  for timestep in timesteps.astype(np.float64)
310
310
  ]
311
+
312
+
313
+ def deprecated(func) -> Callable:
314
+ """
315
+ Deprecate a function.
316
+
317
+ This is a decorator which can be used to mark functions as deprecated.
318
+ It will result in a warning being emitted when the function is used.
319
+
320
+ Parameters
321
+ ----------
322
+ func: The function to be deprecated.
323
+
324
+ Returns
325
+ -------
326
+ The decorated function.
327
+ """
328
+
329
+ def new_func(*args, **kwargs):
330
+ warnings.warn(
331
+ f"{func.__name__} is deprecated and will be removed in a future version.",
332
+ category=DeprecationWarning,
333
+ stacklevel=2,
334
+ )
335
+ return func(*args, **kwargs)
336
+
337
+ new_func.__name__ = func.__name__
338
+ new_func.__doc__ = func.__doc__
339
+ new_func.__dict__.update(func.__dict__)
340
+ return new_func
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rashdf
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
16
  Requires-Dist: h5py
17
- Requires-Dist: geopandas <0.15,>=0.14
17
+ Requires-Dist: geopandas <2.0,>=1.0
18
18
  Requires-Dist: pyarrow
19
19
  Requires-Dist: xarray
20
20
  Provides-Extra: dev
@@ -22,6 +22,12 @@ Requires-Dist: pre-commit ; extra == 'dev'
22
22
  Requires-Dist: ruff ; extra == 'dev'
23
23
  Requires-Dist: pytest ; extra == 'dev'
24
24
  Requires-Dist: pytest-cov ; extra == 'dev'
25
+ Requires-Dist: fiona ; extra == 'dev'
26
+ Requires-Dist: kerchunk ; extra == 'dev'
27
+ Requires-Dist: zarr ; extra == 'dev'
28
+ Requires-Dist: dask ; extra == 'dev'
29
+ Requires-Dist: fsspec ; extra == 'dev'
30
+ Requires-Dist: s3fs ; extra == 'dev'
25
31
  Provides-Extra: docs
26
32
  Requires-Dist: sphinx ; extra == 'docs'
27
33
  Requires-Dist: numpydoc ; extra == 'docs'
@@ -76,8 +82,8 @@ Also, methods to extract certain HDF group attributes as dictionaries:
76
82
  ```python
77
83
  >>> from rashdf import RasPlanHdf
78
84
  >>> with RasPlanHdf("path/to/rasmodel/Muncie.p04.hdf") as plan_hdf:
79
- >>> results_unsteady_summary = plan_hdf.get_results_unsteady_summary()
80
- >>> results_unsteady_summary
85
+ >>> results_unsteady_summary_attrs = plan_hdf.get_results_unsteady_summary_attrs()
86
+ >>> results_unsteady_summary_attrs
81
87
  {'Computation Time DSS': datetime.timedelta(0),
82
88
  'Computation Time Total': datetime.timedelta(seconds=23),
83
89
  'Maximum WSEL Error': 0.0099277812987566,
@@ -101,9 +107,9 @@ CLI help:
101
107
  $ rashdf --help
102
108
  ```
103
109
 
104
- Print the output formats supported by Fiona:
110
+ Print the output formats supported by pyorgio:
105
111
  ```
106
- $ rashdf --fiona-drivers
112
+ $ rashdf --pyogrio-drivers
107
113
  ```
108
114
 
109
115
  Help for a specific subcommand:
@@ -0,0 +1,12 @@
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=4kftqnZedhSWPl-5Yn3vz9Z4VifTXcUokti0s5lX1lU,56479
6
+ rashdf/utils.py,sha256=Cba6sULF0m0jg6CQass4bPm2oxTd_avoe1pRQxq082c,10896
7
+ rashdf-0.6.0.dist-info/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
+ rashdf-0.6.0.dist-info/METADATA,sha256=0MarTKZArGaOTTROyz4PENscSiVy7cYwvatftl89y_Q,5920
9
+ rashdf-0.6.0.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
10
+ rashdf-0.6.0.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
+ rashdf-0.6.0.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
+ rashdf-0.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (71.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- cli.py,sha256=dnTMEBid99xqorBFKZnUwOTDyTmIg08D83bSCkJ6104,5389
2
- rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
3
- rashdf/base.py,sha256=lHYVDwFTA1qFI34QYZ55QKcp7b8CeZsmDfESdkYISbg,2432
4
- rashdf/geom.py,sha256=z3ak4TYjYo8-jrIQNSU96S7ulX5xk67rFZ5J0Y9yKbI,22048
5
- rashdf/plan.py,sha256=YfJdjzZmGA9X8QnUflkc-8DMzMGoW5Gda3lRgMfxeQc,39669
6
- rashdf/utils.py,sha256=93arHtIT-iL9dIpbYr7esjrxv1uJabTRJSruyjvr8mw,10168
7
- rashdf-0.4.0.dist-info/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
- rashdf-0.4.0.dist-info/METADATA,sha256=x7h2Pvw_xTQmzOhJKWkj6-R1lzCp_gDBt1wIkBikTZQ,5671
9
- rashdf-0.4.0.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
10
- rashdf-0.4.0.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
- rashdf-0.4.0.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
- rashdf-0.4.0.dist-info/RECORD,,