diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 09a56365..f8482a2a 100755
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,6 +1,16 @@
All notable changes to this project will be documented in this file.
We follow the [Semantic Versioning 2.0.0](http://semver.org/) format.
+## v4.5.13.9 - 2025-01-24 - [PR#1399](https://github.com/NOAA-OWP/inundation-mapping/pull/1399)
+
+This update improves stage-based CatFIM by detecting and correcting instances where the stage value provided in the WRDS database is actually stage + elevation (which is actually water surface elevation and, uncaught, causes overflooding).
+
+### Changes
+- `inundation-mapping/tools/catfim/generate_categorical_fim.py`: Added an update to detect and fix cases where WSE is provided in lieu of stage. Added `uncorrected_stage` and `is_interval` columns to output CSV.
+- `inundation-mapping/tools/catfim/generate_categorical_fim_mapping.py`: Added update to facilitate the new `is_interval` column.
+
+
+
## v4.5.13.8 - 2025-01-24 - [PR#1405](https://github.com/NOAA-OWP/inundation-mapping/pull/1405)
Removing the references to lid_to_run from CatFIM in order to keep the CatFIM scripts cleaner.
diff --git a/tools/catfim/generate_categorical_fim.py b/tools/catfim/generate_categorical_fim.py
index d56db263..2261c37a 100755
--- a/tools/catfim/generate_categorical_fim.py
+++ b/tools/catfim/generate_categorical_fim.py
@@ -737,6 +737,29 @@ def iterate_through_huc_stage_based(
if not os.path.exists(mapping_lid_directory):
os.mkdir(mapping_lid_directory)
+ # Check whether stage value is actually a WSE value, and fix if needed:
+ # Get lowest stage value
+ lowest_stage_val = stage_values_df['stage_value'].min()
+
+ maximum_stage_threshold = 250 # TODO: Move to a variables file?
+
+ # Make an "rfc_stage" column for better documentation which shows the original
+ # uncorrect WRDS value before we adjsuted it for inundation
+ stage_values_df['rfc_stage'] = stage_values_df['stage_value']
+
+ # Stage value is larger than the elevation value AND greater than the
+ # maximum stage threshold, subtract the elev from the "stage" value
+ # to get the actual stage
+
+ if (lowest_stage_val > lid_altitude) and (lowest_stage_val > maximum_stage_threshold):
+ stage_values_df['stage_value'] = stage_values_df['stage_value'] - lid_altitude
+ MP_LOG.lprint(
+ f"{huc_lid_id}: Lowest stage val > elev and higher than max stage thresh. Subtracted elev from stage vals to fix."
+ )
+
+ # +++++++++++++++++++++++++++++
+ # This section is for inundating stages and intervals come later
+
# At this point we have at least one valid stage/category
# cyle through on the stages that are valid
# This are not interval values
@@ -763,7 +786,6 @@ def iterate_through_huc_stage_based(
# These are the up to 5 magnitudes being inundated at their stage value
(messages, hand_stage, datum_adj_wse, datum_adj_wse_m) = produce_stage_based_lid_tifs(
stage_value,
- False,
datum_adj_ft,
branch_dir,
lid_usgs_elev,
@@ -823,6 +845,9 @@ def iterate_through_huc_stage_based(
# MP_LOG.trace(f"non_rec_stage_values_df is {non_rec_stage_values_df}")
+ # +++++++++++++++++++++++++++++
+ # Creating interval tifs (if applicable)
+
# We already inundated and created files for the specific stages just not the intervals
# Make list of interval recs to be created
interval_list = [] # might stay empty
@@ -854,7 +879,6 @@ def iterate_through_huc_stage_based(
executor.submit(
produce_stage_based_lid_tifs,
interval_stage_value,
- True,
datum_adj_ft,
branch_dir,
lid_usgs_elev,
@@ -912,7 +936,9 @@ def iterate_through_huc_stage_based(
# for threshold in categories: (threshold and category are somewhat interchangeable)
# some may have failed inundation, which we will rectify later
MP_LOG.trace(f"{huc_lid_id}: updating threshhold values")
+
for threshold in valid_stage_names:
+
try:
# we don't know if the magnitude/stage can be mapped yes it hasn't been inundated
@@ -929,7 +955,12 @@ def iterate_through_huc_stage_based(
'q': flows[threshold],
'q_uni': flows['units'],
'q_src': flows['source'],
- 'stage': thresholds[threshold],
+ 'rfs_stage': stage_values_df.loc[stage_values_df['stage_name'] == threshold][
+ 'rfc_stage'
+ ],
+ 'stage': stage_values_df.loc[stage_values_df['stage_name'] == threshold][
+ 'stage_value'
+ ],
'stage_uni': thresholds['units'],
's_src': thresholds['source'],
'wrds_time': thresholds['wrds_timestamp'],
@@ -1131,7 +1162,7 @@ def __calc_stage_intervals(non_rec_stage_values_df, past_major_interval_cap, huc
# MP_LOG.trace(f"{huc_lid_id}: Added interval value of {int_val}")
stage_values_claimed.append(int_val)
- MP_LOG.lprint(f"{huc_lid_id} interval recs are {interval_recs}")
+ # MP_LOG.lprint(f"{huc_lid_id} interval recs are {interval_recs}")
return interval_recs
diff --git a/tools/catfim/generate_categorical_fim_mapping.py b/tools/catfim/generate_categorical_fim_mapping.py
index f6648f36..833e0546 100755
--- a/tools/catfim/generate_categorical_fim_mapping.py
+++ b/tools/catfim/generate_categorical_fim_mapping.py
@@ -43,7 +43,6 @@
# we will use an MP object either way
def produce_stage_based_lid_tifs(
stage_val,
- is_interval_stage,
datum_adj_ft,
branch_dir,
lid_usgs_elev,
@@ -665,10 +664,12 @@ def post_process_huc(
# careful. ft can be part of the site name, so only check part 3
interval_stage = None
+ is_interval = False
if len(file_name_parts) >= 3 and "fti" in file_name_parts[2]:
try:
stage_val = file_name_parts[2].replace("fti", "")
interval_stage = float(stage_val)
+ is_interval = True
except ValueError:
interval_stage = None
MP_LOG.error(
@@ -686,6 +687,7 @@ def post_process_huc(
magnitude,
nws_lid_attributes_filename,
interval_stage,
+ is_interval,
parent_log_output_file,
child_log_file_prefix,
)
@@ -862,6 +864,7 @@ def reformat_inundation_maps(
magnitude,
nws_lid_attributes_filename,
interval_stage,
+ is_interval,
parent_log_output_file,
child_log_file_prefix,
):
@@ -914,6 +917,7 @@ def reformat_inundation_maps(
extent_poly_diss['version'] = fim_version
extent_poly_diss['huc'] = huc
extent_poly_diss['interval_stage'] = interval_stage
+ extent_poly_diss['is_interval'] = is_interval
# Project to Web Mercator
extent_poly_diss = extent_poly_diss.to_crs(VIZ_PROJECTION)
@@ -931,6 +935,9 @@ def reformat_inundation_maps(
# already has an ahps_lid column which we want and not the nws_lid column
extent_poly_diss = extent_poly_diss.drop(columns='nws_lid')
+ # Remove uncorrected stage from interval rows (to decrease potential for confusion)
+ extent_poly_diss.loc[extent_poly_diss['is_interval'] == True, 'stage_uncorrected'] = None
+
# Save dissolved multipolygon
handle = os.path.split(tif_to_process)[1].replace('.tif', '')
diss_extent_filename = os.path.join(gpkg_dir, f"{huc}_{handle}.gpkg")