Origins to Closest Destinations Tutorial#
This tutorial demonstrates how to run an Origins to Closest Destinations (OD) analysis with RA2CE. RA2CE automatically finds the shortest or quickest route from each origin to its nearest destination.
If you are not yet familiar with preparing origins and destinations shapefiles, see the Origins and Destinations Data Preparation tutorial.
Step 1: Import Libraries and Set Paths#
[ ]:
from pathlib import Path
from ra2ce.analysis.analysis_config_data.analysis_config_data import AnalysisSectionLosses, AnalysisConfigData
from ra2ce.analysis.analysis_config_data.enums.analysis_losses_enum import AnalysisLossesEnum
from ra2ce.analysis.analysis_config_data.enums.weighing_enum import WeighingEnum
from ra2ce.network.network_config_data.enums.aggregate_wl_enum import AggregateWlEnum
from ra2ce.network import RoadTypeEnum
from ra2ce.network.network_config_data.enums.network_type_enum import NetworkTypeEnum
from ra2ce.network.network_config_data.enums.source_enum import SourceEnum
from ra2ce.network.network_config_data.network_config_data import (
NetworkSection, NetworkConfigData, OriginsDestinationsSection, HazardSection
)
from ra2ce.ra2ce_handler import Ra2ceHandler
# Specify the path to your RA2CE project folder and input data
root_dir = Path('data', 'closest_origin_destinations')
network_path = root_dir.joinpath('static', 'network')
Step 2: Define Network with Origins & Destinations#
Define the network configuration using OSM data clipped to a region polygon and include specific road types.
[ ]:
network_section = NetworkSection(
source=SourceEnum.OSM_DOWNLOAD,
polygon=network_path.joinpath("region_polygon.geojson"),
network_type=NetworkTypeEnum.DRIVE,
road_types=[
RoadTypeEnum.MOTORWAY,
RoadTypeEnum.MOTORWAY_LINK,
RoadTypeEnum.PRIMARY,
RoadTypeEnum.PRIMARY_LINK,
RoadTypeEnum.SECONDARY,
RoadTypeEnum.SECONDARY_LINK,
RoadTypeEnum.TERTIARY,
RoadTypeEnum.TERTIARY_LINK,
RoadTypeEnum.RESIDENTIAL,
],
save_gpkg=True,
)
[ ]:
origin_destination_section = OriginsDestinationsSection(
origins=network_path.joinpath("origins.shp"),
destinations=network_path.joinpath("destinations.shp"),
origin_count="POPULATION",
)
[ ]:
hazard_section = HazardSection(
hazard_map=[root_dir.joinpath("static", "hazard", "max_flood_depth.tif")],
aggregate_wl=AggregateWlEnum.MEAN,
hazard_crs="EPSG:32736",
overlay_segmented_network=False,
)
[ ]:
network_config_data = NetworkConfigData(
root_path=root_dir,
static_path=root_dir.joinpath('static'),
network=network_section,
hazard=hazard_section,
origins_destinations=origin_destination_section,
)
Step 3: Define the Analysis#
Use AnalysisLossesEnum.MULTI_LINK_ORIGIN_CLOSEST_DESTINATION to calculate routes that avoid disrupted roads.
[ ]:
analyse_section = AnalysisSectionLosses(
name="OD_accessibility_analysis",
analysis=AnalysisLossesEnum.MULTI_LINK_ORIGIN_CLOSEST_DESTINATION,
weighing=WeighingEnum.LENGTH,
calculate_route_without_disruption=True,
save_csv=True,
save_gpkg=True,
)
analysis_config_data = AnalysisConfigData(
output_path=root_dir.joinpath("output"),
static_path=root_dir.joinpath('static'),
analyses=[analyse_section],
)
Step 4: Run the Analysis#
[ ]:
handler = Ra2ceHandler.from_config(
network=network_config_data,
analysis=analysis_config_data
)
handler.configure()
handler.run_analysis()
Step 5: Interpret Results#
Results are stored in the output
folder and include both CSV and GeoPackage files.
[ ]:
import geopandas as gpd
analysis_output_path = root_dir / "output" / "multi_link_origin_closest_destination"
results_gpkg = analysis_output_path / "OD_accessibility_analysis_optimal_routes_without_hazard.gpkg"
gdf = gpd.read_file(results_gpkg)
gdf.head()
Identifying Isolated Populations#
Origins that cannot reach any destination due to hazard disruption are flagged in OD_accessibility_analysis_origins.gpkg
.
[ ]:
origin_gdf = gpd.read_file(analysis_output_path / 'OD_accessibility_analysis_origins.gpkg')
map = origin_gdf.explore(column='EV1_me_A', cmap=['green', 'red'],
marker_kwds={'radius':5}, tiles="CartoDB dark_matter")
map
[ ]:
no_access_gdf = origin_gdf[origin_gdf['EV1_me_A'] == 'no access']
no_access_gdf.explore(column='POPULATION', cmap='cool',
marker_kwds={'radius':5}, tiles="CartoDB dark_matter")
Inspecting Optimal Routes#
Routes are computed from each origin to its closest destination. Routes can be filtered by specific destinations or categories.
[ ]:
destinations_gdf = gpd.read_file(analysis_output_path / 'OD_accessibility_analysis_destinations.gpkg')
optimal_routes_with_hazard_gdf = gpd.read_file(analysis_output_path / 'OD_accessibility_analysis_optimal_routes_with_hazard.gpkg')
d_1_gdf = destinations_gdf[destinations_gdf['d_id'] == 'D_1']
optimal_routes_d_1_with_hazard_gdf = optimal_routes_with_hazard_gdf[optimal_routes_with_hazard_gdf['destination'] == 'D_1']
origins_with_optimal_route_d_1 = origin_gdf[origin_gdf['o_id'].isin(optimal_routes_d_1_with_hazard_gdf['origin'])]
optimal_routes_d_1_with_hazard_gdf.explore(column='difference',
cmap='RdYlGn_r', legend=True,
tiles="CartoDB dark_matter")
Key Notes#
Some routes may no longer exist if disrupted roads block all access
Remaining routes may be longer or slower, showing detours
Some origins may completely lose access to the destination
This analysis helps quantify loss of accessibility due to hazards.