📘 Example: Creating a Historical Event in FloodAdapt

This notebook demonstrates how to create a historical event using FloodAdapt. Historical events are valuable for validation, controlled testing, sensitivity analysis, and what-if scenarios based on historic events.

A FloodAdapt Event consists of 2 things:

In this example, we construct a full historical event with water level, rainfall, wind, and river discharge forcings, and then save it to a FloodAdapt database.

⏱️ Step 1. Setup and Imports

We begin by importing the required classes and modules for constructing forcings and managing event data within the flood_adapt framework.

import flood_adapt.objects.forcing as f

from pathlib import Path
from datetime import datetime

from flood_adapt.objects import HistoricalEvent, TimeFrame
from flood_adapt import unit_system as us
from flood_adapt import FloodAdapt, Settings

# Setup FloodAdapt
STATIC_DATA_DIR = Path("../../_data/examples/static-data").resolve()
settings = Settings(
    DATABASE_ROOT=Path("../../_data/examples").resolve(),
    DATABASE_NAME="charleston_test"
)
fa = FloodAdapt(database_path=settings.database_path)

🗓️ Step 2. Define the Simulation Time Frame

We specify a one-day time frame for the historical event, from January 1 to January 2, 2025.

# Create an time frame for the simulation
start_time = datetime(year=2020, month=1, day=1)
end_time = datetime(year=2020, month=1, day=2)
time_frame = TimeFrame(start_time=start_time, end_time=end_time)

🌊 Step 3. Define Water Level Forcing

Historical water levels can be included in 3 few ways:

  • Tide Gauge: Measured water levels downloaded from NOAA CO-OPS. The automated download is only available for stations in the US. Outside the US, water level records can be stored in the FloodAdapt database. This avoids having to import the csv for each event, see below.
  • CSV: Custom water levels specified in a csv file (perhaps from a tide gauge not connected to the noaa_coops API)
  • Model: Generate the storm surge to be used for the overland simulation by running an offshore model. For historic events, this model uses weather re-analysis data (wind and pressure) from the NOAA GFS model.
# Recorded water levels from a CSV file
csv_file = STATIC_DATA_DIR / "tide.csv"
water_levels = f.WaterlevelCSV(path=csv_file)
wl_df = water_levels.to_dataframe(time_frame=time_frame)

# Alternative: Water levels downloaded from a tide gauge
tide_gauge = fa.database.site.sfincs.tide_gauge
df_tide_gauge = tide_gauge.get_waterlevels_in_time_frame(
    time=time_frame,
)
water_levels_gauged = f.WaterlevelGauged()

# Alternative: Water levels simulated by an offshore model
water_levels_from_offshore = f.WaterlevelModel()

# Inspect
wl_df.plot(
    title="Water Level from CSV",
    xlabel="Time",
    ylabel="Water Level (m)",
    legend=False,
    figsize=(5, 2)
)

df_tide_gauge.plot(
    title="Water Level from Tide Gauge",
    xlabel="Time",
    ylabel="Water Level (m)",
    legend=False,
    figsize=(5, 2)
)

🧩 Step 4. Create a minimal event and modify it

Given a water level forcing, and a TimeFrame, you can create the simplest possible event in FloodAdapt as shown below.

In many cases, it is interesting to to investigate a combination of different forcings. In steps 5 - 9, we will show the creation of various forcings and how to add them to an event.

simple_event = HistoricalEvent(
    name="simple_event",
    time=time_frame,
    forcings={
        f.ForcingType.WATERLEVEL: [water_levels], 
        # or [water_levels_gauged], [water_levels_from_offshore]
    }
)

🌧️ Step 5. Define Meteo Forcing

Historic events have several options to define wind and rainfall, see also Synthteic event. Additionally, FloodAdapt provides an easy connection to NOAA GFS model re-analysis data for rainfall and wind that is automatically downloaded based on the event’s ‘TimeFrame’.

