Manual Damage Curves Tutorial#
This is particularly useful when:
You have local historical damage data.
You want to account for specific infrastructure types.
You need to test alternative vulnerability scenarios.
Step 1: Define project paths#
As usual, define your project folder and subdirectories:
[1]:
from pathlib import Path
root_dir = Path("data", "damage_manual")
static_path = root_dir.joinpath("static")
hazard_path = static_path.joinpath("hazard")
network_path = static_path.joinpath("network")
output_path = root_dir.joinpath("output")
Step 2: Configure the road network and hazard#
polygon.geojson
).[2]:
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.enums.network_type_enum import NetworkTypeEnum
from ra2ce.network.network_config_data.network_config_data import NetworkSection
from ra2ce.ra2ce_handler import Ra2ceHandler
network_section = NetworkSection(
network_type=NetworkTypeEnum.DRIVE,
source=SourceEnum.OSM_DOWNLOAD,
polygon=static_path.joinpath("polygon.geojson"),
save_gpkg=True,
road_types=[
RoadTypeEnum.SECONDARY,
RoadTypeEnum.SECONDARY_LINK,
RoadTypeEnum.PRIMARY,
RoadTypeEnum.PRIMARY_LINK,
RoadTypeEnum.TRUNK,
RoadTypeEnum.MOTORWAY,
RoadTypeEnum.MOTORWAY_LINK,
],
)
c:\Users\hauth\miniforge3\envs\ra2ce_env\Lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
[3]:
from ra2ce.network.network_config_data.enums.aggregate_wl_enum import AggregateWlEnum
from ra2ce.network.network_config_data.network_config_data import HazardSection
hazard_section = HazardSection(
hazard_map=[Path(file) for file in hazard_path.glob("*.tif")],
aggregate_wl=AggregateWlEnum.MEAN, # mean water depth used in analysis
hazard_crs="EPSG:4326", # ensure hazard map is in EPSG:4326 projection
)
We combine the network and hazard information into a single configuration object.
[4]:
from ra2ce.network.network_config_data.network_config_data import NetworkConfigData
network_config_data = NetworkConfigData(
root_path=root_dir,
static_path=static_path,
network=network_section,
hazard=hazard_section
)
network_config_data.network.save_gpkg = True
Step 3: Define the damage analysis#
Here, we specify that RA2CE should perform a damage analysis using manual damage curves (MAN) with the class AnalysisSectionDamages and the attribute damage_curve
set to DamageCurveEnum.MAN For manual damage
curves, it is important to also specify the input data in the folder path in the config input_path
. This is the location where the custom manual curves will be defined and placed (see next step).
The event type can be set to EVENT if damages are to be calculated for the hazard maps only (example below), or to RETURN_PERIOD if the analysis should estimate risk over a specified return period (see tutorial ).
[ ]:
[5]:
from ra2ce.analysis.damages.damages import AnalysisSectionDamages
from ra2ce.analysis.analysis_config_data.enums.analysis_damages_enum import AnalysisDamagesEnum
from ra2ce.analysis.analysis_config_data.enums.event_type_enum import EventTypeEnum
from ra2ce.analysis.analysis_config_data.enums.damage_curve_enum import DamageCurveEnum
from ra2ce.analysis.analysis_config_data.analysis_config_data import AnalysisConfigData
damages_analysis = [AnalysisSectionDamages(
name='damages_reference_curve_manual',
analysis=AnalysisDamagesEnum.DAMAGES,
event_type=EventTypeEnum.EVENT,
damage_curve=DamageCurveEnum.MAN, # use manual damage curve
save_csv=True,
save_gpkg=True
)]
analysis_config_data = AnalysisConfigData(
analyses=damages_analysis,
output_path=output_path,
input_path=root_dir.joinpath("input_data")
)
Step 4: Create damage curve files#
In the manual situation, two input files are expected:
A file that specifies the shape of the vulnerability curve:
x-axis = hazard intensity (e.g. water depth in cm)
y-axis = damage fraction (0–1, representing the % of total construction cost)
A file that specifies the construction costs per road type and number of lanes.
Both files should be placed in the folder input_data/damage_functions/all_road_types/
.
Vulnerability curve (hazard severity vs. damage fraction)#
The file hazard_severity_damage_fraction.csv
looks like:
depth;damage
cm;% of total construction costs
0;0
100;0.1
200;0.2
400;0.4
800;0.8
12000;1
Where:
depth
= water depth in cmdamage
= damage fraction (0–1, relative to construction cost)
[6]:
import matplotlib.pyplot as plt
import pandas as pd
input_data_path = root_dir.joinpath("input_data")
vuln_curves = pd.read_csv(input_data_path.joinpath("damage_functions", "all_road_types","hazard_severity_damage_fraction.csv"), delimiter=";")
plt.plot(vuln_curves["depth"], vuln_curves["damage"], marker="o")
plt.xlabel("Depth [cm]")
plt.ylabel("Damage fraction")
plt.title("Manual vulnerability curve")
plt.grid(True)
plt.show()

