Isochrone Analysis from Points of Interest¶
This notebook demonstrates how to generate accessibility isochrones from single or multiple points using different methods:
Simple
radiusandwaysisochronesStepped isochrones with customizable intervals
# Install required packages (uncomment if needed)
# !pip install objectnat iduedu
# Import necessary libraries
from iduedu import get_intermodal_graph, get_4326_boundary
import geopandas as gpd
from shapely import Point
from objectnat import get_graph_isochrones, get_stepped_graph_isochrones
1. Load Intermodal Graph¶
Load a multimodal transportation graph (roads, public transport, etc.) for a specific region using its OSM ID.
# Load boundary and graph for a specific region using OSM ID 1114252.
poly = get_4326_boundary(osm_id=1114252)
G_intermodal = get_intermodal_graph(territory=poly, clip_by_territory=True)
2026-07-10 13:07:54.188 | WARNING | iduedu.graph.transformers:keep_largest_connected_component:64 - Removing 343 nodes outside the largest strongly connected component. Retaining 21016 of 21359 nodes.
2. Create Points of Interest¶
Define one or more source points from which isochrones will be generated.
# Define a single point of interest
point = gpd.GeoDataFrame(geometry=[Point(30.27060176, 59.93546846)], crs=4326)
3. Generate Radius Isochrones¶
Create circular isochrones using a travel time threshold (e.g. 10 minutes).
isochrones_radius = get_graph_isochrones(
G_intermodal,
geometry_type='radius',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
)
# Visualize
isochrones_radius.explore(tiles='CartoDB Positron')
4. Generate Ways Isochrones¶
Create road network-based polygons representing reachable areas within a time or distance threshold.
isochrones_ways = get_graph_isochrones(
G_intermodal,
geometry_type='ways',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
)
# Visualize
isochrones_ways.explore(tiles='CartoDB Positron')
5. Compare Isochrone Types¶
Overlay both types of isochrones to compare coverage.
m = isochrones_radius.explore(tiles='CartoDB Positron', color='blue', name='Radius')
isochrones_ways.explore(m=m, color='red', name='Ways')
6. Generate Stepped Isochrones (Radius)¶
Create concentric buffer zones with stepped intervals (e.g. every 3 minutes).
stepped_radius = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='radius',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=15,
step=3,
)
stepped_radius.explore(tiles='CartoDB Positron', column='dist')
7. Generate Stepped Isochrones (Ways)¶
Create layered polygons in the road network with custom intervals (e.g. every 3 minutes).
stepped_ways = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='ways',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=15,
step=3,
)
stepped_ways.explore(tiles='CartoDB Positron', column='dist')
8. Generate Stepped Isochrones (Separate)¶
Create distinct buffer rings for each interval.
stepped_separate = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='separate',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=10,
step=2,
)
stepped_separate.explore(tiles='CartoDB Positron', column='dist')
Key Parameter Summary:¶
isochrone_type:'radius','ways', or'separate'weight_type:'time_min'(minutes) or'length_meter'(meters)weight_value: total cutoff (e.g. 10 minutes)step: interval size for stepped isochrones (optional)Additional:
buffer_factor,road_buffer_size
Animation for stepped isochrones¶
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from shapely import Point
import geopandas as gpd
from objectnat import get_stepped_graph_isochrones
edges = G_intermodal.edges_gdf.copy()
point = gpd.GeoDataFrame(geometry=[Point(30.27060176, 59.93546846)], crs=4326).to_crs(edges.crs)
bbox = gpd.GeoDataFrame(geometry=[poly], crs=4326).to_crs(edges.crs)
type_colors = {
'walk': '#a3a3a3',
'bus': '#1f77b4',
'trolleybus': '#2ca02c',
'tram': '#ff7f0e',
'subway': '#9467bd',
'boarding': '#8c564b'
}
edges['color'] = edges['type'].map(type_colors)
steps = [0.1, 0.5, 1, 2, 3, 4, 5]
fig, ax = plt.subplots(figsize=(10, 8), dpi=150)
plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
edges_plot = edges.plot(ax=ax, color=edges['color'], alpha=0.5, linewidth=0.1, legend=True)
bbox.boundary.plot(ax=ax, color='black', linestyle='--', linewidth=1)
point.plot(ax=ax, color='red', markersize=50)
ax.set_axis_off()
def update(step):
for coll in ax.collections:
if coll.get_label() == 'isochrone':
coll.remove()
result = get_stepped_graph_isochrones(
G_intermodal,
geometry_type='separate',
gdf_origins=point,
weight_type="time_min",
weight_value_cutoff=15,
step=step,
)
result.plot(ax=ax, alpha=1, label='isochrone', column='dist', legend=False)
ax.set_title(f'Isochrone with step = {step} minutes')
ani = FuncAnimation(
fig,
update,
frames=steps,
repeat=True,
interval=2000
)
ani.save('isochrone_animation.gif', writer='pillow', fps=1)