Skip to content
This repository was archived by the owner on Feb 26, 2026. It is now read-only.

Commit 178201f

Browse files
authored
Merge pull request #337 from Dewberry/release/0.10.1
Bugfix Release 0.10.1
2 parents 97979d2 + 97d6797 commit 178201f

File tree

11 files changed

+71
-60
lines changed

11 files changed

+71
-60
lines changed

docs/source/postman.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ Postman collection
33

44
For reference and documentation of the API, please open the postman collection for the version of ripple1d
55

6+
`v.0.10.1: <https://github.com/Dewberry/ripple1d/blob/58a873910f0dfe312f7d674793470389836aac5b/ripple1d/api/postman_collection.json>`_ This version contains new args for the conflate_model and compute_conflation_metrics endpoints :
7+
- `model_name` (str) added to `extract_submodel`. This is the name of the source model. Example: Red River.prj -> Red River (model_name)
8+
- `terrain_agreement_ignore_error` (bool) added to `create_ras_terrain`. If true, this will log and ignore any errors encountered in the terrain agreement calculation process.
9+
610
`v.0.10.0: <https://github.com/Dewberry/ripple1d/blob/93cf22cf11791d59820635be6c02327b39912b49/ripple1d/api/postman_collection.json>`_ This version contains new args for the conflate_model and compute_conflation_metrics endpoints :
711
- `model_name` (str) added to `conflate_model`. This is the name of the source model. Example: Red River.prj -> Red River (model_name)
812
- `model_name` (str) added to `compute_conflation_metrics`. This is the name of the source model.Example: Red River.prj -> Red River (model_name)

