OS Open Roads

Last update

March 23, 2024

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/examples/continuity/../../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: 24778
INFO:cityseer.tools.io:Edges: 32723
INFO:cityseer.tools.io:Dropped 463 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.
100%|██████████| 24778/24778 [00:00<00:00, 33240.53it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 10.
100%|██████████| 30681/30681 [00:00<00:00, 50838.55it/s] 

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)
Continuity by street names
Continuity by route numbers
Continuity by highway types
Continuity by overlapping routes and names types
INFO:cityseer.metrics.observe:Calculating metrics for names.
100%|██████████| 30609/30609 [00:00<00:00, 31274.62it/s]
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: 62 - 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: 46 - clapham 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.
100%|██████████| 30609/30609 [00:00<00:00, 109845.11it/s]
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 142 - 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: 8.1km - a1
INFO:cityseer.metrics.observe:Length: 7.96km - a10
INFO:cityseer.metrics.observe:Length: 7.76km - a3220
INFO:cityseer.metrics.observe:Length: 7.12km - a107
INFO:cityseer.metrics.observe:Calculating metrics for highways.
100%|██████████| 30609/30609 [00:00<00:00, 156124.61it/s]
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 5056 - a road
INFO:cityseer.metrics.observe:Count: 2510 - minor road
INFO:cityseer.metrics.observe:Count: 1711 - primary road
INFO:cityseer.metrics.observe:Count: 1487 - b road
INFO:cityseer.metrics.observe:Count: 463 - secondary access road
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street lengths.
INFO:cityseer.metrics.observe:Length: 336.17km - a road
INFO:cityseer.metrics.observe:Length: 181.0km - minor road
INFO:cityseer.metrics.observe:Length: 116.88km - primary road
INFO:cityseer.metrics.observe:Length: 105.99km - b road
INFO:cityseer.metrics.observe:Length: 33.74km - secondary access road
INFO:cityseer.metrics.observe:Calculating metrics for routes.
100%|██████████| 30609/30609 [00:00<00:00, 118874.56it/s]
INFO:cityseer.metrics.observe:Reporting top 5 continuity observations by street counts.
INFO:cityseer.metrics.observe:Count: 230 - a1
INFO:cityseer.metrics.observe:Count: 204 - a41
INFO:cityseer.metrics.observe:Count: 162 - 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.76km - a41
INFO:cityseer.metrics.observe:Length: 13.1km - a1
INFO:cityseer.metrics.observe:Length: 12.38km - a40
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 = "#111"
    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
30609it [00:00, 469577.59it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 30609/30609 [00:02<00:00, 11117.21it/s]
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 545878.18it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 30609/30609 [00:03<00:00, 10180.88it/s]
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 464096.40it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 30609/30609 [00:03<00:00, 9764.13it/s] 
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 694550.28it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 30609/30609 [00:03<00:00, 8866.57it/s] 
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 532384.47it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 6/6 [00:02<00:00,  2.75it/s]
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 593554.44it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 6/6 [00:02<00:00,  2.96it/s]
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 652573.79it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 11/11 [00:02<00:00,  5.26it/s]
INFO:cityseer.tools.plot:Extracting edge geometries
30609it [00:00, 700114.25it/s]
INFO:cityseer.tools.plot:Generating plot
100%|██████████| 11/11 [00:01<00:00,  5.77it/s]
Plotting results for method: names
Plotting results for method: routes
Plotting results for method: highways
Plotting results for method: hybrid