RA2CE Feature: Adaptation measures#
This notebook demonstrates how to define and evaluate adaptation options in RA2CE. An adaptation option represents a physical intervention on the road network (e.g. raising the road level, applying flood-resistant surfacing) that changes the damage a road segment sustains for a given flood depth.
The notebook covers three steps:
Network setup — build and overlay the road network with a hazard map.
Analysis configuration — define damage functions and adaptation options.
Cost-Benefit Analysis (CBA) — compare adaptation options by computing the benefit-cost ratio for each road segment over a defined time horizon.
What is a Cost-Benefit Analysis?#
A Cost-Benefit Analysis (CBA) is a method for comparing the total expected costs of an intervention against the total expected benefits, both expressed in present-day monetary terms.
In the context of road network adaptation:
Costs are the discounted construction and maintenance expenditures of implementing the adaptation measure (e.g. flood-proofing a road), expressed per metre of road.
Benefits are the discounted avoided damages: the reduction in flood damage that would have occurred without the adaptation, cumulated over the analysis time horizon.
The key output is the benefit-cost ratio (BCR):
BCR > 1 → the intervention pays off: avoided damages exceed its costs.
BCR < 1 → the intervention costs more than it saves.
BCR = 1 → break-even point.
Future costs and benefits are discounted back to present value using the discount_rate parameter, and the increasing likelihood of flood events under climate change is captured by the climate_factor.
[11]:
from pathlib import Path
import geopandas as gpd
from ra2ce.analysis.analysis_config_data.analysis_config_data import (
AnalysisConfigData,
AnalysisSectionAdaptation,
AnalysisSectionAdaptationOption,
AnalysisSectionDamages,
)
from ra2ce.analysis.analysis_config_data.enums.analysis_damages_enum import AnalysisDamagesEnum
from ra2ce.analysis.analysis_config_data.enums.analysis_enum import AnalysisEnum
from ra2ce.analysis.analysis_config_data.enums.analysis_losses_enum import AnalysisLossesEnum
from ra2ce.analysis.analysis_config_data.enums.damage_curve_enum import DamageCurveEnum
from ra2ce.analysis.analysis_config_data.enums.event_type_enum import EventTypeEnum
from ra2ce.network.network_config_data.enums.aggregate_wl_enum import AggregateWlEnum
from ra2ce.network.network_config_data.enums.road_type_enum import RoadTypeEnum
from ra2ce.network.network_config_data.enums.source_enum import SourceEnum
from ra2ce.network.network_config_data.network_config_data import (
HazardSection,
NetworkConfigData,
NetworkSection,
)
from ra2ce.ra2ce_handler import Ra2ceHandler
[12]:
root_dir = Path("data", "adaptation")
static_path = root_dir.joinpath("static")
hazard_path =static_path.joinpath("hazard")
network_path = static_path.joinpath("network")
output_path=root_dir.joinpath("output")
input_path = root_dir.joinpath("input") # path of the data files for all adaptation options and reference option
Step 1 — Build the network and overlay the hazard map#
The network must first be configured and overlaid with a flood hazard raster. The current adaptation workflow has the following requirements:
``aggregate_wl`` must be set to
AggregateWlEnum.MEAN— the mean water level per edge is used as the damage driver.``source`` must be
SourceEnum.OSM_DOWNLOADorSourceEnum.SHAPEFILE— the network is built from OpenStreetMap or a local shapefile.Hazard file naming — adaptation is event-based (single hazard map). The hazard filename must not start with
"RP", as that prefix triggers the risk-based (return-period) damage workflow instead.
We run handler.configure() in this step to build and cache the network before defining the analysis. This avoids rebuilding the network when the analysis configuration changes.
[13]:
_network_section = NetworkSection(
source= SourceEnum.OSM_DOWNLOAD,
polygon= network_path.joinpath("polygon_jacksonvill_beach.shp"),
road_types=[
RoadTypeEnum.SECONDARY,
RoadTypeEnum.SECONDARY_LINK,
RoadTypeEnum.PRIMARY,
RoadTypeEnum.PRIMARY_LINK,
RoadTypeEnum.TRUNK,
RoadTypeEnum.MOTORWAY,
RoadTypeEnum.MOTORWAY_LINK,
RoadTypeEnum.RESIDENTIAL,
RoadTypeEnum.UNCLASSIFIED,
],
save_gpkg=True,
reuse_network_output=True,
)
_hazard = HazardSection(
hazard_map=[Path(file) for file in hazard_path.glob("*.tif")],
hazard_field_name= ["waterdepth"],
aggregate_wl = AggregateWlEnum.MEAN,
hazard_crs = "EPSG:4326",
)
_network_config_data = NetworkConfigData(
root_path=root_dir,
static_path=static_path,
network=_network_section,
hazard=_hazard
)
[14]:
handler = Ra2ceHandler.from_config(_network_config_data, None)
handler.configure()
Step 2 — Configure damages and adaptation options#
Damages#
The damage configuration is shared across all adaptation options. It defines the damage model used to translate flood depth on each road segment into a monetary damage value.
Two constraints apply here:
``damage_curve`` must be
DamageCurveEnum.MAN(manual curve). Adaptation effects are modelled by modifying the damage-depth curve per option, so only user-supplied curves are supported.``event_type`` must be
EventTypeEnum.EVENT, since adaptation is event-based (single hazard map, not a return-period series).
[ ]:
_damages_section = AnalysisSectionDamages(
analysis=AnalysisDamagesEnum.DAMAGES,
event_type=EventTypeEnum.EVENT,
damage_curve=DamageCurveEnum.MAN,
save_gpkg=True,
save_csv=True,
)
Adaptation options#
Adaptation options are defined in two parts:
``AnalysisSectionAdaptation`` — global CBA parameters shared by all options:
discount_rate— annual discount rate to account for inflation (e.g.0.025= 2.5 %).initial_frequency— yearly frequency of occurrence of the hazard event at year 0 (e.g.0.05= once every 20 years).climate_factor— annual increase in event frequency under climate change.time_horizon— number of years over which the CBA is evaluated.
``adaptation_options`` — a list of
AnalysisSectionAdaptationOptionobjects:The first entry (
AO0) represents the reference (business-as-usual) case and only requires anidandname.Each subsequent entry represents an intervention and additionally requires:
construction_cost— cost per metre of road at construction time.construction_interval— years between reconstructions.maintenance_cost— annual cost per metre of road.maintenance_interval— years between maintenance cycles.
[ ]:
# - adaptation
_adaptation_options = [
AnalysisSectionAdaptationOption(
id="AO0",
name="No adaptation",
),
AnalysisSectionAdaptationOption(
id="AO1",
name="Cheap construction, expensive maintenance",
construction_cost=1000.0, # this is the cost per meter
construction_interval=20.0,
maintenance_cost=0.0, # this is cost per meter per year
maintenance_interval=0.0,
),
]
_adaptation_section = AnalysisSectionAdaptation(
analysis=AnalysisEnum.ADAPTATION,
adaptation_options=_adaptation_options,
discount_rate=0.025, # correcting inflation 0.025 = 2.5%
initial_frequency=0.05, # yearly frequency of occurrence of the event (hazard map)
climate_factor=0.007, # factor to correct for the positive increase of the frequency of occurrence of the event
time_horizon=20, # time horizon in years for the CBA analysis
save_gpkg=True,
save_csv=True,
)
_analysis_config_data = AnalysisConfigData(
input_path=input_path,
static_path=static_path,
output_path=output_path,
analyses=[
_damages_section,
_adaptation_section,
],
aggregate_wl=AggregateWlEnum.MEAN,
)
Required input folder structure#
Each adaptation option needs its own sub-folder under input/ named after its id. The folder must contain the damage function files used to calculate road damage for that option. For this example (AO0 = reference, AO1 = one adaptation option) the expected layout is:
root_dir/
├── input/
│ ├── AO0/ ← reference (no adaptation)
│ │ └── damages/
│ │ └── input/
│ │ └── damages_function/
│ │ └── all_road_types/
│ │ ├── hazard_severity_damage_fraction.csv
│ │ └── max_damage_road_types.csv
│ └── AO1/ ← adaptation option 1
│ └── damages/
│ └── input/
│ └── damages_function/
│ └── all_road_types/
│ ├── hazard_severity_damage_fraction.csv
│ └── max_damage_road_types.csv
├── static/
│ ├── hazard/
│ ├── network/
│ └── output_graph/
└── output/
AO1 uses a different hazard_severity_damage_fraction.csv than AO0 to represent the reduced vulnerability of the adapted road surface.
Step 3 — Run the analysis#
Now that the network is already built (Step 1), we can run the full analysis. Because reuse_network_output=True was set in the network section, RA2CE loads the cached network from disk instead of rebuilding it from OSM.
[ ]:
handler = Ra2ceHandler.from_config(_network_config_data, _analysis_config_data)
handler.configure()
handler.run_analysis()
Step 4 — Inspect results#
The output GeoPackage contains one row per road segment. Key output columns are:
Column |
Description |
|---|---|
|
Benefit-cost ratio for option 1. Values > 1 mean avoided damages outweigh intervention costs over the time horizon. |
|
Total discounted avoided damages (€/m) over the time horizon. |
|
Total discounted construction and maintenance costs (€/m) over the time horizon. |
[15]:
output_gdf = gpd.read_file(output_path / "adaptation.gpkg")
output_gdf.head()
[15]:
| rfid | merge_key | AO0_damages | AO0_event_impact | AO0_net_impact | AO1_cost | AO1_damages | AO1_event_impact | AO1_net_impact | AO1_benefit | AO1_bc_ratio | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 168 | 168 | 0.0 | 0.0 | 0.000000 | 249653.0 | 0.0 | 0.0 | 0.00000 | 0.000000 | 0.000000 | LINESTRING (-81.40705 30.28579, -81.40707 30.2... |
| 1 | 2 | 2 | 0.0 | 0.0 | 0.000000 | 140883.0 | 0.0 | 0.0 | 0.00000 | 0.000000 | 0.000000 | LINESTRING (-81.40707 30.28804, -81.40680 30.2... |
| 2 | 1034 | 1034 | 0.0 | 0.0 | 0.000000 | 148722.0 | 0.0 | 0.0 | 0.00000 | 0.000000 | 0.000000 | LINESTRING (-81.40862 30.28803, -81.40799 30.2... |
| 3 | 891 | 891 | 0.0 | 0.0 | 0.000000 | 251426.0 | 0.0 | 0.0 | 0.00000 | 0.000000 | 0.000000 | LINESTRING (-81.40452 30.28621, -81.40455 30.2... |
| 4 | 4 | 4 | 167315.0 | 167315.0 | 296160.756359 | 289492.0 | 56797.0 | 56797.0 | 100535.17305 | 195625.583308 | 0.675755 | LINESTRING (-81.40561 30.28807, -81.40538 30.2... |
[ ]:
output_gdf.explore(
column="AO1_bc_ratio",
tiles="CartoDB positron",
cmap="RdYlGn",
legend=True,
)
[ ]: