OSM

Last update

last-modified

Open In Colab

Preparation

Set the coordinates and buffer distances to your preferred extents.

from cityseer.tools import io

# lng, lat, buffer_dist, plot_buffer = -1.7063649924889566, 52.19277374082795, 1500, 1250  # stratford-upon-avon
lng, lat, buffer_dist, plot_buffer = (
    -0.13039709427587876,
    51.516434828344366,
    6000,
    5000,
)  # london
# lng, lat, buffer_dist, plot_buffer = 18.425702641104582, -33.9204746754594, 3000, 2500  # cape town
poly_wgs, _ = io.buffered_point_poly(lng, lat, buffer_dist)
poly_utm, _ = io.buffered_point_poly(lng, lat, buffer_dist, projected=True)
# select extents for plotting
plot_bbox = poly_utm.centroid.buffer(plot_buffer).bounds

Loading data from OSM

For this example, we’ll use a custom OSM query that excludes footways because the continuity analysis needs OSM ways that have street name or route number information.

For this form of analysis, only basic cleaning is recommended:

  • not consolidating nodes because this risks accidentally dropping highway / ref / name attributes
  • not like closeness or betweenness methods, where topological distortions are arguably a bigger issue
query = """
[out:json];
(
    way["highway"]
    ["area"!="yes"]
    ["highway"!~"footway|pedestrian|steps|bus_guideway|escape|raceway|proposed|planned|abandoned|platform|construction"]
    ["service"!~"parking_aisle"]
    ["amenity"!~"charging_station|parking|fuel|motorcycle_parking|parking_entrance|parking_space"]
    ["access"!~"private|customers"]
    ["indoor"!="yes"]
    (poly:"{geom_osm}");
);
out body;
>;
out qt;
"""
G_osm = io.osm_graph_from_poly(
    poly_wgs, custom_request=query, simplify=True
)
WARNING:cityseer.tools.io:Unsuccessful OSM API request response, trying again...
INFO:cityseer.tools.graphs:Generating interpolated edge geometries.
WARNING:cityseer.tools.util:The to_crs_code parameter 4326 is not a projected CRS
INFO:cityseer.tools.io:Converting networkX graph to CRS code 32630.
WARNING:cityseer.tools.util:The to_crs_code parameter 4326 is not a projected CRS
INFO:cityseer.tools.io:Processing node x, y coordinates.
INFO:cityseer.tools.io:Processing edge geom coordinates, if present.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.graphs:Removing dangling nodes.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Snapping gapped endings.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing dangling nodes.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating edges STR tree.
INFO:cityseer.tools.graphs:Splitting opposing edges.
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.util:Creating nodes STR tree
INFO:cityseer.tools.graphs:Consolidating nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 50.
INFO:cityseer.tools.graphs:Ironing edges.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 1.
INFO:cityseer.tools.graphs:Removing dangling nodes.
INFO:cityseer.tools.graphs:Removing filler nodes.

Observe continuity metrics

We can now run the continuity metrics.

import matplotlib.pyplot as plt

from cityseer.tools import io, plot
from cityseer.metrics import observe

# methods can be "names", "routes", "highways"

print("Continuity by street names")
G_cont, NamesContReport = observe.street_continuity(G_osm, method="names")
NamesContReport.report_by_count(n_items=5)
NamesContReport.report_by_length(n_items=5)

print("Continuity by route numbers")
G_cont, RoutesContReport = observe.street_continuity(G_cont, method="routes")
RoutesContReport.report_by_count(n_items=5)
RoutesContReport.report_by_length(n_items=5)

print("Continuity by highway types")
G_cont, HwyContReport = observe.street_continuity(G_cont, method="highways")
HwyContReport.report_by_count(n_items=5)
HwyContReport.report_by_length(n_items=5)

