Narragansett Bay: SFINCS Demo¶
This notebook demonstrates the full SFINCS workflow for Narragansett Bay, Rhode Island:
- QGIS Plugin: define the model domain and select discharge points
- Create the model from the AOI polygon
- Run the simulation with compound forcing (ocean boundary + river discharge + precipitation + wind + pressure)
- Visualize water level comparisons and the flood depth map
QGIS Plugin: Defining the Model Domain¶
Before building a SFINCS model, we need two inputs:
- An AOI polygon that defines the model boundary
- A discharge points file listing NWM flowlines that enter the domain
The NWM Coastal QGIS plugin provides an interactive workflow for creating both. Here is a step-by-step walkthrough.
Step 1: Install the Plugin¶
Install the NWM Coastal plugin from the QGIS Plugin Manager.

Step 2: Load the Basemap¶
Click "Add Basemap" in the toolbar to load National HydroFabric data (watershed divides, NWM flowlines, gages) and NOAA CO-OPS station locations. Set the minimum stream order to filter small tributaries.

Step 3: Explore the Data¶
The plugin adds several layers: OSM basemap, watershed divides, NWM flowpaths, USGS gages, NHF nexus points, and CO-OPS tide stations. Use the layers panel to toggle visibility and inspect the coastal area.

Step 4: Draw the AOI Polygon¶
Click "Draw Polygon" in the toolbar, then sketch the model domain by clicking vertices on the map. Right-click to finish. The polygon should cover the coastal area and extend offshore to capture the tidal boundary.

Step 5: Merge with Watershed Boundaries¶
Click "Union with NHF Divides" to snap the polygon boundary to watershed divide lines. This ensures the model domain aligns with hydrologic boundaries.

Step 6: Identify River Discharge Points¶
With the merged polygon in place, zoom in to find NWM flowlines that connect to the domain boundary. These flowlines will become river discharge sources in the model. The red arrows below highlight flowlines entering the merged polygon.

