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-09-16 12:49:13.309 | WARNING | iduedu.graph.transformers:keep_largest_connected_component:64 - Removing 334 nodes outside the largest strongly connected component. Retaining 21184 of 21518 nodes.
2026-09-16 12:49:13.330 | WARNING | iduedu.graph_builders.intermodal_builders:join_pt_walk_graph:248 - Public transport nodes dropped with the smaller components: {'trolleybus': 2}
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).fillna('#d9d9d9') # other edge types in light grey
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)