Creating 15-Minute City Isochrones in Python

Creating 15-Minute City Isochrones in Python requires combining OpenStreetMap network extraction, time-weighted graph traversal, and spatial aggregation. The most reliable workflow uses osmnx for street network retrieval, networkx for shortest-path calculations with mode-specific travel speeds, and geopandas/shapely for polygon generation. By converting edge lengths to travel times, running a Dijkstra search from a central coordinate, and extracting reachable nodes within 900 seconds, you can generate accurate walk, bike, or drive isochrones that respect real-world routing constraints.

This approach avoids raster-based approximations and produces vector-accurate accessibility boundaries suitable for urban planning, logistics optimization, and spatial analytics. For teams evaluating alternative spatial libraries, Generating Isochrones with PySal and GeoPandas covers complementary matrix-based techniques, while the broader Python Routing Engines & Isochrone Mapping cluster details production deployment patterns.

Core Pipeline

The workflow follows a deterministic six-step sequence:

  1. Network Extraction: Download a localized, topologically correct street graph using osmnx.graph_from_point().
  2. Impedance Assignment: Convert geometric edge lengths to temporal weights (seconds) using mode-specific velocities.
  3. Origin Mapping: Snap the input coordinate to the nearest valid graph node.
  4. Graph Traversal: Execute single-source Dijkstra with a 900-second cutoff to identify all reachable nodes.
  5. Spatial Aggregation: Convert reachable nodes to points, apply a metric buffer, and union geometries.
  6. Boundary Smoothing: Apply a concave hull to remove artificial buffer artifacts and produce a contiguous polygon.

Production-Ready Implementation

The following script implements the pipeline with modern shapely 2.x and geopandas 0.14+ APIs. It handles CRS projection for accurate metric buffering and returns a standards-compliant GeoDataFrame.

import osmnx as ox
import networkx as nx
import geopandas as gpd
import shapely
from shapely.geometry import Point

def generate_15min_isochrone(lat, lon, travel_mode="walk", speed_kmh=5.0, buffer_m=50):
    """
    Generate a 15-minute (900s) isochrone polygon for a given coordinate.
    Returns a GeoDataFrame in EPSG:4326.
    """
    # 1. Fetch and simplify street network
    G = ox.graph_from_point(
        (lat, lon), 
        dist=3000, 
        network_type=travel_mode, 
        simplify=True
    )

    # 2. Calculate travel time (seconds) per edge
    speed_ms = (speed_kmh * 1000) / 3600
    for u, v, data in G.edges(data=True):
        length_m = data.get("length", 0)
        data["travel_time"] = length_m / speed_ms

    # 3. Locate nearest graph node to origin
    origin_node = ox.distance.nearest_nodes(G, lon, lat)

    # 4. Run single-source shortest path with time cutoff
    reachable = nx.single_source_dijkstra_path_length(
        G, origin_node, weight="travel_time", cutoff=900
    )

    if len(reachable) < 4:
        raise ValueError("Insufficient reachable nodes. Verify coordinates, network_type, or speed_kmh.")

    # 5. Extract coordinates and build spatial object
    coords = [(G.nodes[n]["x"], G.nodes[n]["y"]) for n in reachable]
    points_gdf = gpd.GeoDataFrame(geometry=[Point(c) for c in coords], crs="EPSG:4326")

    # 6. Project to metric CRS for accurate buffering, then generate boundary
    points_metric = points_gdf.to_crs(epsg=3857)
    buffered = points_metric.buffer(buffer_m)
    merged = shapely.union_all(buffered.geometry)
    isochrone = shapely.concave_hull(merged, ratio=0.95)

    # Return to WGS84 for standard GIS compatibility
    return gpd.GeoDataFrame({"geometry": [isochrone], "travel_time_max": 900}, crs="EPSG:4326")

# Usage
# iso = generate_15min_isochrone(48.8566, 2.3522, travel_mode="walk", speed_kmh=5.0)
# iso.to_file("15min_walk.gpkg", driver="GPKG")

Impedance Modeling & Network Configuration

Accurate isochrones depend on realistic impedance functions. The default speed_kmh parameter assumes uniform velocity, which rarely reflects urban mobility. For production systems, replace static speeds with dynamic impedance tables that account for:

  • Topography: Elevation gain reduces cycling and walking speeds by 15–30% on gradients >5%.
  • Infrastructure Quality: Dedicated bike lanes, pedestrian zones, and traffic signals introduce friction coefficients.
  • Temporal Variance: Rush-hour congestion can increase drive times by 40–60%. OSMnx allows you to load maxspeed tags and apply time-of-day multipliers.

When modeling multimodal networks, ensure network_type aligns with OSM routing profiles (walk, bike, drive, drive_service). For detailed routing engine comparisons and impedance calibration strategies, consult the official OSMnx documentation and NetworkX shortest path algorithms.

Performance Optimization & Scaling

Graph traversal scales linearly with edge count. For city-wide or regional deployments:

  • Precompute Graphs: Serialize networks to .graphml or .gpkg to avoid repeated OSM API calls.
  • Limit Search Radius: A 3,000m radius is sufficient for 15-minute walk/bike isochrones. Drive isochrones may require 8,000–12,000m.
  • Parallelize Origins: Use concurrent.futures or multiprocessing to process multiple coordinates. Each worker should maintain its own graph instance to avoid thread-safety issues in networkx.
  • Memory Management: Large graphs (>500k edges) benefit from nx.algorithms.shortest_paths.weighted.single_source_dijkstra with explicit heap optimization.

Validation & Output Formats

Always validate generated polygons against ground-truth routing APIs (e.g., OpenRouteService, Valhalla) before deployment. Common validation checks include:

  • Topology Integrity: Ensure isochrone.is_valid returns True.
  • Area Reasonableness: A 15-minute walk typically covers 0.8–1.5 km² in dense urban cores.
  • Edge Cases: Water bodies, gated communities, and one-way restrictions should naturally constrain the boundary.

Export to GeoPackage (.gpkg) for lossless geometry storage, or GeoJSON for web visualization. When integrating with frontend mapping libraries, project to EPSG:3857 client-side to align with standard tile grids.