Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/0.8.2 #304

Merged
merged 31 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
609bebf
Merge pull request #290 from Dewberry/main
sclaw Jan 9, 2025
885e3e9
fix depth indexing such that float rounding errors in get_flow_depth_…
sclaw Jan 9, 2025
8c1a16e
(proactive) match precision of later functions to hopefully reduce ro…
sclaw Jan 9, 2025
4728743
check whether HEC-RAS terrain was generated and raise an error if it …
sclaw Jan 9, 2025
30dd6d5
catch and raise error when downloaded terrain is all nodata.
sclaw Jan 9, 2025
7a8bb72
update ds reach length parsing
sclaw Jan 9, 2025
396da59
fix bugged conflation metrics to properly initialize sub model
sclaw Jan 9, 2025
12ee091
handle case of u/s xs being lower station than d/s xs
sclaw Jan 14, 2025
80479ea
handle case of single cross-section in subset_gpkg. Raise more usefu…
sclaw Jan 14, 2025
1517c27
add unsupported projection test
sclaw Jan 14, 2025
3abe42b
add test for misbehaving model
sclaw Jan 14, 2025
e83df0b
add test for interpolated sections and null downstream reach length
sclaw Jan 14, 2025
79530b7
RAS adds * to flow change locations at interpolated sections regardle…
sclaw Jan 14, 2025
5516ef5
update gitignore for winooski
sclaw Jan 14, 2025
7328696
handle interpolated sections in create fim lib
sclaw Jan 14, 2025
4eba5ff
handle interpolated sections
sclaw Jan 15, 2025
444dabf
handle interpolated sections AND numeric sections
sclaw Jan 15, 2025
305301a
fix bad f string
sclaw Jan 15, 2025
56100bf
fix bad f string
sclaw Jan 15, 2025
ffd3a6a
Merge pull request #296 from Dewberry/bugfix/kwse-flow-rounding
sclaw Jan 15, 2025
76b8e98
update river_reach_rs after section renaming in sub model creation
sclaw Jan 15, 2025
7efc572
Merge pull request #297 from Dewberry/bugfix/conflation-metrics
sclaw Jan 15, 2025
150fe35
Merge pull request #302 from Dewberry/bugfix/reversed-reach-conflation
sclaw Jan 15, 2025
0f2e87c
Merge branch 'dev' into bugfix/nodata-rasters
sclaw Jan 15, 2025
76c4633
Merge pull request #301 from Dewberry/bugfix/nodata-rasters
sclaw Jan 15, 2025
f58d39c
Merge branch 'dev' into bugfix/terrain-gen-failure
sclaw Jan 15, 2025
35a60ad
Merge pull request #300 from Dewberry/bugfix/terrain-gen-failure
sclaw Jan 15, 2025
0a22d0e
Merge pull request #299 from Dewberry/bugfix/null-ds-length
sclaw Jan 17, 2025
8ee1a9b
update versions
sclaw Jan 17, 2025
400742f
update docs
sclaw Jan 17, 2025
71081db
update authors
sclaw Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ tests/ras-data/Baxter/nwm_models
tests/ras-data/Baxter/Terrain
tests/ras-data/MissFldwy/submodels
tests/ras-data/PatuxentRiver/submodels
tests/ras-data/winooski/submodels

/ripple1d/models/
.vscode/settings.json
Expand All @@ -186,4 +187,4 @@ py312/
*.log*
venv3/
*huey.jsonld
*flask.jsonld
*flask.jsonld
Binary file removed docs/images/terrain_agreement/1.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/2.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/3.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/4.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/5.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/6.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/7.png
Binary file not shown.
Binary file removed docs/images/terrain_agreement/8.png
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

project = "ripple1d"
copyright = "2024, Dewberry"
author = "Seth Lawler, Matt Deshotel, Max Kipp, Abdul Siddiqui"
author = "Seth Lawler, Matt Deshotel, Scott Lawson, Abdul Siddiqui"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
2 changes: 1 addition & 1 deletion docs/source/postman.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Postman collection

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

