Skip to content

Isochrones and Transport Accessibility

Isochrones represent areas reachable from a starting point within a given time limit along a transport network. This function enables analysis of transport accessibility using pedestrian, automobile, public transport graphs, or their combination.


The library offers multiple isochrone generation methods:

Baseline isochrones

Show a single area reachable within a specified time.

objectnat.get_accessibility_isochrones(isochrone_type, points, weight_value, weight_type, nx_graph, **kwargs)

Calculate accessibility isochrones from input points based on the provided city graph.

Supports two types of isochrones
  • 'radius': Circular buffer-based isochrones
  • 'ways': Road network-based isochrones

Parameters:

Name Type Description Default
isochrone_type Literal['radius', 'ways']

Type of isochrone to calculate: - "radius": Creates circular buffers around reachable nodes - "ways": Creates polygons based on reachable road network

required
points GeoDataFrame

GeoDataFrame containing source points for isochrone calculation.

required
weight_value float

Maximum travel time (minutes) or distance (meters) threshold.

required
weight_type Literal['time_min', 'length_meter']

Type of weight calculation: - "time_min": Time-based accessibility in minutes - "length_meter": Distance-based accessibility in meters

required
nx_graph Graph

NetworkX graph representing the transportation network. Must contain CRS and speed attributes for time calculations.

required
**kwargs Any

Additional parameters: - buffer_factor: Size multiplier for buffers (default: 0.7) - road_buffer_size: Buffer size for road edges in meters (default: 5)

{}

Returns:

Type Description
tuple[GeoDataFrame, GeoDataFrame | None, GeoDataFrame | None]

Tuple containing: - isochrones: GeoDataFrame with calculated isochrone polygons - pt_stops: Public transport stops within isochrones (if available) - pt_routes: Public transport routes within isochrones (if available)

Source code in src\objectnat\methods\isochrones\isochrones.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def get_accessibility_isochrones(
    isochrone_type: Literal["radius", "ways"],
    points: gpd.GeoDataFrame,
    weight_value: float,
    weight_type: Literal["time_min", "length_meter"],
    nx_graph: nx.Graph,
    **kwargs: Any,
) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
    """
    Calculate accessibility isochrones from input points based on the provided city graph.

    Supports two types of isochrones:
        - 'radius': Circular buffer-based isochrones
        - 'ways': Road network-based isochrones

    Parameters:
        isochrone_type (Literal["radius", "ways"]):
            Type of isochrone to calculate:
            - "radius": Creates circular buffers around reachable nodes
            - "ways": Creates polygons based on reachable road network
        points (gpd.GeoDataFrame):
            GeoDataFrame containing source points for isochrone calculation.
        weight_value (float):
            Maximum travel time (minutes) or distance (meters) threshold.
        weight_type (Literal["time_min", "length_meter"]):
            Type of weight calculation:
            - "time_min": Time-based accessibility in minutes
            - "length_meter": Distance-based accessibility in meters
        nx_graph (nx.Graph):
            NetworkX graph representing the transportation network.
            Must contain CRS and speed attributes for time calculations.
        **kwargs: Additional parameters:
            - buffer_factor: Size multiplier for buffers (default: 0.7)
            - road_buffer_size: Buffer size for road edges in meters (default: 5)

    Returns:
        (tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]):
            Tuple containing:
            - isochrones: GeoDataFrame with calculated isochrone polygons
            - pt_stops: Public transport stops within isochrones (if available)
            - pt_routes: Public transport routes within isochrones (if available)

    """

    buffer_params = {
        "buffer_factor": 0.7,
        "road_buffer_size": 5,
    }
    original_crs = points.crs
    buffer_params.update(kwargs)

    points = points.copy()
    local_crs, graph_type = _validate_inputs(points, weight_value, weight_type, nx_graph)

    nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
        points, nx_graph, graph_type, weight_type, weight_value
    )

    weight_cutoff = (
        weight_value + (100 if weight_type == "length_meter" else 1) if isochrone_type == "ways" else weight_value
    )

    dist_matrix, subgraph = _calculate_distance_matrix(
        nx_graph, points["nearest_node"].values, weight_type, weight_cutoff, dist_nearest
    )

    logger.info("Building isochrones geometry...")
    nodes, edges = graph_to_gdf(subgraph)
    if isochrone_type == "radius":
        isochrone_geoms = _build_radius_isochrones(
            dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
        )
    else:  # isochrone_type == 'ways':
        if graph_type in ["intermodal", "walk"]:
            isochrone_edges = edges[edges["type"] == "walk"]
        else:
            isochrone_edges = edges.copy()
        all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
        all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
        isochrone_geoms = _build_ways_isochrones(
            dist_matrix=dist_matrix,
            weight_value=weight_value,
            weight_type=weight_type,
            speed=speed,
            nodes=nodes,
            all_isochrones_edges=all_isochrones_edges,
            buffer_factor=buffer_params["buffer_factor"],
        )
    isochrones = _create_isochrones_gdf(points, isochrone_geoms, dist_matrix, local_crs, weight_type, weight_value)
    pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
    if pt_nodes is not None:
        pt_nodes.to_crs(original_crs, inplace=True)
    if pt_edges is not None:
        pt_edges.to_crs(original_crs, inplace=True)
    return isochrones.to_crs(original_crs), pt_nodes, pt_edges

