OS Open Roads

Last update

last-modified

Open In Colab

Data Source

The following example uses the OS Open Roads dataset, which is available under the Open Government License.

© Crown copyright and database right 2022

Preparation

The following example assumes:

  • That the notebook is being run from a cloned cityseer-api repository.
  • That the above dataset has been downloaded to temp/os_open_roads/oproad_gb.gpkg as a relative path. If running this notebook directly from within a clone of the cityseer-api repo, then this equates to the cityseer-api/temp/os_open_roads/oproad_gb.gpkg path. Please edit the paths and path setup in this cell if you are using different directories.
from __future__ import annotations

from pathlib import Path

repo_path = Path.cwd()
if str(repo_path).endswith("continuity"):
    repo_path = Path.cwd() / "../.."
if not str(repo_path.resolve()).endswith("cityseer-examples"):
    raise ValueError(
        "Please check your notebook working directory relative to your project and data paths."
    )

open_roads_path = Path(repo_path / "temp/os_open_roads/oproad_gb.gpkg")
print("data path:", open_roads_path)
print("path exists:", open_roads_path.exists())
data path: /Users/gareth/dev/benchmark-urbanism/cityseer-examples/temp/os_open_roads/oproad_gb.gpkg
path exists: True

Extents

Instead of loading the entire dataset, we’ll use a bounding box to only load an area of interest.

from pyproj import Transformer
from shapely import geometry

from cityseer.tools import io

# create graph - only UK locations will work for OS Open Roads data
# stratford-upon-avon
# lng, lat, buffer_dist, plot_buffer = -1.7063649924889566, 52.19277374082795, 2500, 2000
# london
lng, lat, buffer_dist, plot_buffer = (
    -0.13039709427587876,
    51.516434828344366,
    6000,
    5000,
)
# transform from WGS to BNG
transformer = Transformer.from_crs("EPSG:4326", "EPSG:27700")
easting, northing = transformer.transform(lat, lng)
# calculate bbox relative to centroid
centroid = geometry.Point(easting, northing)
target_bbox: tuple[float, float, float, float] = centroid.buffer(buffer_dist).bounds  # type: ignore
plot_bbox: tuple[float, float, float, float] = centroid.buffer(plot_buffer).bounds  # type: ignore

Load

We can now load the OS Open Roads dataset

# load OS Open Roads data from downloaded geopackage
G_open = io.nx_from_open_roads(open_roads_path, target_bbox=target_bbox)
INFO:cityseer.tools.io:Nodes: 24773
INFO:cityseer.tools.io:Edges: 32732
INFO:cityseer.tools.io:Dropped 462 edges where not both start and end nodes were present.
INFO:cityseer.tools.io:Running basic graph cleaning
INFO:cityseer.tools.graphs:Removing filler nodes.
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 10.

Observe continuity metrics

This step runs the continuity analysis using the specified heuristic.

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_open, 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: 82 - finchley road
INFO:cityseer.metrics.observe:Count: 66 - harrow road
INFO:cityseer.metrics.observe:Count: 58 - old kent road
INFO:cityseer.metrics.observe:Count: 57 - king's road
INFO:cityseer.metrics.observe:Count: 53 - fulham road
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 5.4km - harrow road
INFO:cityseer.metrics.observe:Length: 5.34km - finchley road
INFO:cityseer.metrics.observe:Length: 4.48km - outer circle
INFO:cityseer.metrics.observe:Length: 3.7km - westway
INFO:cityseer.metrics.observe:Length: 3.28km - old kent 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: 138 - a1
INFO:cityseer.metrics.observe:Count: 135 - a10
INFO:cityseer.metrics.observe:Count: 129 - a41
INFO:cityseer.metrics.observe:Count: 128 - a107
INFO:cityseer.metrics.observe:Count: 116 - a5
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 9.1km - a41
INFO:cityseer.metrics.observe:Length: 7.96km - a10
INFO:cityseer.metrics.observe:Length: 7.88km - a1
INFO:cityseer.metrics.observe:Length: 7.74km - a3220
INFO:cityseer.metrics.observe:Length: 7.12km - a107
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: 5054 - a road
INFO:cityseer.metrics.observe:Count: 2498 - minor road
INFO:cityseer.metrics.observe:Count: 1709 - primary road
INFO:cityseer.metrics.observe:Count: 1504 - b road
INFO:cityseer.metrics.observe:Count: 461 - secondary access road
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 336.2km - a road
INFO:cityseer.metrics.observe:Length: 180.36km - minor road
INFO:cityseer.metrics.observe:Length: 116.84km - primary road
INFO:cityseer.metrics.observe:Length: 107.35km - b road
INFO:cityseer.metrics.observe:Length: 33.7km - secondary access road
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: 228 - a1
INFO:cityseer.metrics.observe:Count: 204 - a41
INFO:cityseer.metrics.observe:Count: 176 - a40
INFO:cityseer.metrics.observe:Count: 162 - a10
INFO:cityseer.metrics.observe:Count: 154 - a107
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 14.77km - a41
INFO:cityseer.metrics.observe:Length: 13.38km - a40
INFO:cityseer.metrics.observe:Length: 13.0km - a1
INFO:cityseer.metrics.observe:Length: 10.98km - a3220
INFO:cityseer.metrics.observe:Length: 9.46km - a10
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

Conclusion

This notebook demonstrated how to compute street continuity metrics from the OS Open Roads dataset for London, applying the same name, route, highway, and hybrid continuity heuristics as the OSM example but using an authoritative UK national dataset. The OS Open Roads data provides well-structured road classification attributes, producing clean continuity results that highlight the extent of major arterial routes like the A1 and A41.

Next steps: For other analysis types, return to the Recipes overview.