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:

  1. Network setup — build and overlay the road network with a hazard map.

  2. Analysis configuration — define damage functions and adaptation options.

  3. 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):

\[\text{BCR} = \frac{\text{Total discounted benefits}}{\text{Total discounted costs}}\]
  • 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_DOWNLOAD or SourceEnum.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:

  1. ``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.

  2. ``adaptation_options`` — a list of AnalysisSectionAdaptationOption objects:

    • The first entry (AO0) represents the reference (business-as-usual) case and only requires an id and name.

    • 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

AO1_bc_ratio

Benefit-cost ratio for option 1. Values > 1 mean avoided damages outweigh intervention costs over the time horizon.

AO1_benefits

Total discounted avoided damages (€/m) over the time horizon.

AO1_costs

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,
)

Make this Notebook Trusted to load map: File -> Trust Notebook
[ ]: