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
= Path("../../_data/examples/static-data").resolve()
STATIC_DATA_DIR = Settings(
settings =Path("../../_data/examples").resolve(),
DATABASE_ROOT="charleston_test"
DATABASE_NAME
)= FloodAdapt(database_path=settings.database_path) fa
📘 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:
- a TimeFrame describing the start and end time of the hazard simulation(s)
- a collection of forcings to be applied to the hazard model(s)
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.
🗓️ 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
= datetime(year=2020, month=1, day=1)
start_time = datetime(year=2020, month=1, day=2)
end_time = TimeFrame(start_time=start_time, end_time=end_time) time_frame
🌊 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
= STATIC_DATA_DIR / "tide.csv"
csv_file = f.WaterlevelCSV(path=csv_file)
water_levels = water_levels.to_dataframe(time_frame=time_frame)
wl_df
# Alternative: Water levels downloaded from a tide gauge
= fa.database.site.sfincs.tide_gauge
tide_gauge = tide_gauge.get_waterlevels_in_time_frame(
df_tide_gauge =time_frame,
time
)= f.WaterlevelGauged()
water_levels_gauged
# Alternative: Water levels simulated by an offshore model
= f.WaterlevelModel()
water_levels_from_offshore
# Inspect
wl_df.plot(="Water Level from CSV",
title="Time",
xlabel="Water Level (m)",
ylabel=False,
legend=(5, 2)
figsize
)
df_tide_gauge.plot(="Water Level from Tide Gauge",
title="Time",
xlabel="Water Level (m)",
ylabel=False,
legend=(5, 2)
figsize )
🧩 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.
= HistoricalEvent(
simple_event ="simple_event",
name=time_frame,
time={
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’.
= f.MeteoHandler().read(time_frame)
meteo_dataset 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
= 'press_msl' # available variables: 'wind10_u' or 'wind10_v' or 'press_msl' or 'precip'
to_plot
= meteo_dataset[to_plot]
var = plt.subplots()
fig, ax = var.isel(time=0).plot(ax=ax, cmap='viridis', add_colorbar=True)
plot
def update(frame):
ax.clear()=frame).plot(ax=ax, cmap='viridis', add_colorbar=False)
var.isel(timef'Time: {str(var.time[frame].values)}')
ax.set_title(
= animation.FuncAnimation(fig, update, frames=len(var.time), interval=200)
ani
plt.close(fig)
# TODO look at hvplot or other options to display the animation HTML(ani.to_jshtml())
🌬️ 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.
= f.RainfallMeteo()
rainfall = f.WindMeteo() wind
🏞️ 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)}")
= fa.database.site.sfincs.river[0]
river
= f.DischargeConstant(
discharge_constant =river,
river=us.UnitfulDischarge(value=100, units=us.UnitTypesDischarge.cms)
discharge
)
# Inspect
= discharge_constant.to_dataframe(time_frame=time_frame)
df
df.plot(="Constant Discharge River",
title="Time",
xlabel="Discharge (cms)",
ylabel=True,
legend=(5, 2)
figsize )
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
andwind
. Fordischarge
however, each river is required to have a forcing associated with it.
# Create a HistoricalEvent with the forcings and time frame
= HistoricalEvent(
event ="example_historical_event",
name=time_frame,
time={
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
=event) fa.save_event(event