print("Continuity by overlapping routes and names types")
G_cont, HybridContReport = observe.hybrid_street_continuity(G_cont)
HybridContReport.report_by_count(n_items=5)
HybridContReport.report_by_length(n_items=5)
INFO:cityseer.metrics.observe:Calculating metrics for names.
Continuity by street names
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 47 - harrow road
INFO:cityseer.metrics.observe:Count: 39 - wandsworth road
INFO:cityseer.metrics.observe:Count: 38 - king's road
INFO:cityseer.metrics.observe:Count: 35 - holloway road
INFO:cityseer.metrics.observe:Count: 33 - caledonian road
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 4.77km - harrow road
INFO:cityseer.metrics.observe:Length: 3.55km - finchley road
INFO:cityseer.metrics.observe:Length: 3.23km - battersea park road
INFO:cityseer.metrics.observe:Length: 3.16km - holloway road
INFO:cityseer.metrics.observe:Length: 3.08km - wandsworth road
INFO:cityseer.metrics.observe:Calculating metrics for routes.
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 131 - a4
INFO:cityseer.metrics.observe:Count: 112 - a501
INFO:cityseer.metrics.observe:Count: 109 - a3220
INFO:cityseer.metrics.observe:Count: 87 - a400
INFO:cityseer.metrics.observe:Count: 80 - a3
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 11.48km - a4
INFO:cityseer.metrics.observe:Length: 11.32km - a3220
INFO:cityseer.metrics.observe:Length: 10.04km - a501
INFO:cityseer.metrics.observe:Length: 7.98km - a400
INFO:cityseer.metrics.observe:Length: 7.5km - a202
Continuity by route numbers
Continuity by highway types
INFO:cityseer.metrics.observe:Calculating metrics for highways.
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 12278 - residential
INFO:cityseer.metrics.observe:Count: 3487 - service
INFO:cityseer.metrics.observe:Count: 2782 - unclassified
INFO:cityseer.metrics.observe:Count: 2044 - primary
INFO:cityseer.metrics.observe:Count: 1701 - tertiary
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 1034.22km - residential
INFO:cityseer.metrics.observe:Length: 297.87km - service
INFO:cityseer.metrics.observe:Length: 229.81km - unclassified
INFO:cityseer.metrics.observe:Length: 178.19km - primary
INFO:cityseer.metrics.observe:Length: 143.56km - tertiary
INFO:cityseer.metrics.observe:Calculating metrics for routes.
Continuity by overlapping routes and names types
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 207 - a3220
INFO:cityseer.metrics.observe:Count: 184 - a4
INFO:cityseer.metrics.observe:Count: 181 - a202
INFO:cityseer.metrics.observe:Count: 181 - a501
INFO:cityseer.metrics.observe:Count: 176 - a3
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 19.69km - a3220
INFO:cityseer.metrics.observe:Length: 17.39km - a202
INFO:cityseer.metrics.observe:Length: 15.88km - a501
INFO:cityseer.metrics.observe:Length: 15.45km - a4
INFO:cityseer.metrics.observe:Length: 15.0km - a3
for method, shape_exp, descriptor, cmap, inverse, col_by_categ in zip(
    ["names", "routes", "highways", "hybrid"],  #
    [1, 0.75, 0.5, 1],  #
    ["Street names", "Routes", "Road types", "Hybrid routes & names"],  #
    ["plasma", "viridis", "tab10", "tab10"],  #
    [False, False, True, False],  #
    [False, False, True, True],
):
    print(f"Plotting results for method: {method}")
    # plot
    bg_colour = "#1d1d1d"
    fig, axes = plt.subplots(
        2, 1, dpi=150, figsize=(8, 12), facecolor=bg_colour, constrained_layout=True
    )
    fig.suptitle(
        f"OS Open Roads plotted by {descriptor} continuity",
        fontsize="small",
        ha="center",
    )
    # by count
    plot.plot_nx_edges(
        axes[0],  # type: ignore
        nx_multigraph=G_cont,
        edge_metrics_key=f"{method}_cont_by_count",
        bbox_extents=plot_bbox,
        cmap_key=cmap,
        lw_min=0.5,
        lw_max=2,
        edge_label_key=f"{method}_cont_by_label",
        colour_by_categorical=col_by_categ,
        shape_exp=shape_exp,
        face_colour=bg_colour,
        invert_plot_order=inverse,
    )
    axes[0].set_xlabel(f"{descriptor} by count", fontsize="x-small")  # type: ignore
    # by length
    plot.plot_nx_edges(
        axes[1],  # type: ignore
        nx_multigraph=G_cont,
        edge_metrics_key=f"{method}_cont_by_length",
        bbox_extents=plot_bbox,
        cmap_key=cmap,
        lw_min=0.5,
        lw_max=2,
        edge_label_key=f"{method}_cont_by_label",
        colour_by_categorical=col_by_categ,
        shape_exp=shape_exp,
        face_colour=bg_colour,
        invert_plot_order=inverse,
    )
    axes[1].set_xlabel(f"{descriptor} by length (metres)", fontsize="x-small")  # type: ignore
    plt.show()
INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot
Plotting results for method: names
INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot

INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot
Plotting results for method: routes
INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot

INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot
Plotting results for method: highways
INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot

INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot
Plotting results for method: hybrid
INFO:cityseer.tools.plot:Extracting edge geometries
INFO:cityseer.tools.plot:Generating plot

Longer continuous routes (brighter colours) indicate streets that maintain their name or classification over greater distances, suggesting major through-routes. Fragmented streets (darker) are typically local or residential.

Conclusion

This notebook demonstrated how to compute street continuity metrics from OpenStreetMap data for London, using street names, route numbers, highway types, and a hybrid approach to measure how far streets extend as continuous entities. The analysis reveals the hierarchical structure of the road network, with major routes like the A4 and A501 achieving the longest continuous extents.

Next steps: For continuity from Ordnance Survey data, see OS Open Roads Continuity.