Step 7: Save and Export¶
Click "Save Polygon" to export the merged polygon as aoi.geojson
and the selected discharge points as discharge_nwm.geojson.
These two files are the inputs for the SFINCS model creation step below.
Tip: Drawing Refinement Regions¶
The same "Draw Polygon" tool can be used to define refinement
regions. A typical workflow is to first run the model without
refinement, inspect the results, identify areas that need higher
resolution (e.g., narrow channels or areas with steep gradients),
then draw a polygon around those areas and export it as a GeoJSON.
The exported polygon can then be passed to the grid.refinement
config to increase mesh resolution locally.
Setup¶
from __future__ import annotations
import os
from pathlib import Path
notebook_dir = Path.cwd() # assumes notebook is run from docs/examples/notebooks/
os.chdir(notebook_dir.parent / "narragansett-ri")
1. Create the SFINCS model¶
SfincsCreateConfig.from_dict builds a configuration from a plain
dictionary (same structure as the YAML file). The key settings are:
- grid: base resolution of 512 m with 3 levels of refinement near the coast
- elevation: merged from NWS 30 m coastal DEM and GEBCO bathymetry
- subgrid: 4x subgrid pixels with land-use-based Manning coefficients
- river_discharge: NWM flowlines exported by the QGIS plugin
- add_noaa_gages: automatically discover NOAA tide gauges in the domain
from coastal_calibration import SfincsCreateConfig, SfincsCreator, configure_logger
configure_logger(level="INFO")
create_config = SfincsCreateConfig.from_dict(
{
"aoi": "./aoi.geojson",
"output_dir": "./output",
"download_dir": "../downloads/narragansett_grid",
"grid": {
"resolution": 512, # base cell size in meters
"crs": "utm",
"rotated": False,
"refinement": [
{"polygon": "./aoi.geojson", "level": 3, "buffer_m": -200},
],
},
"elevation": {
"datasets": [
{
"name": "nws_30m",
"zmin": -20000,
"source": "nws_30m",
"coastal_domain": "atlgulf",
},
{"name": "gebco_15arcs", "zmin": -20000, "source": "gebco_15arcs"},
],
"buffer_cells": 1,
},
"mask": {"zmin": -50.0, "boundary_zmax": -1.0, "reset_bounds": True},
"subgrid": {
"nr_subgrid_pixels": 4,
"lulc_dataset": "esa_worldcover",
"manning_land": 0.04,
"manning_sea": 0.02,
},
"river_discharge": {
"flowlines": "./discharge_nwm.geojson", # from QGIS plugin
"nwm_id_column": "flowpath_id",
},
"add_noaa_gages": True,
}
)
Run the create workflow¶
creator = SfincsCreator(create_config)
result = creator.run()
if not result.success:
raise RuntimeError(f"Model creation failed at stage '{result.stages_failed}': {result.errors}")
print(result)
========================================
Coastal Calibration Workflow
Start Time: 2026-04-01 11:56:48
========================================
----------------------------------------
Stage: create_grid
Start Time: 2026-04-01 11:56:48
Create SFINCS grid from AOI
AOI:
docs/examples/narragansett-ri/aoi.geojson
Resolution: 512 m, CRS: utm
Refinement: 1 polygon(s), max level 3
Grid created successfully
[✓] COMPLETED (4s)
----------------------------------------
Stage: create_fetch_data
Start Time: 2026-04-01 11:56:53
Fetch elevation and land cover data for AOI
Reusing existing nws_30m.tif
Reusing existing gebco_15arcs.tif
Reusing existing esa_worldcover.tif
Fetched 3 dataset(s): ['nws_30m', 'gebco_15arcs', 'esa_worldcover']
[✓] COMPLETED (0s)
----------------------------------------
Stage: create_elevation
Start Time: 2026-04-01 11:56:53
Add elevation and bathymetry data
Elevation datasets: ['nws_30m', 'gebco_15arcs']
Elevation created successfully
[✓] COMPLETED (2s)
----------------------------------------
Stage: create_mask
Start Time: 2026-04-01 11:56:55
Create active cell mask
zmin=-50.0
Active mask created successfully
[✓] COMPLETED (0s)
----------------------------------------
Stage: create_boundary
Start Time: 2026-04-01 11:56:55
Create water level boundary cells
boundary_zmax=-1.0
Boundary cells created successfully
[✓] COMPLETED (0s)
----------------------------------------
Stage: create_discharge
Start Time: 2026-04-01 11:56:55
Add river discharge source points
Read 7 flowpath(s) from discharge_nwm.geojson
167237333: snapped to active cell (57 m away)
6130295: snapped to active cell (47 m away)
6130217: snapped to active cell (64 m away)
6130047: snapped to active cell (92 m away)
6128891: snapped to active cell (29 m away)
6119066: snapped to active cell (141 m away)
6119064: snapped to active cell (141 m away)
Wrote 7 discharge source location(s) to sfincs_nwm.src
[✓] COMPLETED (0s)
----------------------------------------
Stage: create_subgrid
Start Time: 2026-04-01 11:56:55
Create subgrid tables
nr_subgrid_pixels=4
Subgrid tables created successfully
[✓] COMPLETED (1m 8s)
----------------------------------------
Stage: create_obs
Start Time: 2026-04-01 11:58:04
Add observation points
Loading cached station metadata from cache/coops_stations_metadata.json
Fetching datum information for 4 station(s)
Fetching data from 4 station(s)
Added 4 NOAA CO-OPS observation point(s)
noaa_8447386: placed at face center z=-11.862 m (35 m from original)
noaa_8452660: placed at face center z=-2.211 m (39 m from original)
noaa_8452944: placed at face center z=-5.223 m (20 m from original)
noaa_8454000: placed at face center z=-1.995 m (192 m from original)
Snapped 4 observation point(s) to nearest wet cell
[✓] COMPLETED (0s)
----------------------------------------
Stage: create_write
Start Time: 2026-04-01 11:58:05
Write SFINCS model to disk
Output directory:
docs/examples/narragansett-ri/output
Model written successfully
[✓] COMPLETED (0s)
----------------------------------------
========================================
Workflow COMPLETED | Total Duration: 0:01:16
========================================
Timing Summary:
----------------------------------------
[✓] create_grid: 4s
[✓] create_fetch_data: 0s
[✓] create_elevation: 2s
[✓] create_mask: 0s
[✓] create_boundary: 0s
[✓] create_discharge: 0s
[✓] create_subgrid: 1m 8s
[✓] create_obs: 0s
[✓] create_write: 0s
WorkflowResult: SUCCESS Start: 2026-04-01 11:56:48 End: 2026-04-01 11:58:05 Duration: 1m 16s Completed: create_grid, create_fetch_data, create_elevation, create_mask, create_boundary, create_discharge, create_subgrid, create_obs, create_write
Inspect the created model¶
output = Path("output")
assert output.exists(), (
f"Output directory not found: {output.resolve()} — run the create step first."
)
for f in sorted(output.iterdir()):
if f.name.startswith(".") or f.suffix == ".log":
continue
size = f.stat().st_size
label = f"{size / 1e6:.1f} MB" if size > 1e6 else f"{size / 1e3:.1f} KB"
print(f" {f.name:<30s} {label}")
create_progress.json 2.2 KB create_result.json 1.3 KB gis 0.1 KB obs_station_map.json 0.5 KB sfincs.inp 0.9 KB sfincs.nc 60.8 MB sfincs.obs 0.2 KB sfincs_nwm.src 0.2 KB sfincs_subgrid.nc 92.1 MB subgrid 0.2 KB
2. Run the simulation pipeline¶
The run configuration specifies:
- Simulation period: 60 hours starting 2024-01-09
- Forcing sources: NWM analysis for meteorology, STOFS for ocean boundary
- Compound forcing: ocean boundary + river discharge + precipitation + wind + barometric pressure
- Flood depth map: downscaled to the 30 m NWS DEM
from coastal_calibration import CoastalCalibConfig, CoastalCalibRunner
run_config = CoastalCalibConfig.from_dict(
{
"model": "sfincs",
"simulation": {
"start_date": "2024-01-09",
"duration_hours": 60,
"coastal_domain": "atlgulf",
"meteo_source": "nwm_ana",
},
"boundary": {"source": "stofs"},
"paths": {
"work_dir": "./run",
"raw_download_dir": "../downloads",
},
"download": {"enabled": True},
"model_config": {
"prebuilt_dir": "./output",
"discharge_locations_file": "./output/sfincs_nwm.src",
"merge_discharge": True,
"forcing_to_mesh_offset_m": 0.0, # STOFS already in NAVD88
"vdatum_mesh_to_msl_m": 0.1, # NAVD88 mesh -> MSL
"include_precip": True,
"include_wind": True,
"include_pressure": True,
"floodmap_dem": "../downloads/narragansett_grid/nws_30m.tif",
"inp_overrides": {
"tspinup": 10800, # 3-hour spinup
},
},
}
)
Run the pipeline¶
The pipeline executes 14 stages: download forcing data, build the SFINCS input files (timing, forcing, discharge, precipitation, wind, pressure), run the model, generate the flood depth map, and produce comparison plots against NOAA observations.
runner = CoastalCalibRunner(run_config)
result = runner.run()
if not result.success:
raise RuntimeError(f"Model run failed at stage '{result.stages_failed}': {result.errors}")
print(result)
Cleaned generated files from
docs/examples/narragansett-ri/run
========================================
Coastal Calibration Workflow
Start Time: 2026-04-01 11:58:05
========================================
----------------------------------------
Stage: download
Start Time: 2026-04-01 11:58:05
Download input data (NWM, STOFS)
meteo/nwm_ana: 61/61 [OK]
hydro/nwm: 61/61 [OK]
coastal/stofs: 1/1 [OK]
Total: 123/123 (failed: 0)
Download complete — raw files stored in
docs/examples/downloads
[✓] COMPLETED (7s)
----------------------------------------
Stage: sfincs_symlinks
Start Time: 2026-04-01 11:58:13
Create .nc symlinks for NWM data
Skipped 213 meteo + 0 streamflow symlinks (already exist)
[✓] COMPLETED (0s)
----------------------------------------
Stage: sfincs_data_catalog
Start Time: 2026-04-01 11:58:13
Generate HydroMT data catalog for SFINCS
Data catalog written to
docs/examples/narragansett-ri/run/data_cat
alog.yml
[✓] COMPLETED (0s)
----------------------------------------
Stage: sfincs_init
Start Time: 2026-04-01 11:58:13
Initialize SFINCS model (pre-built)
Copied pre-built model from
docs/examples/narragansett-ri/output to
docs/examples/narragansett-ri/run/sfincs_m
odel
Removed stale output files: sfincs_netbndbzsbzifile.nc, sfincs_netamuv.nc,
sfincs_netamp.nc, sfincs_netampr.nc, sfincs_map.nc, sfincs_his.nc
SFINCS model initialized (grid_type=quadtree) at
docs/examples/narragansett-ri/run/sfincs_m
odel
[✓] COMPLETED (0s)
----------------------------------------
Stage: sfincs_timing
Start Time: 2026-04-01 11:58:13
Set SFINCS timing
Spinup: 3600 s
Timing set: 2024-01-09 00:00:00 to 2024-01-11 12:00:00
[✓] COMPLETED (0s)
----------------------------------------
Stage: sfincs_forcing
Start Time: 2026-04-01 11:58:13
Add water level forcing
Read 200 boundary point(s) from
docs/examples/narragansett-ri/run/sfincs_m
odel/sfincs.bnd
Loaded stofs_waterlevel: time=61, node=60857
Interpolated stofs_waterlevel to 200 boundary points (61 time steps)
Wrote boundary forcing to sfincs_netbndbzsbzifile.nc
Water level forcing added from stofs_waterlevel
[✓] COMPLETED (17s)
----------------------------------------
Stage: sfincs_discharge
Start Time: 2026-04-01 11:58:31
Add discharge sources
Added 7 discharge source point(s) from
docs/examples/narragansett-ri/output/sfinc
s_nwm.src
Assigned discharge timeseries to 7 point(s) (7 unique feature_id(s))
[✓] COMPLETED (4s)
----------------------------------------
Stage: sfincs_precip
Start Time: 2026-04-01 11:58:36
Add precipitation forcing
Precipitation forcing added from nwm_ana_meteo (res=512 m)
[✓] COMPLETED (2s)
----------------------------------------
Stage: sfincs_wind
Start Time: 2026-04-01 11:58:38
Add wind forcing
Wind forcing added from nwm_ana_meteo (res=512 m)
[✓] COMPLETED (2s)
----------------------------------------
Stage: sfincs_pressure
Start Time: 2026-04-01 11:58:41
Add atmospheric pressure forcing
Atmospheric pressure forcing added from nwm_ana_meteo (baro=1, res=512 m)
[✓] COMPLETED (1s)
----------------------------------------
Stage: sfincs_write
Start Time: 2026-04-01 11:58:43
Write SFINCS model
Applied 1 sfincs.inp override(s): {'tspinup': 10800}
SFINCS model written to
docs/examples/narragansett-ri/run/sfincs_m
odel
[✓] COMPLETED (0s)
----------------------------------------
Stage: sfincs_run
Start Time: 2026-04-01 11:58:43
Run SFINCS model
Running SFINCS via native executable:
.pixi/envs/dev/bin/sfincs
SFINCS run completed
[✓] COMPLETED (7m 48s)
----------------------------------------
Stage: sfincs_floodmap
Start Time: 2026-04-01 12:06:32
Downscale flood depth map
Loaded zsmax from
docs/examples/narragansett-ri/run/sfincs_m
odel/sfincs_map.nc
Creating index COG:
docs/examples/narragansett-ri/run/sfincs_m
odel/floodmap_index.tif
Index COG created (0.1 MB)
Downscaling flood depth map
GeoTIFF has no overviews, building them: floodmap_hmax.tif
Building 1 overview levels: [2]
Flood depth map written:
docs/examples/narragansett-ri/run/sfincs_m
odel/floodmap_hmax.tif (0.4 MB)
Flood depth map:
docs/examples/narragansett-ri/run/sfincs_m
odel/floodmap_hmax.tif
[✓] COMPLETED (1s)
----------------------------------------
Stage: sfincs_plot
Start Time: 2026-04-01 12:06:33
Plot simulated vs observed water levels
Loading cached station metadata from cache/coops_stations_metadata.json
Fetching datum information for 4 station(s)
Fetching data from 4 station(s)
Matched 4 observation point(s) to NOAA station(s): 8447386, 8452660, 8452944,
8454000
Applied mesh→MSL vdatum offset: +0.1000 m
Loading cached station metadata from cache/coops_stations_metadata.json
Requesting water_level data for 4 station(s) from 20240109 00:00 to 20240111
12:00
Fetching data from 4 station(s)
Successfully retrieved data for 4 station(s)
Loading cached station metadata from cache/coops_stations_metadata.json
Fetching datum information for 4 station(s)
Fetching data from 4 station(s)
Saved 1 comparison figure(s) to
docs/examples/narragansett-ri/run/sfincs_m
odel/figs
[✓] COMPLETED (4s)
----------------------------------------
========================================
Workflow COMPLETED | Total Duration: 0:08:32
========================================
Timing Summary:
----------------------------------------
[✓] download: 7s
[✓] sfincs_symlinks: 0s
[✓] sfincs_data_catalog: 0s
[✓] sfincs_init: 0s
[✓] sfincs_timing: 0s
[✓] sfincs_forcing: 17s
[✓] sfincs_discharge: 4s
[✓] sfincs_precip: 2s
[✓] sfincs_wind: 2s
[✓] sfincs_pressure: 1s
[✓] sfincs_write: 0s
[✓] sfincs_run: 7m 48s
[✓] sfincs_floodmap: 1s
[✓] sfincs_plot: 4s
WorkflowResult: SUCCESS Start: 2026-04-01 11:58:05 End: 2026-04-01 12:06:37 Duration: 8m 32s Completed: download, sfincs_symlinks, sfincs_data_catalog, sfincs_init, sfincs_timing, sfincs_forcing, sfincs_discharge, sfincs_precip, sfincs_wind, sfincs_pressure, sfincs_write, sfincs_run, sfincs_floodmap, sfincs_plot
3. View results¶
The pipeline compares modeled water levels against NOAA CO-OPS tide gauge observations at stations within the domain.
from IPython.display import Image, display
figs_dir = Path("run/sfincs_model/figs")
assert figs_dir.exists(), f"Results not found: {figs_dir.resolve()} — run the pipeline first."
for png in sorted(figs_dir.glob("stations_comparison_*.png")):
display(Image(filename=str(png), width=800))
4. SFINCS mesh¶
The SFINCS model uses a quadtree grid. Coarser cells (512 m) cover the offshore domain while regions near the coastline and inside the bay are refined to higher resolution.
from coastal_calibration.plotting import SfincsGridInfo, plot_mesh
info = SfincsGridInfo.from_model_root("run/sfincs_model")
print(info)
SfincsGridInfo(quadtree, EPSG:32619) Faces: 283,608 Edges: 569,283 Level 1: 3,269 cells (512 m) Level 2: 1,267 cells (256 m) Level 3: 2,704 cells (128 m) Level 4: 276,368 cells (64 m)
fig, ax = plot_mesh(info, title="Narragansett Bay SFINCS mesh")
5. Flood depth map¶
The pipeline produces a downscaled flood depth map when
floodmap_dem is configured. The process has three steps:
- Read
zsmax: extract the maximum water surface elevation over the simulation period fromsfincs_map.nc - Build an index COG: for each pixel in the high-resolution DEM,
find the SFINCS grid cell it falls in and store the mapping as a
GeoTIFF (
floodmap_index.tif). This index is reusable across runs with the same grid. - Compute depth: for each DEM pixel, look up the
zsmaxvalue via the index and subtract the DEM elevation. Pixels with depth below 5 cm are masked out. The result is written as a Cloud Optimized GeoTIFF at the DEM resolution (30 m in this case).
from coastal_calibration.plotting import plot_floodmap
fig, ax = plot_floodmap(
"run/sfincs_model/floodmap_hmax.tif",
title="Max water depth, Narragansett Bay, RI",
)
fig.savefig("../images/narragansett_thumb.png", dpi=150, bbox_inches="tight")
Summary¶
This notebook demonstrated the full SFINCS workflow:
- QGIS Plugin: drew the AOI polygon and identified NWM discharge points
- Model Creation: built a quadtree mesh with elevation, subgrid,
and river discharge sources using
SfincsCreator - Simulation: ran the pipeline with compound forcing (ocean + river +
meteo) and validated against NOAA observations using
CoastalCalibRunner - Visualization: inspected the quadtree mesh and flood depth map