Madison

library(tidyverse)
library(osmdata)
library(sf)
library(tigris)
# set different Overpass server
set_overpass_url("https://overpass.private.coffee/api/interpreter")

We obtain all tertiary roads in the greater Madison area, as well as the city boundaries for Madison:

madison_city_limits <- places(state = "WI") |> filter(NAME == "Madison") |> st_transform(crs = "EPSG:4326")
bb <- c(-89.594193, 42.997023, -89.202118, 43.166131)
x <- opq (bbox = bb) |>
    add_osm_feature (key = "highway", value = "tertiary") |>
    osmdata_sf () 

tertiaries <- x$osm_lines

Now we filter to only the highways that are within Madison or have at least a portion within Madison.

msn_tertiary <- tertiaries[madison_city_limits, , op = st_intersects]

msn_tertiary |> 
  mutate(maxspeed_numeric = as.numeric(str_remove(maxspeed, " mph")),
         maxspeed_factor = as_factor(maxspeed)) |> 
  st_drop_geometry() |> 
  count(maxspeed_numeric) |> 
  gt::gt()
maxspeed_numeric n
15 3
20 16
25 445
30 307
35 153
40 23
45 5
50 1
NA 940

We see that roughly half of tertiary highways have missing speed limit tags. But of the ones that do have it, many of them have limits lower than the 30 mph that the Brokenspoke analysis assumes. Note that this is based on OSM segments without taking their length into account.

We can also map this data to get a better sense of the spatial distribution. One may hypothesize that the lower speed limit roads are closer to the center of Madison.

library(tmap)
tmap_mode("plot")
msn_tertiary |> 
  mutate(maxspeed_numeric = as.numeric(str_remove(maxspeed, " mph")),
         thirty_mph = case_when(maxspeed_numeric < 30 ~ "below 30 mph",
                                maxspeed_numeric == 30 ~ "30 mph",
                                maxspeed_numeric > 30 ~ "over 30 mph"
                                 )) |> 
  tm_shape() +
  tm_lines(col = "thirty_mph")

This seems to confirm that hypothesis: In central areas, a lot of tertiary roads have limits below 30 mph, whereas those with limits above 30 are in the peripheral areas.

Milwaukee

Let’s repeat the analysis for Milwaukee.

milwaukee_city_limits <- places(state = "WI") |> filter(NAME == "Milwaukee") |> st_transform(crs = "EPSG:4326")
bb <- c(-88.205109, 42.845378, -87.782135, 43.219020)
x <- opq (bbox = bb) |>
    add_osm_feature (key = "highway", value = "tertiary") |>
    osmdata_sf () 

tertiaries <- x$osm_lines

mke_tertiary <- tertiaries[milwaukee_city_limits, , op = st_intersects]

mke_tertiary |> 
  mutate(maxspeed_numeric = as.numeric(str_remove(maxspeed, " mph")),
         maxspeed_factor = as_factor(maxspeed)) |> 
  st_drop_geometry() |> 
  count(maxspeed_numeric) |> 
  gt::gt()
maxspeed_numeric n
20 4
25 360
30 2021
35 175
40 22
NA 205

Speed limit data on Milwaukee streets is more completed, and a large majority of them indeed have a speed limit of 30 mph.

mke_tertiary |> 
  mutate(maxspeed_numeric = as.numeric(str_remove(maxspeed, " mph")),
         thirty_mph = case_when(maxspeed_numeric < 30 ~ "below 30 mph",
                                maxspeed_numeric == 30 ~ "30 mph",
                                maxspeed_numeric > 30 ~ "over 30 mph"
                                 )) |> 
  tm_shape() +
  tm_lines(col = "thirty_mph")

The spatial distribution of 30 vs <30 mph streets is more even than in Madison. But streets with a speed limit above 30 mph are again at the edges of the city.