Maximum construction costs per road type and lanes#
The file max_damage_road_types.csv
looks like:
Road_type \ lanes;1;2;3;4;5;6;7;8
unit;euro/m;euro/m;euro/m;euro/m;euro/m;euro/m;euro/m;euro/m
tertiary_link;110;120;130;140;150;130;140;150
tertiary;110;120;130;140;150;130;140;150
trunk;110;120;130;140;150;130;140;150
trunk_link;110;120;130;140;150;130;140;150
secondary_link;11;12;13;14;15;13;14;15
secondary;11;12;13;14;15;13;14;15
primary_link;11;12;13;14;15;13;14;15
primary;11;12;13;14;15;13;14;15
residential;1100;1200;1300;1400;1500;1300;1400;1500
['tertiary', 'residential'];510;520;530;540;550;530;540;550
motorway;1100;1200;1300;1400;1500;1300;1400;1500
motorway_link;510;520;530;540;550;530;540;550
Note
The first file defines the shape of the damage curve (hazard → damage fraction).
The second file defines the maximum construction costs per road type and lane count.
Together, they allow RA2CE to estimate damages in absolute currency units.
Step 5: Run the analysis#
[7]:
Ra2ceHandler.run_with_config_data(network_config_data, analysis_config_data)
c:\Users\hauth\miniforge3\envs\ra2ce_env\Lib\site-packages\osmnx\simplification.py:513: UserWarning: Geometry is in a geographic CRS. Results from 'buffer' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.
merged = convert.graph_to_gdfs(G, edges=False)["geometry"].buffer(tolerance).unary_union
c:\Users\hauth\miniforge3\envs\ra2ce_env\Lib\site-packages\osmnx\simplification.py:560: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.
centroids = node_clusters.centroid
100%|██████████| 127/127 [00:00<00:00, 126827.76it/s]
2025-10-01 05:05:17 PM - [avg_speed_calculator.py:175] - root - WARNING - No valid file found with average speeds data\damage_manual\static\output_graph\avg_speed.csv, calculating and saving them instead.
Graph hazard overlay with flood100: 100%|██████████| 109/109 [00:00<00:00, 184.69it/s]
Graph fraction with hazard overlay with flood100: 100%|██████████| 109/109 [01:00<00:00, 1.81it/s]
Graph hazard overlay with flood1000: 100%|██████████| 109/109 [00:00<00:00, 229.58it/s]
Graph fraction with hazard overlay with flood1000: 100%|██████████| 109/109 [01:16<00:00, 1.42it/s]
2025-10-01 05:07:37 PM - [hazard_intersect_builder_for_tif.py:225] - root - WARNING - Some geometries have NoneType objects (no coordinate information), namely: Empty GeoDataFrame
Columns: [osmid, oneway, lanes, ref, highway, maxspeed, reversed, length, rfid_c, rfid, avgspeed, time, bridge, name, junction, geometry]
Index: [].This could be due to segmentation, and might cause an exception in hazard overlay
Network hazard overlay with flood100: 100%|██████████| 2727/2727 [00:02<00:00, 910.21it/s]
Network fraction with hazard overlay with flood100: 100%|██████████| 2727/2727 [00:12<00:00, 209.88it/s]
Network hazard overlay with flood1000: 100%|██████████| 2727/2727 [00:03<00:00, 880.10it/s]
Network fraction with hazard overlay with flood1000: 100%|██████████| 2727/2727 [00:17<00:00, 156.20it/s]
2025-10-01 05:08:14 PM - [damage_network_base.py:121] - root - WARNING - Of the 2727 road segments, only 1568 had lane data, so for 1159 the '
lane data will be interpolated from the existing data
2025-10-01 05:08:15 PM - [damage_network_base.py:134] - root - WARNING - Interpolated the missing lane data as follows: {'motorway': 2.0, 'primary': 2.0, 'secondary': 2.0, 'trunk': 1.0}
[7]:
[DamagesResultWrapper(segment_based_result=AnalysisResult(analysis_result= osmid oneway lanes ref infra_type \
u v key
42002848 4336238970 0 435722626 True 3.0 N2 trunk
42003549 5284280675 0 215573156 True 2.0 A79 motorway
5284280682 0 254696086 True 2.0 A2 motorway
42004326 3744093239 0 254696086 True 2.0 A2 motorway
42005137 2832217846 0 720205318 True 1.0 A79 motorway_link
... ... ... ... ... ...
12680821393 42080697 0 6773022 False 2.0 nan secondary
42080429 0 6773022 False 2.0 nan secondary
12696408646 2540119540 0 6773079 False 2.0 nan secondary
42083916 0 6773079 False 2.0 nan secondary
13073531942 4659465278 0 435562863 True 2.0 A79 motorway
maxspeed reversed length rfid_c rfid ... \
u v key ...
42002848 4336238970 0 80 False 121.954 1 1 ...
42003549 5284280675 0 100 False 31.186 2 2 ...
5284280682 0 100 False 43.429 3 3 ...
42004326 3744093239 0 100 False 26.171 4 3 ...
42005137 2832217846 0 80 False 54.061 5 99 ...
... ... ... ... ... ... ...
12680821393 42080697 0 50 False 29.033 2723 45 ...
42080429 0 50 True 22.549 2724 54 ...
12696408646 2540119540 0 50 False 33.712 2725 78 ...
42083916 0 50 True 2.818 2726 95 ...
13073531942 4659465278 0 100 False 23.232 2727 124 ...
F_EV1_me F_EV1_fr F_EV2_mi F_EV2_ma F_EV2_me \
u v key
42002848 4336238970 0 NaN 1.0 NaN NaN NaN
42003549 5284280675 0 NaN 1.0 NaN NaN NaN
5284280682 0 NaN 1.0 NaN NaN NaN
42004326 3744093239 0 NaN 1.0 NaN NaN NaN
42005137 2832217846 0 1.000000 1.0 1.0 2.0 1.166667
... ... ... ... ... ...
12680821393 42080697 0 4.090909 1.0 5.0 5.0 5.000000
42080429 0 3.750000 1.0 3.0 5.0 4.437500
12696408646 2540119540 0 1.000000 1.0 1.0 1.0 1.000000
42083916 0 1.000000 1.0 1.0 2.0 1.666667
13073531942 4659465278 0 1.416667 1.0 1.0 3.0 2.000000
F_EV2_fr road_type lanes_copy dam_EV1_al \
u v key
42002848 4336238970 0 1.0 trunk 3.0 NaN
42003549 5284280675 0 1.0 motorway 2.0 NaN
5284280682 0 1.0 motorway 2.0 NaN
42004326 3744093239 0 1.0 motorway 2.0 NaN
42005137 2832217846 0 1.0 motorway 1.0 2757.0
... ... ... ... ...
12680821393 42080697 0 1.0 secondary 2.0 143.0
42080429 0 1.0 secondary 2.0 101.0
12696408646 2540119540 0 1.0 secondary 2.0 40.0
42083916 0 1.0 secondary 2.0 3.0
13073531942 4659465278 0 1.0 motorway 2.0 3949.0
dam_EV2_al
u v key
42002848 4336238970 0 NaN
42003549 5284280675 0 NaN
5284280682 0 NaN
42004326 3744093239 0 NaN
42005137 2832217846 0 3217.0
... ...
12680821393 42080697 0 174.0
42080429 0 120.0
12696408646 2540119540 0 40.0
42083916 0 6.0
13073531942 4659465278 0 5576.0
[2727 rows x 28 columns], analysis_config=AnalysisSectionDamages(name='damages_reference_curve_manual', save_gpkg=True, save_csv=True, analysis=<AnalysisDamagesEnum.DAMAGES: 1>, representative_damage_percentage=100, event_type=<EventTypeEnum.EVENT: 1>, damage_curve=<DamageCurveEnum.MAN: 3>, risk_calculation_mode=<RiskCalculationModeEnum.NONE: 0>, risk_calculation_year=0, create_table=False, file_name=None), output_path=WindowsPath('data/damage_manual/output'), _custom_name='damages_reference_curve_manual_segmented'), link_based_result=AnalysisResult(analysis_result= u v key \
0 42002848 3510081992 0
1 42002848 42005643 0
2 42002848 2832217848 0
3 42003549 1912827603 0
4 42003549 2119022099 0
.. ... ... ...
104 2587147852 2832217848 0
105 3441582565 3441582576 0
106 3475087790 3475088037 0
107 9646055861 9646055863 0
108 9646055861 10036651817 0
osmid oneway lanes \
0 435722626 True 3
1 72400805 True 2
2 [720205318, 284802951] True 1
3 [382923864, 215573156] True ['3', '2']
4 [337060729, 254696086, 7125689, 7125692, 10930... True 2
.. ... ... ...
104 [369221729, 720205319] True 1
105 [337060737, 587459365] True 2
106 [340294690, 340294691, 340294686] True ['1', '2']
107 6779700 True 1
108 6779700 True 1
ref highway maxspeed reversed ... EV1_me EV1_fr EV2_me \
0 N2 trunk 80 False ... 1.000000 1.0 1.000000
1 N2 trunk 80 False ... NaN 1.0 NaN
2 A79 motorway_link 80 False ... 2.837607 1.0 3.508065
3 A79 motorway 100 False ... 4.592040 1.0 4.892019
4 A2 motorway 100 False ... 4.510000 1.0 4.539823
.. ... ... ... ... ... ... ... ...
104 A79 motorway 100 False ... 4.330798 1.0 4.637363
105 nan primary 50 False ... 4.227273 1.0 3.323529
106 nan primary 50 False ... 4.000000 1.0 4.062500
107 nan secondary 50 False ... 1.000000 1.0 3.400000
108 nan secondary 50 False ... 1.500000 1.0 2.444444
EV2_fr dam_EV1_al_segments dam_EV1_al \
0 1.0 [nan, 1364.0] 1364.0
1 1.0 [nan, nan, nan, nan, nan] 0.0
2 1.0 [5581.0, 9553.0, 2757.0, nan, nan, nan, nan, 2... 27270.0
3 1.0 [13464.0, 15332.0, nan, 8167.0, 10481.0, 8444.... 191760.0
4 1.0 [nan, nan, 12109.0, nan, nan, nan, nan, 2392.0... 84491.0
.. ... ... ...
104 1.0 [16409.0, 27780.0, 27231.0, 36025.0, 33800.0, ... 220478.0
105 1.0 [27.0, nan, nan, 55.0, 34.0, 47.0, nan, 42.0, ... 405.0
106 1.0 [nan, nan, 123.0, 148.0] 271.0
107 1.0 [2.0, 2.0, 2.0, 2.0] 8.0
108 1.0 [nan, nan, nan, nan, nan, nan, 2.0, nan, nan, ... 12.0
dam_EV2_al_segments dam_EV2_al \
0 [nan, 1364.0] 1364.0
1 [nan, nan, nan, nan, nan] 0.0
2 [6725.0, 10747.0, 3217.0, nan, nan, nan, nan, ... 34419.0
3 [13464.0, 15332.0, nan, 10209.0, 10500.0, 9703... 209378.0
4 [nan, nan, 12109.0, 7222.0, nan, nan, nan, 340... 109199.0
.. ... ...
104 [19170.0, 20835.0, 28273.0, 33725.0, 39306.0, ... 226039.0
105 [27.0, 24.0, nan, 55.0, 43.0, 47.0, 9.0, 42.0,... 480.0
106 [nan, nan, 127.0, 151.0] 278.0
107 [5.0, 8.0, 8.0, 8.0] 29.0
108 [4.0, 2.0, nan, 2.0, 6.0, 7.0, 7.0, 7.0, 5.0, ... 84.0
name junction
0 nan nan
1 nan nan
2 nan nan
3 nan nan
4 nan nan
.. ... ...
104 nan nan
105 Nieuwe Limmelderweg nan
106 Nieuwe Limmelderweg nan
107 nan roundabout
108 nan roundabout
[109 rows x 27 columns], analysis_config=AnalysisSectionDamages(name='damages_reference_curve_manual', save_gpkg=True, save_csv=True, analysis=<AnalysisDamagesEnum.DAMAGES: 1>, representative_damage_percentage=100, event_type=<EventTypeEnum.EVENT: 1>, damage_curve=<DamageCurveEnum.MAN: 3>, risk_calculation_mode=<RiskCalculationModeEnum.NONE: 0>, risk_calculation_year=0, create_table=False, file_name=None), output_path=WindowsPath('data/damage_manual/output'), _custom_name='damages_reference_curve_manual_link_based'))]
Output#
The results of the manual damage analysis are provided in two GeoPackage (GPKG) files:
damages_reference_curve_manual_link_based.gpkg
: damage estimates per network linkdamages_reference_curve_manual_segment.gpkg
: damage estimates per 100m segment
Key attributes of interest (in currency):
dam_EV1_al
: estimated damage for the first flood map (manual method).dam_EV2_al
: estimated damage for the second flood map (manual method).
You can open these files in GIS software (QGIS, ArcGIS) or load them in Python with GeoPandas:
[ ]:
import geopandas as gpd
output_path = root_dir / "output" / 'damages'
link_based = gpd.read_file(output_path / "damages_reference_curve_manual_link_based.gpkg")
segment_based = gpd.read_file(output_path / "damages_reference_curve_manual_segmented.gpkg")
# Inspect the first rows
print(link_based.head())
print(segment_based.head())
You can open the results in GIS software to visualize which road segments are most affected by the hazard.