Lane-accurate street maps with OpenStreetMap – writing a vector tileserver for osm2streets

I’ve built safecyclingmap.com, an open-source proof of concept map that renders cycleways and streets down to the individual lanes, to assist cyclists picking safe routes and support advocacy for reallocating road space to more efficient modes (as supported by the TfNSW Future Transport Strategy).

I’ve open sourced the backend infrastructure so that others can incorporate the details into their web and phone maps (see osm2streets-vector-tileserver or read below). This builds upon the incredible work of Dustin Carlino who leads the A/B Street traffic simulation project.

I wrote the initial vector tileserver from scratch in one afternoon in Typescript, calling the JS bindings for the Rust osm2streets code compiled to wasm. It generates GeoJSON which is then returned by Koa as a protobuf. I’ve spent some more hours improving performance, but there is much that could be improved.

The TfNSW Future Transport Strategy states “We will focus on getting more out of our existing investments, by reallocating road space to more efficient modes of transport like buses, walking, cycling and micromobility devices.”

This blog post explains how and why I built it.

Existing detailed street maps

Most street maps just use a line for each street:

Usually the colour of this line denotes how large or important the road is. If your city doesn’t have much dedicated bicycle infrastructure, picking a safe road can be a challenge.

In 2020 Google announced that it would be implementing more detailed street maps in selected cities:

Soon, you’ll be able to see highly detailed street information that shows the accurate shape and width of a road to scale. You can also see exactly where sidewalks, crosswalks, and pedestrian islands are located… We’ll start rolling out detailed street maps in London, New York, and San Francisco in the coming months, with plans to expand to more cities over time.

A more detailed, colorful map, Aug 18th 2020, Google.

Justin O’Beirne, a prolific writer on the consumer maps competitive landscape, wrote in April 2022 that Google has released detailed street maps in 40 cities so far. No cities in Australia make up this list.

Open source competition

OpenStreetMap (OSM) is a free, editable map of the whole world that is being built by volunteers largely from scratch and released with an open-content license. Because of the license it has grown quickly and now rivals (and often exceeds) the detail of commercial map providers. OSM data is used in Instagram, Wikipedia, Strava, Snapchat, Uber and many others.

An project using this data has been quietly generating highly detailed street maps. Dustin Carlino started building the A/B Street traffic simulation app since around 2019, and as part of this released the osm2streets project. osm2streets takes OSM data and draws detailed shapes of streets – though didn’t yet support rendering into generic web or phone “slippy” maps as detailed on this Github issue.

Brunswick, Melbourne rendered in A/B Street

I wrote an open source vector tileserver osm2streets-vector-tileserver to enable “slippy” web and phone maps to load the detailed car and bicycle lane information that osm2streets generates.

As a proof of concept, I’ve built safecyclingmap.com to consume the tiles produced by osm2streets-vector-tileserver. It is a visualisation of this detailed street data to assist cyclists with picking safe routes in cities with little dedicated cycling infrastructure.

Technical details

Bear in mind: I initially wrote the tileserver in one afternoon, and I haven’t put much effort into reliably hosting the infrastructure yet, so this could be improved.

The backend works by taking vector tile requests from a frontend (with a url like https://api.safecyclingmap.com/tile/18/241180/157318).

Once a request is received, the tile at zoom level 15 that contains the requested tile as a subset is calculated.

The OSM XML is then fetched from a local Overpass Turbo instance for that superset tile, and the osm2streets street network output for this tile is generated (using the JS to wasm (Rust) bindings at https://www.npmjs.com/package/osm2streets-js).

Requested subset tiles are then generated using the street network object that covers the superset tile.

The geojson layers are then generated and combined, and returned to the client as a Protobuf.

If you have any questions or are interested in contributing, come and join the Matrix room at #osm2streets-vector-tileserver:matrix.org or raise a PR at https://github.com/jakecoppinger/osm2streets-vector-tileserver.

Caching

I’ve used two least-recently-used caches to cache the osm2streets network output, as well as the final individual vector tiles. Vector tiles requested that fit within the bounds of an in-progress zoom level 15 Overpass Turbo download (and associated network generation) busy-wait for the cache to become available (with a timeout if there is an error).

Currently it works in all of Australia, as downloading all map data for the entire world needs a larger hard drive! I needed 8GB of swap space to set up the docker image without a failure.

The frontend is currently hosted on Github pages (behind Cloudflare) because haven’t yet configured SSL config on AWS S3 for this domain.

The backend currently runs on a Vultr VPS, using Cloudflare Tunnel to send requests to localhost:3000.

Further work

Further features that I think would be useful:

  • Making it work worldwide by passing requests outside Australia to a public Overpass instance from https://wiki.openstreetmap.org/wiki/Overpass_API#Public_Overpass_API_instances – with the new caching there shouldn’t too much traffic
  • Contributing to osm2streets to pass extra attributes in the vector tiles returned, such as:
    • Speed of the street, to be able to colour code streets
    • Level of the street, to hide underground roads

One response to “Lane-accurate street maps with OpenStreetMap – writing a vector tileserver for osm2streets”

Leave a Reply

Your email address will not be published. Required fields are marked *