meteo_dataset = f.MeteoHandler().read(time_frame)
print(meteo_dataset) # TODO make sure the meteo files are already downloaded in the database to circumvent flaky noaa coops API calls
Requested meteo data already available
<xarray.Dataset> Size: 485kB
Dimensions:      (time: 9, lon: 41, lat: 41)
Coordinates:
  * time         (time) datetime64[ns] 72B 2020-01-01 ... 2020-01-02
  * lon          (lon) float32 164B -90.0 -89.5 -89.0 ... -71.0 -70.5 -70.0
  * lat          (lat) float32 164B 23.0 23.5 24.0 24.5 ... 41.5 42.0 42.5 43.0
    spatial_ref  int32 4B 0
Data variables:
    wind10_u     (time, lat, lon) float64 121kB -5.165 -5.625 ... 11.64 11.98
    wind10_v     (time, lat, lon) float64 121kB -5.967 -5.257 ... -0.8354 -1.565
    press_msl    (time, lat, lon) float64 121kB 1.017e+05 ... 1.004e+05
    precip       (time, lat, lon) float64 121kB 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0

Visualizing Meteo data

It can be difficult to visualize spatially varying timeseries data. So, below is a simple animation generator to do some basic data checks on the downloaded NOAA GFS data.

Choose any of the available timeseries data variables and generate the animation.

import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML

to_plot = 'press_msl' # available variables: 'wind10_u' or 'wind10_v' or 'press_msl' or 'precip'

var = meteo_dataset[to_plot] 
fig, ax = plt.subplots()
plot = var.isel(time=0).plot(ax=ax, cmap='viridis', add_colorbar=True)

def update(frame):
    ax.clear()
    var.isel(time=frame).plot(ax=ax, cmap='viridis', add_colorbar=False)
    ax.set_title(f'Time: {str(var.time[frame].values)}')

ani = animation.FuncAnimation(fig, update, frames=len(var.time), interval=200)
plt.close(fig)

HTML(ani.to_jshtml()) # TODO look at hvplot or other options to display the animation

🌬️ Step 6. Define Meteo Forcings

To use the downloaded NOAA’s GFS hindcast data in FloodAdapt, you need to create Meteo forcings and add them to your event.

Under the hood, the meteo forcings use the MeteoHandler to download the data, and then return slices of that dataset.

rainfall = f.RainfallMeteo()
wind = f.WindMeteo()

🏞️ Step 7. Define River Discharge Forcing

Discharge is required to be defined for the pre-configured river(s). These rivers must be registered in the hazard model configuration beforehand.

# The available rivers are defined in the hazard model when creating the database.
# You cannot add new rivers to the model in an event
# You can only set the discharge of each given river.
print(f"Number of available rivers: {len(fa.database.site.sfincs.river)}")

river = fa.database.site.sfincs.river[0]

discharge_constant = f.DischargeConstant(
    river=river,
    discharge=us.UnitfulDischarge(value=100, units=us.UnitTypesDischarge.cms)
)

# Inspect
df = discharge_constant.to_dataframe(time_frame=time_frame)
df.plot(
    title="Constant Discharge River",
    xlabel="Time",
    ylabel="Discharge (cms)",
    legend=True,
    figsize=(5, 2)
)
Number of available rivers: 1

🧩 Step 8. Combine Forcings and Create Event

All defined forcings are collected into a single dictionary, which is used to construct a HistoricalEvent.

NOTE: each event can only have 1 forcing of the types: water level, rainfall and wind. For discharge however, each river is required to have a forcing associated with it.

# Create a HistoricalEvent with the forcings and time frame
event = HistoricalEvent(
    name="example_historical_event",
    time=time_frame,
    forcings={
        f.ForcingType.WATERLEVEL: [water_levels], 
        # or one of `water_levels_gauged` or `water_levels_from_offshore`,
        f.ForcingType.RAINFALL: [rainfall],
        f.ForcingType.WIND: [wind],
        f.ForcingType.DISCHARGE: [discharge_constant],
    },
)

💾 Step 9. Save the Event to a FloodAdapt Database

Finally, we save the event to a FloodAdapt database.

# Save the event to the database
fa.save_event(event=event)
Back to top