Urban metrics

library(osmnxr)

osmnxr summarises a street network with the geometric and topological measures used in urban morphology (Boeing 2025). We use a bundled real network — the centre of Olinda, Brazil — so everything here runs offline.

g <- ox_example("olinda")
g
#> 
#> ── osm_graph ───────────────────────────────────────────────────────────────────
#> 498 nodes, 1191 edges
#> Network type: "unknown"
#> Simplified: FALSE
#> CRS: "WGS 84"

Basic statistics

ox_basic_stats(g)
#> # A tibble: 1 × 7
#>   n_nodes n_edges total_length mean_length mean_out_degree self_loops circuity
#>     <int>   <int>        <dbl>       <dbl>           <dbl>      <int>    <dbl>
#> 1     498    1191       95484.        80.2            2.39          1     1.06

The pieces of this summary are standard urban indicators:

area_km2 <- as.numeric(sf::st_area(sf::st_convex_hull(sf::st_union(g$nodes)))) / 1e6
n_intersections <- sum(g$nodes$osmid %in% c(g$edges$u, g$edges$v))
n_intersections / area_km2 # intersections per km^2
#> [1] 151.0356

Circuity

Circuity is total street length over straight-line distance between segment endpoints. A value near 1 means straight streets; higher means more winding:

ox_circuity(g)
#> [1] 1.061807

Centrality: finding chokepoints

Betweenness centrality counts the share of shortest paths passing through each node. Its maximum highlights structural chokepoints — “a bridge connecting a city’s halves” (Boeing & Ha 2024) — that are single points of failure for mobility and resilience.

ct <- ox_centrality(g, type = "betweenness", normalized = TRUE)
ct[order(-ct$betweenness), ][1:5, ]
#> # A tibble: 5 × 2
#>        osmid betweenness
#>        <dbl>       <dbl>
#> 1 6146825103       0.183
#> 2 5662175659       0.178
#> 3 8291701581       0.165
#> 4 1572677068       0.163
#> 5 5662175651       0.160

Map it: the darkest, largest nodes carry the most through-traffic.

nodes <- g$nodes
nodes$betweenness <- ct$betweenness[match(nodes$osmid, ct$osmid)]
plot(g, col = "grey80", lwd = 0.6)
plot(nodes["betweenness"], pch = 19,
     cex = 0.4 + 4 * nodes$betweenness / max(nodes$betweenness),
     pal = function(n) hcl.colors(n, "YlOrRd", rev = TRUE), add = TRUE)

The high-betweenness nodes trace the through-routes that hold the network together — exactly the junctions a planner would protect or reinforce.

References

Boeing, G. (2025). Modeling and analyzing urban networks and amenities with OSMnx. Geographical Analysis.

Boeing, G., & Ha, J. (2024). Resilient by design: simulating street network disruptions across every urban area in the world. Transportation Research Part A.