isochrone_ways_15_min isochrone_radius_15_min isochrone_3points_radius_8_min


Stepped isochrones

Show accessibility ranges divided into time intervals (e.g., 5, 10, 15 minutes).

objectnat.get_accessibility_isochrone_stepped(isochrone_type, point, weight_value, weight_type, nx_graph, step=None, **kwargs)

Calculate stepped accessibility isochrones for a single point with specified intervals.

Parameters:

Name Type Description Default
isochrone_type Literal['radius', 'ways', 'separate']

Visualization method for stepped isochrones: - "radius": Voronoi-based in circular buffers - "ways": Voronoi-based in road network polygons - "separate": Circular buffers for each step

required
point GeoDataFrame

Single source point for isochrone calculation (uses first geometry if multiple provided).

required
weight_value float

Maximum travel time (minutes) or distance (meters) threshold.

required
weight_type Literal['time_min', 'length_meter']

Type of weight calculation: - "time_min": Time-based in minutes - "length_meter": Distance-based in meters

required
nx_graph Graph

NetworkX graph representing the transportation network.

required
step float

Interval between isochrone steps. Defaults to: - 100 meters for distance-based - 1 minute for time-based

None
**kwargs Any

Additional parameters: - buffer_factor: Size multiplier for buffers (default: 0.7) - road_buffer_size: Buffer size for road edges in meters (default: 5)

{}

Returns:

Type Description
tuple[GeoDataFrame, GeoDataFrame | None, GeoDataFrame | None]

Tuple containing: - stepped_isochrones: GeoDataFrame with stepped polygons and distance/time attributes - pt_stops: Public transport stops within isochrones (if available) - pt_routes: Public transport routes within isochrones (if available)

