Skip to content

Commit da24211

Browse files
Adds full resolution and mainstem inundation composite capability
This resolves #476. See Changelog 3.0.24.0 for full details.
1 parent 69f5fb3 commit da24211

9 files changed

+1528
-4
lines changed

docs/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
All notable changes to this project will be documented in this file.
22
We follow the [Semantic Versioning 2.0.0](http://semver.org/) format.
33

4+
## v3.0.24.0 - 2021-11-08 - [PR #482](https://github.com/NOAA-OWP/cahaba/pull/482)
5+
6+
Adds `composite_ms_fr_inundation.py` to allow for the generation of an inundation map given a "flow file" CSV and full-resolution (FR) and mainstem (MS) relative elevation models, synthetic rating curves, and catchments rasters created by the `fim_run.sh` script.
7+
8+
## Additions
9+
- `composite_ms_fr_inundation.py`: New module that is used to inundate both MS and FR FIM and composite the two inundation rasters.
10+
- `/tools/gms_tools/`: Three modules (`inundate_gms.py`, `mosaic_inundation.py`, `overlapping_inundation.py`) ported from the GMS branch used to composite inundation rasters.
11+
12+
## Changes
13+
- `inundation.py`: Added 2 exception classes ported from the GMS branch.
14+
15+
<br/><br/>
16+
417
## v3.0.23.3 - 2021-11-04 - [PR #481](https://github.com/NOAA-OWP/cahaba/pull/481)
518
Includes additional hydraulic properties to the `hydroTable.csv`: `Number of Cells`, `SurfaceArea (m2)`, `BedArea (m2)`, `Volume (m3)`, `SLOPE`, `LENGTHKM`, `AREASQKM`, `Roughness`, `TopWidth (m)`, `WettedPerimeter (m)`. Also adds `demDerived_reaches_split_points.gpkg`, `flowdir_d8_burned_filled.tif`, and `dem_thalwegCond.tif` to `-v` whitelist.
619

tools/composite_ms_fr_inundation.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/usr/bin/env python3
2+
import os, argparse, rasterio
3+
import numpy as np
4+
import pandas as pd
5+
from multiprocessing import Pool
6+
7+
from inundation import inundate
8+
from gms_tools.mosaic_inundation import Mosaic_inundation, __append_id_to_file_name
9+
10+
11+
def composite_inundation(fim_dir_ms, fim_dir_fr, huc, flows, composite_output_dir, ouput_name='',
12+
bin_rast_flag=False, depth_rast_flag=False, clean=True, quiet=True):
13+
"""
14+
Run `inundate()` on FIM 3.X mainstem (MS) and full-resolution (FR) outputs and composite results. Assumes that all `fim_run` products
15+
necessary for `inundate()` are in each huc8 folder.
16+
17+
Parameters
18+
----------
19+
fim_dir_ms : str
20+
Path to MS FIM directory. This should be an output directory from `fim_run.sh`.
21+
fim_dir_fr : str
22+
Path to FR FIM directory. This should be an output directory from `fim_run.sh`.
23+
huc : str
24+
HUC8 to run `inundate()`. This should be a folder within both `fim_dir_ms` and `fim_dir_fr`.
25+
flows : str or pandas.DataFrame, can be a single file or a comma-separated list of files
26+
File path to forecast csv or Pandas DataFrame with correct column names.
27+
composite_output_dir : str
28+
Folder path to write outputs. It will be created if it does not exist.
29+
ouput_name : str, optional
30+
Name for output raster. If not specified, by default the raster will be named 'inundation_composite_{flows_root}.tif'.
31+
bin_rast_flag : bool, optional
32+
Flag to create binary raster as output. If no raster flags are passed, this is the default behavior.
33+
depth_rast_flag : bool, optional
34+
Flag to create depth raster as output.
35+
clean : bool, optional
36+
If True, intermediate files are deleted.
37+
quiet : bool, optional
38+
Quiet output.
39+
40+
Returns
41+
-------
42+
None
43+
44+
Raises
45+
------
46+
TypeError
47+
Wrong input data types
48+
AssertionError
49+
Wrong input data types
50+
51+
Notes
52+
-----
53+
- Specifying a subset of the domain in rem or catchments to inundate on is achieved by the HUCs file or the forecast file.
54+
55+
Examples
56+
--------
57+
>>> import composite_ms_fr_inundation
58+
>>> composite_ms_fr_inundation.composite_inundation(
59+
'/home/user/fim_ouput_mainstem',
60+
'/home/user/fim_ouput_fullres',
61+
'12090301',
62+
'/home/user/forecast_file.csv',
63+
'/home/user/fim_inundation_composite',
64+
True,
65+
False)
66+
"""
67+
# Set inundation raster to True if no output type flags are passed
68+
if not (bin_rast_flag or depth_rast_flag):
69+
bin_rast_flag = True
70+
assert not (bin_rast_flag and depth_rast_flag), 'Output can only be binary or depth grid, not both'
71+
assert os.path.isdir(fim_dir_ms), f'{fim_dir_ms} is not a directory. Please specify an existing MS FIM directory.'
72+
assert os.path.isdir(fim_dir_fr), f'{fim_dir_fr} is not a directory. Please specify an existing FR FIM directory.'
73+
assert os.path.exists(flows), f'{flows} does not exist. Please specify a flow file.'
74+
75+
# Instantiate output variables
76+
var_keeper = {
77+
'ms': {
78+
'dir': fim_dir_ms,
79+
'outputs': {
80+
'inundation_rast': os.path.join(composite_output_dir, f'inundation_ms_{huc}.tif') if bin_rast_flag else None,
81+
'depth_rast': os.path.join(composite_output_dir, f'depth_ms_{huc}.tif') if depth_rast_flag else None
82+
}
83+
},
84+
'fr': {
85+
'dir': fim_dir_fr,
86+
'outputs': {
87+
'inundation_rast': os.path.join(composite_output_dir, f'inundation_fr_{huc}.tif') if bin_rast_flag else None,
88+
'depth_rast': os.path.join(composite_output_dir, f'depth_fr_{huc}.tif') if depth_rast_flag else None
89+
}
90+
}
91+
}
92+
# Build inputs to inundate() based on the input folders and huc
93+
if not quiet: print(f"HUC {huc}")
94+
for extent in var_keeper:
95+
rem = os.path.join(var_keeper[extent]['dir'], huc, 'rem_zeroed_masked.tif')
96+
catchments = os.path.join(var_keeper[extent]['dir'], huc, 'gw_catchments_reaches_filtered_addedAttributes.tif')
97+
catchment_poly = os.path.join(var_keeper[extent]['dir'], huc, 'gw_catchments_reaches_filtered_addedAttributes_crosswalked.gpkg')
98+
hydro_table = os.path.join(var_keeper[extent]['dir'], huc, 'hydroTable.csv')
99+
100+
# Ensure that all of the required files exist in the huc directory
101+
for file in (rem, catchments, catchment_poly, hydro_table):
102+
if not os.path.exists(file):
103+
raise Exception(f"The following file does not exist within the supplied FIM directory:\n{file}")
104+
105+
# Run inundation()
106+
extent_friendly = "mainstem (MS)" if extent=="ms" else "full-resolution (FR)"
107+
if not quiet: print(f" Creating an inundation map for the {extent_friendly} configuration...")
108+
result = inundate(rem,catchments,catchment_poly,hydro_table,flows,mask_type=None,
109+
inundation_raster= var_keeper[extent]['outputs']['inundation_rast'],
110+
depths= var_keeper[extent]['outputs']['depth_rast'],
111+
quiet= quiet)
112+
if result != 0:
113+
raise Exception(f"Failed to inundate {rem} using the provided flows.")
114+
115+
# If no output name supplied, create one using the flows file name
116+
if not ouput_name:
117+
flows_root = os.path.splitext(os.path.basename(flows))[0]
118+
ouput_name = os.path.join(composite_output_dir, f'inundation_composite_{flows_root}.tif')
119+
else:
120+
ouput_name = os.path.join(composite_output_dir, ouput_name)
121+
122+
# Composite MS and FR
123+
inundation_map_file = {
124+
'huc8' : [huc] * 2,
125+
'branchID' : [None] * 2,
126+
'inundation_rasters': [var_keeper['fr']['outputs']['inundation_rast'],
127+
var_keeper['ms']['outputs']['inundation_rast']],
128+
'depths_rasters': [var_keeper['fr']['outputs']['depth_rast'],
129+
var_keeper['ms']['outputs']['depth_rast']]
130+
}
131+
inundation_map_file = pd.DataFrame(inundation_map_file)
132+
Mosaic_inundation(
133+
inundation_map_file,
134+
mosaic_attribute='depths_rasters' if depth_rast_flag else 'inundation_rasters',
135+
mosaic_output=ouput_name,
136+
mask=catchment_poly,
137+
unit_attribute_name='huc8',
138+
nodata=-9999,
139+
workers=1,
140+
remove_inputs=clean,
141+
subset=None,verbose=not quiet
142+
)
143+
if bin_rast_flag:
144+
hydroid_to_binary(__append_id_to_file_name(ouput_name, huc))
145+
146+
def hydroid_to_binary(hydroid_raster_filename):
147+
'''Converts hydroid positive/negative grid to 1/0'''
148+
149+
#to_bin = lambda x: np.where(x > 0, 1, np.where(x == 0, -9999, 0))
150+
to_bin = lambda x: np.where(x > 0, 1, np.where(x != -9999, 0, -9999))
151+
hydroid_raster = rasterio.open(hydroid_raster_filename)
152+
profile = hydroid_raster.profile # get profile for new raster creation later on
153+
profile['nodata'] = -9999
154+
bin_raster = to_bin(hydroid_raster.read(1)) # converts neg/pos to 0/1
155+
# Overwrite inundation raster
156+
with rasterio.open(hydroid_raster_filename, "w", **profile) as out_raster:
157+
out_raster.write(bin_raster.astype(hydroid_raster.profile['dtype']), 1)
158+
del hydroid_raster,profile,bin_raster
159+
160+
161+
if __name__ == '__main__':
162+
163+
# parse arguments
164+
parser = argparse.ArgumentParser(description='Inundate FIM 3 full resolution and mainstem outputs using a flow file and composite the results.')
165+
parser.add_argument('-ms','--fim-dir-ms',help='Directory that contains MS FIM outputs.',required=True)
166+
parser.add_argument('-fr','--fim-dir-fr',help='Directory that contains FR FIM outputs.',required=True)
167+
parser.add_argument('-u','--huc',help='HUC within FIM directories to inunundate. Can be a comma-separated list.',required=True)
168+
parser.add_argument('-f','--flows-file',help='File path of flows csv or comma-separated list of paths if running multiple HUCs',required=True)
169+
parser.add_argument('-o','--ouput-dir',help='Folder to write Composite Raster output.',required=True)
170+
parser.add_argument('-n','--ouput-name',help='File name for output(s).',default=None,required=False)
171+
parser.add_argument('-b','--bin-raster',help='Output raster is a binary wet/dry grid. This is the default if no raster flags are passed.',required=False,default=False,action='store_true')
172+
parser.add_argument('-d','--depth-raster',help='Output raster is a depth grid.',required=False,default=False,action='store_true')
173+
parser.add_argument('-j','--num-workers',help='Number of concurrent processesto run.',required=False,default=1,type=int)
174+
parser.add_argument('-c','--clean',help='If flag used, intermediate rasters are NOT cleaned up.',required=False,default=True,action='store_false')
175+
parser.add_argument('-q','--quiet',help='Quiet terminal output.',required=False,default=False,action='store_true')
176+
177+
# Extract to dictionary and assign to variables.
178+
args = vars(parser.parse_args())
179+
fim_dir_ms = args['fim_dir_ms']
180+
fim_dir_fr = args['fim_dir_fr']
181+
hucs = args['huc'].replace(' ', '').split(',')
182+
flows_files = args['flows_file'].replace(' ', '').split(',')
183+
num_workers = int(args['num_workers'])
184+
output_dir = args['ouput_dir']
185+
ouput_name = args['ouput_name']
186+
bin_raster = bool(args['bin_raster'])
187+
depth_raster = bool(args['depth_raster'])
188+
clean = bool(args['clean'])
189+
quiet = bool(args['quiet'])
190+
191+
assert num_workers >= 1, "Number of workers should be 1 or greater"
192+
assert len(flows_files) == len(hucs), "Number of hucs must be equal to the number of forecasts provided"
193+
assert not (bin_raster and depth_raster), "Cannot use both -b and -d flags"
194+
195+
# Create output directory if it does not exist
196+
if not os.path.isdir(output_dir):
197+
os.mkdir(output_dir)
198+
199+
# Create nested list for input into multi-threading
200+
arg_list = []
201+
for huc, flows_file in zip(hucs, flows_files):
202+
arg_list.append((fim_dir_ms, fim_dir_fr, huc, flows_file, output_dir, ouput_name, bin_raster, depth_raster, clean, quiet))
203+
204+
# Multi-thread for each huc in input hucs
205+
with Pool(processes=num_workers) as pool:
206+
# Run composite_inundation()
207+
pool.starmap(composite_inundation, arg_list)

0 commit comments

Comments
 (0)