`v0.8.0: <https://github.com/Dewberry/ripple1d/blob/39089e932b1052e1b708a84eefff47f1973759c5/ripple1d/api/postman_collection.json>`_ This beta version contains new args for the create_ras_terrain endpoint:
`v0.8.0-v.0.8.2: <https://github.com/Dewberry/ripple1d/blob/39089e932b1052e1b708a84eefff47f1973759c5/ripple1d/api/postman_collection.json>`_ This beta version contains new args for the create_ras_terrain endpoint:
- `terrain_agreement_resolution` (float) added to `create_ras_terrain`. This is the maximum distance allowed between the vertices used to calculate terrain agreement metrics. It is in the units of the HEC-RAS model.
- `f` (json or html) added to jobs. Default value is json. Determines the response format of the endpoint.

Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"

[project]
name = "ripple1d"
version = "0.8.1"
dynamic = ["version"]
description = "HEC-RAS model automation"
readme = "README.md"
maintainers = [
{ name = "Seth Lawler", email = "[email protected]" },
{ name = "Matt Deshotel", email = "[email protected]" },
{ name = "Max Kipp", email = "jkipp@dewberry.com" },
{ name = "Scott Lawson", email = "klawson@dewberry.com" },
{ name = "Abdul Siddiqui", email = "[email protected]" }
]
classifiers = [
Expand Down Expand Up @@ -79,6 +79,10 @@ where = ["."]
include = ["ripple1d*"]


[tool.setuptools.dynamic]
version = {attr = "ripple1d.__version__"}


[tool.sphinx]
project = "ripple1d"
author = "Seth Lawler"
15 changes: 15 additions & 0 deletions ripple1d/conflate/rasfim.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from shapely.ops import linemerge, nearest_points, split, transform

from ripple1d.consts import METERS_PER_FOOT
from ripple1d.errors import BadConflation
from ripple1d.utils.ripple_utils import (
check_xs_direction,
clip_ras_centerline,
Expand Down Expand Up @@ -694,6 +695,19 @@ def map_reach_xs(rfc: RasFimConflater, reach: MultiLineString) -> dict:
return {"us_xs": us_data, "ds_xs": ds_data, "eclipsed": False}


def validate_reach_conflation(reach_xs_data: dict, reach_id: str):
"""Raise error for invalid conflation.

The trim_reach method in subset_gpkg.py will return an empty geodataframe when u/s xs_id is lower than d/s xs_id.
This likely indicates poor CRS inference.
"""
us = reach_xs_data["us_xs"]
ds = reach_xs_data["ds_xs"]
if (us["river"] == ds["river"]) & (us["reach"] == ds["reach"]) & (us["xs_id"] < ds["xs_id"]):
err_str = f"Reach {reach_id} has u/s xs station ({us['xs_id']}) lower than d/s xs station ({ds['xs_id']})"
raise BadConflation(err_str)


def ras_reaches_metadata(rfc: RasFimConflater, candidate_reaches: gpd.GeoDataFrame, river_reach_name: str):
"""Return the metadata for the RAS reaches."""
reach_metadata = OrderedDict()
Expand All @@ -702,6 +716,7 @@ def ras_reaches_metadata(rfc: RasFimConflater, candidate_reaches: gpd.GeoDataFra
try:
# get the xs data for the reach
ras_xs_data = map_reach_xs(rfc, reach)
validate_reach_conflation(ras_xs_data, str(reach.ID))
reach_metadata[reach.ID] = ras_xs_data
except Exception as e:
logging.error(f"river-reach: {river_reach_name} | network id: {reach.ID} | Error: {e}")
Expand Down
26 changes: 22 additions & 4 deletions ripple1d/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ def __init__(
self.reach = reach
self.river_reach = river_reach
self.river_reach_rs = f"{river} {reach} {self.river_station}"
self.river_reach_rs_str = f"{river} {reach} {self.river_station_str}"
self.thalweg_drop = None
self.reach_geom = reach_geom
self.computed_channel_reach_length = None
Expand All @@ -411,22 +412,39 @@ def split_xs_header(self, position: int):
@property
def river_station(self):
"""Cross section river station."""
return float(self.split_xs_header(1).replace("*", ""))
return float(self.river_station_str.replace("*", ""))

@property
def river_station_str(self) -> str:
"""Return the river station with * for interpolated sections."""
return self.split_xs_header(1).rstrip()

@property
def left_reach_length(self):
"""Cross section left reach length."""
return float(self.split_xs_header(2))
dist = self.split_xs_header(2)
if not dist:
return 0.0
else:
return float(dist)

@property
def channel_reach_length(self):
"""Cross section channel reach length."""
return float(self.split_xs_header(3))
dist = self.split_xs_header(3)
if not dist:
return 0.0
else:
return float(dist)

@property
def right_reach_length(self):
"""Cross section right reach length."""
return float(self.split_xs_header(4))
dist = self.split_xs_header(4)
if not dist:
return 0.0
else:
return float(dist)

@property
def number_of_coords(self):
Expand Down
18 changes: 18 additions & 0 deletions ripple1d/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,21 @@ class PlanNameNotFoundError(Exception):

class UnknownVerticalUnits(Exception):
"""Raised when unknown vertical units are specified."""


class RasTerrainFailure(Exception):
"""Raised when the HEC-RAS CreateTerrain executable fails to generate terrain."""


class NullTerrainError(Exception):
"""Raised when the downloaded terrain for an error is all nodata values."""


class BadConflation(Exception):
"""Raised when conflation yields a d/s cross-section with higher station than the u/s cross-section."""


class SingleXSModel(Exception):
"""Raised when geopackage creation would yield a single cross-section model."""


2 changes: 1 addition & 1 deletion ripple1d/ops/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def compute_conflation_metrics(source_model_directory: str, source_network: dict
if conflation_parameters["reaches"][network_id]["eclipsed"] == True:
continue

rgs = RippleGeopackageSubsetter(src_gpkg_path, conflation_json, network_id)
rgs = RippleGeopackageSubsetter(src_gpkg_path, conflation_json, "", network_id)
layers = {}
for layer, gdf in rgs.subset_gdfs.items():
layers[layer] = gdf.to_crs(HYDROFABRIC_CRS)
Expand Down
16 changes: 8 additions & 8 deletions ripple1d/ops/ras_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def create_model_run_normal_depth(
fcl = FlowChangeLocation(
nwm_rm.model_name,
nwm_rm.model_name,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station_str,
initial_flows.tolist(),
)

Expand Down Expand Up @@ -185,7 +185,7 @@ def run_incremental_normal_depth(
fcl = FlowChangeLocation(
nwm_rm.model_name,
nwm_rm.model_name,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station_str,
flows.tolist(),
)
# write and compute flow/plans for normal_depth run
Expand Down Expand Up @@ -273,7 +273,7 @@ def run_known_wse(
rm,
nwm_rm.model_name,
nwm_rm.model_name,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].ds_xs.river_station,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].ds_xs.river_station_str,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].ds_xs.thalweg,
)

Expand Down Expand Up @@ -305,7 +305,7 @@ def run_known_wse(
flows,
nwm_rm.model_name,
nwm_rm.model_name,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station,
rm.geoms[nwm_rm.model_name].rivers[nwm_rm.model_name][nwm_rm.model_name].us_xs.river_station_str,
write_depth_grids=write_depth_grids,
show_ras=show_ras,
run_ras=True,
Expand All @@ -326,7 +326,7 @@ def get_flow_depth_arrays(

wse = wses.loc[river_reach_rs, :]
flow = flows.loc[river_reach_rs, :]
df = pd.DataFrame({"wse": wse.round(2), "flow": flow.round(2)}).drop_duplicates()
df = pd.DataFrame({"wse": wse.round(1), "flow": flow.astype(int)}).drop_duplicates()

# convert wse to depth
depth = df["wse"] - thalweg
Expand All @@ -347,7 +347,7 @@ def determine_flow_increments(
for plan_name in plan_names:
rm.plan = rm.plans[plan_name]

river_station = rm.geoms[nwm_id].rivers[nwm_id][nwm_id].us_xs.river_station
river_station = rm.geoms[nwm_id].rivers[nwm_id][nwm_id].us_xs.river_station_str
thalweg = rm.geoms[nwm_id].rivers[nwm_id][nwm_id].us_xs.thalweg

# get new flow/depth for current branch
Expand Down Expand Up @@ -381,8 +381,8 @@ def create_flow_depth_combinations(
"""
depths, flows, wses = [], [], []
for wse, depth in zip(ds_wses, ds_depths):
for flow in input_flows:
if depth >= min_depths.loc[str(int(flow))]:
for profile, flow in input_flows.items():
if depth >= min_depths.loc[profile]:
depths.append(round(depth, 1))
flows.append(int(max([flow, MIN_FLOW])))
wses.append(round(wse, 1))
Expand Down
8 changes: 6 additions & 2 deletions ripple1d/ops/ras_terrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
TERRAIN_AGREEMENT_PRECISION,
)
from ripple1d.data_model import XS, NwmReachModel
from ripple1d.errors import RasTerrainFailure
from ripple1d.ras import RasGeomText, create_terrain
from ripple1d.utils.dg_utils import clip_raster, reproject_raster
from ripple1d.utils.ripple_utils import fix_reversed_xs, resample_vertices, xs_concave_hull
Expand Down Expand Up @@ -185,12 +186,15 @@ def create_ras_terrain(
projection_file,
f"{nwm_rm.terrain_directory}\\{nwm_rm.model_name}",
)
os.remove(src_dem_reprojected_localfile)
terrain_path = result["RAS Terrain"] + "." + map_dem_clipped_basename.replace(".vrt", ".tif")
if os.path.exists(terrain_path):
os.remove(src_dem_reprojected_localfile)
else:
raise RasTerrainFailure(f"Tried to create terrain at {terrain_path}, but CreateTerrain exe failed.")
nwm_rm.update_write_ripple1d_parameters({"source_terrain": terrain_source_url})
logging.info(f"create_ras_terrain complete")

# Calculate terrain agreement metrics
terrain_path = result["RAS Terrain"] + "." + map_dem_clipped_basename.replace(".vrt", ".tif")
agreement_path = compute_terrain_agreement_metrics(
submodel_directory,
terrain_path,
Expand Down
17 changes: 14 additions & 3 deletions ripple1d/ops/subset_gpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import ripple1d
from ripple1d.data_model import NwmReachModel, RippleSourceDirectory
from ripple1d.errors import SingleXSModel
from ripple1d.utils.ripple_utils import (
clip_ras_centerline,
fix_reversed_xs,
Expand Down Expand Up @@ -254,8 +255,9 @@ def subset_gdfs(self) -> dict:
subset_gdfs = {}
subset_gdfs["XS"] = self.subset_xs
if len(subset_gdfs["XS"]) <= 1: # check if only 1 cross section for nwm_reach
logging.warning(f"Only 1 cross section conflated to NWM reach {self.nwm_id}. Skipping this reach.")
return None
err_string = f"Sub model for {self.nwm_id} would have {len(subset_gdfs['XS'])} cross-sections but is not tagged as eclipsed. Skipping."
logging.warning(err_string)
raise SingleXSModel(err_string)
subset_gdfs["River"] = self.subset_river
if self.subset_structures is not None:
subset_gdfs["Structure"] = self.subset_structures
Expand Down Expand Up @@ -379,6 +381,13 @@ def update_river_station(self, subset_gdfs: dict[gpd.GeoDataFrame]) -> dict:
)

subset_gdfs["XS"]["river_station"] = xs_names
subset_gdfs["XS"]["river_reach_rs"] = (
subset_gdfs["XS"]["river"]
+ " "
+ subset_gdfs["XS"]["reach"]
+ " "
+ subset_gdfs["XS"]["river_station"].astype(str)
)
subset_gdfs["XS"]["ras_data"] = subset_gdfs["XS"][["ras_data", "river_station"]].apply(
self.correct_ras_data, axis=1
)
Expand Down Expand Up @@ -476,13 +485,15 @@ def extract_submodel(source_model_directory: str, submodel_directory: str, nwm_i
ripple1d_parameters["messages"] = f"skipping {nwm_id}; no cross sections conflated."
logging.warning(ripple1d_parameters["messages"])
gpkg_path = None
conflation_file = None

else:
rgs = RippleGeopackageSubsetter(rsd.ras_gpkg_file, rsd.conflation_file, submodel_directory, nwm_id)
rgs.write_ripple_gpkg()
ripple1d_parameters = rgs.update_ripple1d_parameters(rsd)
rgs.write_ripple1d_parameters(ripple1d_parameters)
gpkg_path = rgs.ripple_gpkg_file
conflation_file = rsd.conflation_file

logging.info(f"extract_submodel complete for nwm_id {nwm_id}")
return {"ripple1d_parameters": rsd.conflation_file, "ripple_gpkg_file": gpkg_path}
return {"ripple1d_parameters": conflation_file, "ripple_gpkg_file": gpkg_path}
2 changes: 1 addition & 1 deletion ripple1d/ras.py
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,7 @@ def flow_change_locations(self):
FlowChangeLocation(
river,
reach.rstrip(" "),
float(rs),
float(rs.replace("*", "")),
flows,
self.profile_names,
)
Expand Down
6 changes: 5 additions & 1 deletion ripple1d/utils/dg_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from shapely import Polygon

from ripple1d.consts import METERS_PER_FOOT
from ripple1d.errors import UnknownVerticalUnits
from ripple1d.errors import NullTerrainError, UnknownVerticalUnits

from .s3_utils import *

Expand Down Expand Up @@ -176,6 +176,10 @@ def clip_raster(src_path: str, dst_path: str, mask_polygon: Polygon, vertical_un
with rasterio.open(src_path) as src:
out_meta = src.meta
out_image, out_transform = mask.mask(src, [mask_polygon], all_touched=True, crop=True)
nd = src.nodata

if np.all(out_image == nd):
raise NullTerrainError(f"Terrain downloaded from {src_path} was all nodata values.")

out_meta.update(
{
Expand Down
6 changes: 3 additions & 3 deletions ripple1d/utils/sqlite_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def zero_depth_to_sqlite(
wses, flows = rm.plan.read_rating_curves()

# get river-reach-rs
us_river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].us_xs.river_reach_rs
ds_river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].ds_xs.river_reach_rs
us_river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].us_xs.river_reach_rs_str
ds_river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].ds_xs.river_reach_rs_str

wses_t = wses.T
wses_t["us_flow"] = wses_t.index
Expand Down Expand Up @@ -156,7 +156,7 @@ def rating_curves_to_sqlite(
wses = parse_stage_flow(wses)

# get river-reach-rs id
river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].us_xs.river_reach_rs
river_reach_rs = rm.plan.geom.rivers[nwm_id][nwm_id].us_xs.river_reach_rs_str

# get subset of results for this cross section
df = wses[["us_flow", "ds_wse", river_reach_rs]].copy()
Expand Down
2 changes: 1 addition & 1 deletion ripple1d/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""ripple1d version."""

__version__ = "0.8.1"
__version__ = "0.8.2"
Loading