ripple1d/api/postman_collection.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"info": {
3-
"_postman_id": "977032b0-6c0b-4437-9347-7061b817bdae",
3+
"_postman_id": "aae308a3-7a55-4367-a5f4-8728c9165d68",
44
"name": "ripple1d",
55
"description": "Collection for processing existing HEC-RAS models for use in the production of Flood Inundation Maps (FIMs) and rating curves for use in near-real time flood forecasting on the NOAA National Water Model",
66
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7-
"_exporter_id": "29128857"
7+
"_exporter_id": "38126273"
88
},
99
"item": [
1010
{
@@ -75,7 +75,7 @@
7575
"header": [],
7676
"body": {
7777
"mode": "raw",
78-
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"model_name\":\"{{source_model_name}}\",\r\n \"source_network\": {\"file_name\":\"{{nwm_data_directory}}\\\\flows.parquet\",\r\n \"version\":\"2.1\", // optional\r\n \"type\":\"nwm_hydrofabric\"}\r\n \r\n}",
78+
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"model_name\":\"{{source_model_name}}\",\r\n \"source_network\": {\"file_name\":\"{{nwm_data_directory}}\\\\nwm_bbox.parquet\",\r\n \"version\":\"2.1\", // optional\r\n \"type\":\"nwm_hydrofabric\"}\r\n \r\n}",
7979
"options": {
8080
"raw": {
8181
"language": "json"
@@ -104,7 +104,7 @@
104104
"header": [],
105105
"body": {
106106
"mode": "raw",
107-
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"submodel_directory\": \"{{submodels_base_directory}}\\\\{{nwm_reach_id}}\",\r\n \"nwm_id\": \"{{nwm_reach_id}}\"\r\n}",
107+
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"submodel_directory\": \"{{submodels_base_directory}}\\\\{{nwm_reach_id}}\",\r\n \"nwm_id\": \"{{nwm_reach_id}}\",\r\n \"model_name\":\"{{source_model_name}}\"\r\n}",
108108
"options": {
109109
"raw": {
110110
"language": "json"
@@ -133,7 +133,7 @@
133133
"header": [],
134134
"body": {
135135
"mode": "raw",
136-
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"crs\": 2227,\r\n \"metadata\": {\"stac_api\":\"https://stac2.dewberryanalytics.com\", // optional\r\n \"stac_collection_id\":\"ebfe-12090301_LowerColoradoCummins\", // optional\r\n \"stac_item_id\":\"137a9667-e5cf-4cea-b6ec-2e882a42fdc8\"} // optional\r\n}\r\n",
136+
"raw": "{\r\n \"source_model_directory\": \"{{source_model_directory}}\",\r\n \"crs\": 3452,\r\n \"metadata\": {\"stac_api\":\"https://stac2.dewberryanalytics.com\", // optional\r\n \"stac_collection_id\":\"ebfe-12090301_LowerColoradoCummins\", // optional\r\n \"stac_item_id\":\"137a9667-e5cf-4cea-b6ec-2e882a42fdc8\"} // optional\r\n}\r\n",
137137
"options": {
138138
"raw": {
139139
"language": "json"
@@ -162,7 +162,7 @@
162162
"header": [],
163163
"body": {
164164
"mode": "raw",
165-
"raw": "{\r\n \"submodel_directory\": \"{{submodels_base_directory}}\\\\{{nwm_reach_id}}\",\r\n \"resolution\": 3,\r\n \"resolution_units\": \"Meters\",\r\n \"terrain_agreement_resolution\": 3\r\n}",
165+
"raw": "{\r\n \"submodel_directory\": \"{{submodels_base_directory}}\\\\{{nwm_reach_id}}\",\r\n \"resolution\": 3,\r\n \"resolution_units\": \"Meters\",\r\n \"terrain_agreement_resolution\": 3,\r\n \"terrain_agreement_ignore_error\": false\r\n}",
166166
"options": {
167167
"raw": {
168168
"language": "json"

ripple1d/conflate/rasfim.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,6 @@ def load_pq(self, nwm_pq: str):
232232
try:
233233
# read nwm reaches with bbox
234234
nwm_reaches = gpd.read_parquet(nwm_pq, bbox=self._ras_xs.to_crs(self.common_crs).total_bounds)
235-
# select subset of nwm reaches using concave hull of cross sections
236-
cch = gpd.GeoDataFrame(
237-
{
238-
"geometry": [
239-
self._ras_xs.to_crs(self.common_crs).dissolve("river_reach").convex_hull.buffer(100).union_all()
240-
]
241-
},
242-
geometry="geometry",
243-
crs=self.common_crs,
244-
)
245-
nwm_reaches = nwm_reaches.loc[nwm_reaches.intersects(cch.iloc[0].geometry)]
246235

247236
# rename geometry
248237
nwm_reaches = nwm_reaches.rename(columns={"geom": "geometry"})
@@ -468,7 +457,7 @@ def endpoints_from_multiline(mline: MultiLineString) -> Tuple[Point, Point]:
468457

469458

470459
def nearest_line_to_point(
471-
lines: gpd.GeoDataFrame, point: Point, column_id: str = "ID", search_radius: int = 1e9, number_of_returns: int = 1
460+
lines: gpd.GeoDataFrame, point: Point, column_id: str = "ID", search_radius: int = 1e9, number_of_returns: int = 0
472461
) -> np.array:
473462
"""
474463
Return the ID of the line(s) closest to the point.

ripple1d/data_model.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,11 @@ def nwm_conflation_parameters(self, nwm_id: str):
187187
class RippleSourceDirectory:
188188
"""Source Directory for Ripple to create NwmReachModel's. Should contain the conflation.json file and gpkg file for the source model."""
189189

190-
def __init__(self, source_directory: str):
190+
def __init__(self, source_directory: str, model_name: str):
191191

192192
self.source_directory = source_directory
193193
self.model_basename = os.path.basename(self.source_directory)
194-
195-
@property
196-
def model_name(self):
197-
"""Model name."""
198-
return self.model_basename
194+
self.model_name = model_name
199195

200196
def derive_path(self, extension: str):
201197
"""Derive path."""

ripple1d/ops/ras_conflate.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
"""Conflate HEC-RAS Model."""
22

3-
import copy
43
import json
54
import logging
65
import os
76
import traceback
8-
from datetime import datetime
97
from itertools import chain, permutations
108
from urllib.parse import quote
119

12-
import boto3
13-
import pandas as pd
14-
import pystac
15-
1610
import ripple1d
1711
from ripple1d.conflate.rasfim import (
1812
RasFimConflater,
@@ -23,7 +17,7 @@
2317
)
2418
from ripple1d.errors import InvalidNetworkPath
2519
from ripple1d.ops.metrics import compute_conflation_metrics
26-
from ripple1d.utils.ripple_utils import NWMWalker, clip_ras_centerline
20+
from ripple1d.utils.ripple_utils import clip_ras_centerline
2721

2822
logging.getLogger("fiona").setLevel(logging.ERROR)
2923
logging.getLogger("botocore").setLevel(logging.ERROR)
@@ -131,8 +125,8 @@ def conflate_model(source_model_directory: str, model_name: str, source_network:
131125
threshold listed for the reach in the NWM network. The high flow is the
132126
100 year flow from the NWM network.
133127
"""
134-
logging.info(f"conflate_model starting")
135-
if not "file_name" in source_network:
128+
logging.info("conflate_model starting")
129+
if "file_name" not in source_network:
136130
raise KeyError(f"source_network must contain 'file_name', invalid parameters: {source_network}")
137131

138132
if not source_network["type"] == "nwm_hydrofabric":
@@ -152,7 +146,7 @@ def conflate_model(source_model_directory: str, model_name: str, source_network:
152146
logging.error(f"| Error: {e}")
153147
logging.error(f"| Traceback: {traceback.format_exc()}")
154148

155-
logging.info(f"conflate_model complete")
149+
logging.info("conflate_model complete")
156150
return {"conflation_file": conflation_file}
157151

158152

@@ -168,7 +162,7 @@ def _conflate_model(source_model_directory: str, model_name: str, source_network
168162
"metadata": generate_metadata(source_network, rfc),
169163
}
170164
conflation = find_eclipsed_reaches(rfc, conflation)
171-
# conflation = fix_junctions(rfc, conflation)
165+
conflation = fix_junctions(rfc, conflation)
172166
return conflation
173167

174168

@@ -179,7 +173,7 @@ def find_eclipsed_reaches(rfc: RasFimConflater, conflation: dict) -> dict:
179173
try:
180174
reaches = rfc.nwm_walker.walk(us_reach, ds_reach)
181175
for r in reaches:
182-
if not r in [us_reach, ds_reach]:
176+
if r not in [us_reach, ds_reach]:
183177
conflation["reaches"][r] = {"eclipsed": True} | rfc.get_nwm_reach_metadata(r)
184178
except InvalidNetworkPath as e:
185179
logging.error(f"|Error: {e}")

ripple1d/ops/ras_terrain.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import os
88
import re
9+
import traceback
910
from math import ceil, comb, pi
1011
from pathlib import Path
1112

@@ -66,6 +67,7 @@ def create_ras_terrain(
6667
terrain_agreement_el_repeats: int = 5,
6768
terrain_agreement_el_ramp_rate: float = 2.0,
6869
terrain_agreement_el_init: float = 0.5,
70+
terrain_agreement_ignore_error: bool = True,
6971
):
7072
"""Create a RAS terrain file.
7173
@@ -105,6 +107,8 @@ def create_ras_terrain(
105107
21.5, etc.
106108
terrain_agreement_el_init : float, optional
107109
initial value for terrain agreement elevation increments, by default 0.5
110+
terrain_agreement_ignore_error : bool, optional
111+
whether to raise or log+ignore any errors encountered in the compute_terrain_agreement_metrics function.
108112
task_id : str, optional
109113
Task ID to use for logging, by default ""
110114
@@ -195,17 +199,25 @@ def create_ras_terrain(
195199
logging.info(f"create_ras_terrain complete")
196200

197201
# Calculate terrain agreement metrics
198-
agreement_path = compute_terrain_agreement_metrics(
199-
submodel_directory,
200-
terrain_path,
201-
terrain_agreement_resolution,
202-
resolution_units,
203-
terrain_agreement_format,
204-
terrain_agreement_el_repeats,
205-
terrain_agreement_el_ramp_rate,
206-
terrain_agreement_el_init,
207-
)
208-
result["terrain_agreement"] = agreement_path
202+
try:
203+
agreement_path = compute_terrain_agreement_metrics(
204+
submodel_directory,
205+
terrain_path,
206+
terrain_agreement_resolution,
207+
resolution_units,
208+
terrain_agreement_format,
209+
terrain_agreement_el_repeats,
210+
terrain_agreement_el_ramp_rate,
211+
terrain_agreement_el_init,
212+
)
213+
result["terrain_agreement"] = agreement_path
214+
except Exception as e:
215+
if not terrain_agreement_ignore_error:
216+
raise e
217+
logging.error(f"| Error: {e}")
218+
logging.error(f"| Traceback: {traceback.format_exc()}")
219+
220+
result["terrain_agreement"] = None
209221
return result
210222

211223

ripple1d/ops/subset_gpkg.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def source_model_metadata(self):
4646
"""Metadata from the source model."""
4747
with sqlite3.connect(self.src_gpkg_path) as conn:
4848
cur = conn.cursor()
49-
res = cur.execute(f"SELECT key,value from metadata")
49+
res = cur.execute("SELECT key,value from metadata")
5050
return dict(res.fetchall())
5151

5252
def copy_metadata_to_ripple1d_gpkg(self):
@@ -96,7 +96,10 @@ def us_rs(self) -> str:
9696
@property
9797
def us_river_reach(self) -> str:
9898
"""Extract upstream river_reach from conflation parameters."""
99-
return f"{self.us_river.ljust(16)},{self.us_reach.ljust(16)}"
99+
row = self.source_river[
100+
(self.source_river["river"] == self.us_river) & (self.source_river["reach"] == self.us_reach)
101+
]
102+
return row.iloc[0]["river_reach"]
100103

101104
@property
102105
def us_river_reach_rs(self) -> str:
@@ -121,7 +124,10 @@ def ds_rs(self) -> str:
121124
@property
122125
def ds_river_reach(self) -> str:
123126
"""Extract downstream river_reach from conflation parameters."""
124-
return f"{self.ds_river.ljust(16)},{self.ds_reach.ljust(16)}"
127+
row = self.source_river[
128+
(self.source_river["river"] == self.ds_river) & (self.source_river["reach"] == self.ds_reach)
129+
]
130+
return row.iloc[0]["river_reach"]
125131

126132
@property
127133
def us_river_reach_rs(self) -> str:
@@ -206,7 +212,7 @@ def ripple_xs_concave_hull(self):
206212
try:
207213
hulls = self.split_source_hull
208214
ripple_xs_concave_hull = gpd.GeoDataFrame({"geometry": hulls}, geometry="geometry", crs=self.crs)
209-
except Exception as e:
215+
except Exception:
210216
ripple_xs_concave_hull = xs_concave_hull(fix_reversed_xs(self.ripple_xs, self.ripple_river))
211217
return ripple_xs_concave_hull
212218

@@ -441,7 +447,7 @@ def write_ripple_gpkg(self) -> None:
441447
if layer == "Structure":
442448
if (gdf["type"] == 6).any():
443449
logging.warning(
444-
f"Lateral structures are not currently supported in ripple1d. The lateral structures will be dropped."
450+
"Lateral structures are not currently supported in ripple1d. The lateral structures will be dropped."
445451
)
446452
gdf = gdf.loc[gdf["type"] != 6, :]
447453

@@ -495,7 +501,7 @@ def write_ripple1d_parameters(self, ripple1d_parameters: dict):
495501
json.dump(ripple1d_parameters, f, indent=4)
496502

497503

498-
def extract_submodel(source_model_directory: str, submodel_directory: str, nwm_id: int):
504+
def extract_submodel(source_model_directory: str, submodel_directory: str, nwm_id: int, model_name: str):
499505
"""Use ripple conflation data to create a new GPKG from an existing ras geopackage.
500506
501507
Create a new geopackage with information for a specific NWM reach. The new geopackage contains layer for the river centerline, cross-sections, and structures.
@@ -508,6 +514,8 @@ def extract_submodel(source_model_directory: str, submodel_directory: str, nwm_i
508514
The path to export submodel HEC-RAS files to.
509515
nwm_id : int
510516
The id of the NWM reach to create a submodel for
517+
model_name : str
518+
The name of the HEC-RAS model.
511519
task_id : str, optional
512520
Task ID to use for logging, by default ""
513521
@@ -527,7 +535,7 @@ def extract_submodel(source_model_directory: str, submodel_directory: str, nwm_i
527535
raise FileNotFoundError(
528536
f"cannot find directory for source model {source_model_directory}, please ensure dir exists"
529537
)
530-
rsd = RippleSourceDirectory(source_model_directory)
538+
rsd = RippleSourceDirectory(source_model_directory, model_name)
531539

532540
logging.info(f"extract_submodel starting for nwm_id {nwm_id}")
533541

ripple1d/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""ripple1d version."""
22

3-
__version__ = "0.10.0"
3+
__version__ = "0.10.1"

tests/api_tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def test_b_extract_submodel(self):
102102
"source_model_directory": self.SOURCE_RAS_MODEL_DIRECTORY,
103103
"submodel_directory": self.SUBMODELS_DIRECTORY,
104104
"nwm_id": self.REACH_ID,
105+
"model_name": self.MODEL_NAME,
105106
}
106107
process = "extract_submodel"
107108
files = [self.GPKG_FILE]

tests/conflation_tests/plotting.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,19 @@ def divide_section(self, line, splits):
128128
return subs
129129

130130
def clip_xs(self, subset, overlaps, r):
131-
for xs_id in subset["river_reach_rs"].values:
131+
for xs_id in subset["source_river_reach_rs"]:
132132
if xs_id in list(overlaps.keys()):
133133
splits = len(overlaps[xs_id])
134134
position = overlaps[xs_id].index(r)
135-
geom = subset.loc[subset["river_reach_rs"] == xs_id, "geometry"].values[0]
136-
subset.loc[subset["river_reach_rs"] == xs_id, "geometry"] = self.divide_section(geom, splits)[position]
135+
geom = subset.loc[subset["source_river_reach_rs"] == xs_id, "geometry"].values[0]
136+
subset.loc[subset["source_river_reach_rs"] == xs_id, "geometry"] = self.divide_section(geom, splits)[
137+
position
138+
]
137139
return subset
138140

141+
def format_source_id(self, row: dict) -> str:
142+
return " ".join([str(row["source_river"]), str(row["source_reach"]), str(row["source_river_station"])])
143+
139144
def generate_subset_gdfs(self) -> dict:
140145
# Create combo gdf
141146
subset_gdfs = {}
@@ -145,10 +150,11 @@ def generate_subset_gdfs(self) -> dict:
145150
continue
146151
subsetter = RippleGeopackageSubsetter(self.ras_path, self.conflation_path, None, r)
147152
subset = subsetter.subset_xs
153+
subset["source_river_reach_rs"] = subset.apply(self.format_source_id, axis=1)
148154
subset_gdfs[r] = subset
149155

150156
# log xs for duplicate detection
151-
for xs_id in subset["river_reach_rs"]:
157+
for xs_id in subset["source_river_reach_rs"]:
152158
overlaps[xs_id].append(r)
153159

154160
# split duplicated section geometry
@@ -207,7 +213,7 @@ def apply_formatting(self):
207213
ax.legend(fontsize="x-small", loc="lower right", ncols=len(self.nwm_reaches) + 1)
208214
ax.set_axis_off()
209215
try:
210-
ctx.add_basemap(ax, crs=self.crs, source=ctx.providers.USGS.USTopo, attribution=False)
216+
ctx.add_basemap(ax, crs=self.crs, source=ctx.providers.USGS.USImagery, attribution=False)
211217
except requests.exceptions.HTTPError as e:
212218
try:
213219
ctx.add_basemap(ax, crs=self.crs, source=ctx.providers.Esri.WorldStreetMap, attribution=False)

0 commit comments

Comments
 (0)