Source code in src\objectnat\methods\isochrones\isochrones.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def get_accessibility_isochrone_stepped(
    isochrone_type: Literal["radius", "ways", "separate"],
    point: gpd.GeoDataFrame,
    weight_value: float,
    weight_type: Literal["time_min", "length_meter"],
    nx_graph: nx.Graph,
    step: float = None,
    **kwargs: Any,
) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
    """
    Calculate stepped accessibility isochrones for a single point with specified intervals.

    Parameters:
        isochrone_type (Literal["radius", "ways", "separate"]):
            Visualization method for stepped isochrones:
            - "radius": Voronoi-based in circular buffers
            - "ways": Voronoi-based in road network polygons
            - "separate": Circular buffers for each step
        point (gpd.GeoDataFrame):
            Single source point for isochrone calculation (uses first geometry if multiple provided).
        weight_value (float):
            Maximum travel time (minutes) or distance (meters) threshold.
        weight_type (Literal["time_min", "length_meter"]):
            Type of weight calculation:
            - "time_min": Time-based in minutes
            - "length_meter": Distance-based in meters
        nx_graph (nx.Graph):
            NetworkX graph representing the transportation network.
        step (float, optional):
            Interval between isochrone steps. Defaults to:
            - 100 meters for distance-based
            - 1 minute for time-based
        **kwargs: Additional parameters:
            - buffer_factor: Size multiplier for buffers (default: 0.7)
            - road_buffer_size: Buffer size for road edges in meters (default: 5)

    Returns:
        (tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]):
            Tuple containing:
            - stepped_isochrones: GeoDataFrame with stepped polygons and distance/time attributes
            - pt_stops: Public transport stops within isochrones (if available)
            - pt_routes: Public transport routes within isochrones (if available)
    """
    buffer_params = {
        "buffer_factor": 0.7,
        "road_buffer_size": 5,
    }

    buffer_params.update(kwargs)
    original_crs = point.crs
    point = point.copy()
    if len(point) > 1:
        logger.warning(
            f"This method processes only single point. The GeoDataFrame contains {len(point)} points - "
            "only the first geometry will be used for isochrone calculation. "
        )
        point = point.iloc[[0]]

    local_crs, graph_type = _validate_inputs(point, weight_value, weight_type, nx_graph)

    if step is None:
        if weight_type == "length_meter":
            step = 100
        else:
            step = 1
    nx_graph, points, dist_nearest, speed = _prepare_graph_and_nodes(
        point, nx_graph, graph_type, weight_type, weight_value
    )

    dist_matrix, subgraph = _calculate_distance_matrix(
        nx_graph, points["nearest_node"].values, weight_type, weight_value, dist_nearest
    )

    logger.info("Building isochrones geometry...")
    nodes, edges = graph_to_gdf(subgraph)
    nodes.loc[dist_matrix.columns, "dist"] = dist_matrix.iloc[0]

    if isochrone_type == "separate":
        stepped_iso = create_separated_dist_polygons(nodes, weight_value, weight_type, step, speed)
    else:
        if isochrone_type == "radius":
            isochrone_geoms = _build_radius_isochrones(
                dist_matrix, weight_value, weight_type, speed, nodes, buffer_params["buffer_factor"]
            )
        else:  # isochrone_type == 'ways':
            if graph_type in ["intermodal", "walk"]:
                isochrone_edges = edges[edges["type"] == "walk"]
            else:
                isochrone_edges = edges.copy()
            all_isochrones_edges = isochrone_edges.buffer(buffer_params["road_buffer_size"], resolution=1).union_all()
            all_isochrones_edges = gpd.GeoDataFrame(geometry=[all_isochrones_edges], crs=local_crs)
            isochrone_geoms = _build_ways_isochrones(
                dist_matrix=dist_matrix,
                weight_value=weight_value,
                weight_type=weight_type,
                speed=speed,
                nodes=nodes,
                all_isochrones_edges=all_isochrones_edges,
                buffer_factor=buffer_params["buffer_factor"],
            )
        nodes = nodes.clip(isochrone_geoms[0], keep_geom_type=True)
        nodes["dist"] = np.minimum(np.ceil(nodes["dist"] / step) * step, weight_value)
        voronois = gpd.GeoDataFrame(geometry=nodes.voronoi_polygons(), crs=local_crs)
        stepped_iso = (
            voronois.sjoin(nodes[["dist", "geometry"]]).dissolve(by="dist", as_index=False).drop(columns="index_right")
        )
        stepped_iso = stepped_iso.clip(isochrone_geoms[0], keep_geom_type=True)

    pt_nodes, pt_edges = _process_pt_data(nodes, edges, graph_type)
    if pt_nodes is not None:
        pt_nodes.to_crs(original_crs, inplace=True)
    if pt_edges is not None:
        pt_edges.to_crs(original_crs, inplace=True)
    return stepped_iso.to_crs(original_crs), pt_nodes, pt_edges

stepped_isochrone_ways_15_min stepped_isochrone_radius_15_min stepped_isochrone_separate_15_min