Isochrone Analysis from Points of Interest

This notebook demonstrates how to generate accessibility isochrones from single or multiple points using different methods:

  • Simple radius and ways isochrones

  • Stepped 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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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')
Make this Notebook Trusted to load map: File -> Trust Notebook

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)