The isochrone logic we have developed is used in a number of tools throughout the application. This document details the process, highlighting any approximations made.
Many stages go into producing the isochrones, starting from raw input files through to rendering the time information on the map.
Input data
An isochrone needs to know what roads it’s traversing. Our road network is built from OpenStreetMap (OSM) data, determining mode accessibility and travel speeds all from the tags that are provided by OSM contributors. See the appendix at the bottom of this article for our interpretation of OSM tags and what they mean for the routing behaviour over the network.
Once we know where people can travel based on roads and junctions, we need to determine the height profile of the road in order to know how hard it is to traverse. Our height information comes from Ordnance Survey’s Terrain50 data.
We also include the use of public transport in our routing algorithm, more specifically bus and railway.
The bus information comes from the Bus Open Data Service (BODS) and the rail information comes from the Rail Delivery Group. Both sources contain the locations of station stops and the times that services run between them.
Building a routable network
The base network
In order to route a network, we need a data structure that contains all of the possible paths that can be taken. From a given point the program needs to know which other points are reachable and how long it will take to reach them. To capture this we make a network graph. It represents the network as a set of edges and vertices, where a vertex is a point where two or more edges meet. An edge always has a single start vertex and end vertex.
We start with the OSM road network, turning every road and junction into edges and vertices. Each edge has a set of properties which we determine from the source OSM tags:
- Access in both directions, for each routable mode. See appendix for more details
- Car speed, assumed the same in both directions. See appendix for more details
Once we have a corresponding road edge for every road, we assess each generated edge for its suitability to remain in the graph:
- If an edge cannot be traversed by any of our routing modes, it is removed
- Many roads are very short and produce edges that don’t provide value to the routing output. We dub these edges cul-de-sacs and define them as an edge that is a dead-end (only connected at one end) and shorter than 50m in length
Adding public transport
For each public transport service we create an edge between each pair of adjacent stops. If possible, we snap the vertices for the edge to existing vertices. A vertex is deemed suitable for snapping if it lies within 50m of the transit stop. If a nearby vertex cannot be found, a new vertex is inserted along the nearest existing edge within 10m, splitting the edge into two. If no edge is within 10m, then the service isn’t included in the network graph.
The final part of adding transit links is connecting them to the road network. While their endpoints may match spatially, a user cannot teleport from walking on a pavement to a seat on a bus (and vice versa). For example, the bus may not arrive at the exact moment the user reaches a bus stop. The route between road edge and transit edge is captured as a board-alight edge, whose cost embodies the waiting for a service to arrive.
The geometry of transit and board-alight edges is redundant; their purpose is to indicate available routes that a user can take at what cost.
Calculating the time it takes to traverse an edge
An isochrone represents the least time it takes to reach areas. To find the quickest path in a network, we need to know how long it takes to get around. The time it takes to traverse an edge depends both on the edge type and the mode that is traversing it.
Calculating road traverse times for cars
The time it takes for a car to traverse a road edge is
\[t=\frac{l}{s}\]Where:
- \(l\) is the length of the edge, in metres
- \(s\) is the speed of the car, in metres per second
- \(t\) is the time taken to traverse the edge, in seconds
The length is calculated from the geometry of the edge, and the speed of the car is determined in an earlier stage (see The base network)
Calculating road traverse times for active travel (walking and cycling)
With the height information provided by Terrain50, we can calculate “effective” lengths for each road edge, which is the real length of the edge modified by its elevation profile. For instance, an edge which is hilly will effectively be longer than an equivalent edge that is flat, as it is more difficult to traverse.
See appendix for details on calculating the effective length
The time it takes for an active user to traverse an edge is
\[t=\frac{l_{\textrm{effective}}}{s}\]Where:
- \(l_{effective}\) is the effective length of the edge, in metres
- \(s\) is the speed of the user, in metres per second
- \(t\) is the time taken to traverse the edge, in seconds
The speed of active users depends on the mode of travel, and is approximated as the following:
| Mode of travel | Speed (mph) |
|---|---|
| ebike | 15.5 |
| bicycle | 11 |
| foot | 3 |
Calculating transit traverse times
The time it takes to traverse a transit edge is calculated by averaging the time it takes for the service to make the journey between the two transit stops that the edge connects. This information is taken from the timetable data and is independent of travel mode.
The time it takes to “board” a service (i.e. move from the road network onto the transit network) is given as
\[t=\frac{h}{2}\]Where:
- \(t\) is the time taken to board
- \(h\) is the headway, defined as the average time between services along that edge. This is calculated by dividing the length of the time period (below) by the number of services that occur during it.
We limit the resulting time to 15 minutes, as per the Department for Transport’s transport analysis guidance (TAG).
There is no cost to “alight” a service (i.e. move from the transit network to the road network).
To avoid losing the resolution of data provided by the timetables, we calculate traversal costs for a number of time periods:
- Weekday morning (7AM to 10AM)
- Weekday inter-peak (10AM to 4PM)
- Weekday afternoon (4PM to 7PM)
- Weekday evening (7PM to 11PM)
- Saturday daytime (8AM to 7PM)
- Saturday evening (7PM to 11PM)
- Sunday daytime (8AM to 7PM)
- Sunday evening (7PM to 11PM)
Routing over the network
Once we have a routable network and know the times it takes for a given mode to traverse its edges, we can compute the quickest route between two points. This is done using dijkstra’s algorithm and is well defined elsewhere.
An isochrone needs to know the time it takes to reach all points from an origin, not just a single one, so our first job is to iterate over every vertex in the network and route to it, building up a “shortest path network” (SPN). The SPN tells us the time it takes to reach every point in the network.
Rendering the isochrone
The SPN tells us how long it takes to reach each vertex in the network, but that alone would result in a patchy isochrone as the data points only exist at the starts and ends of the edges. The first job for rendering is sampling points along the road edges, determining the cost to reach that point by linearly interpolating the cost to reach the start and end.
Once we have a sea of points and the time it takes to reach each one, we still don’t quite have an isochrone. We need to “fill in the blanks”, blending the points together to produce an area with highs and lows. This interpolation is done by the following:
- For each point, draw a circle around it
- The centre of the circle has the value of the point. As you approach the edge of the circle, increase the value to simulate travelling away from the road and into the overgrowth, where it becomes much harder to travel
- Where values overlap with a neighbouring circle, keep the lowest of the two values
Appendix
Mode access inferred from OSM tags
We determine what modes of travel can traverse a given road based on the value of the highway tag.
The baseline access to a road, based on highway tag value is as follows:
| highway value | Accessible on foot | Accessible on bike | Accessible by car |
|---|---|---|---|
| bridleway | Yes | Yes | No |
| cycleway | Yes | Yes | No |
| footway | Yes | No | No |
| living_street | Yes | Yes | Yes |
| motorway | No | No | Yes |
| motorway_link | No | No | Yes |
| path | Yes | Yes | No |
| pedestrian | Yes | No | No |
| primary | Yes | Yes | Yes |
| primary_link | Yes | Yes | Yes |
| residential | Yes | Yes | Yes |
| secondary | Yes | Yes | Yes |
| secondary_link | Yes | Yes | Yes |
| service | Yes | Yes | Yes |
| steps | Yes | No | No |
| tertiary | Yes | Yes | Yes |
| tertiary_link | Yes | Yes | Yes |
| trunk | Yes | Yes | Yes |
| trunk_link | Yes | Yes | Yes |
| unclassified | Yes | Yes | Yes |
There are also several ways in which a road can declare exceptions to the inferred value, for example on one-way roads, roads that include a cycle path, or roads where pedestrians aren’t allowed. Such cases can become complicated and contradictory.
An example way’s tags are as follows:

In this case, we would begin by assuming all modes can access the road due to highway=unclassified. We then know that cars can only travel in the forward direction due to oneway=yes. However, bicycles are not restricted to only one-way flow due to oneway:bicycle=no.
Car traversal speed inferred from OSM tags
Like access, we determine a car’s approximate speed along a road based on the OSM tags associated with it. Again, we start from the highway tag to determine a base speed:
| highway value | Assumed speed (MPH) |
|---|---|
| motorway | 68 |
| motorway_link | 50 |
| trunk | 50 |
| trunk_link | 40 |
| primary | 50 |
| primary_link | 40 |
| secondary | 40 |
| secondary_link | 30 |
| tertiary | 35 |
| tertiary_link | 25 |
| living_street | 5 |
| pedestrian | 5 |
| residential | 25 |
| unclassified | 25 |
| service | 15 |
| track | 10 |
| road | 25 |
Individual roads can also give a maxspeed tag, which overrides our assumed base speed.
Using the same example road above:

Here, we would begin by assuming a car speed of 25 due to highway=unclassified. However, this road also gives a maxspeed=20 mph, which would override our base speed, resulting in cars travelling along this road at 20mph.
Calculating the effective length of an edge
Take the following representation of how an edge’s elevation varies along its length

The elevation profile is represented as a list of pairs of values, where each pair is given as the distance along the edge and the elevation at that point. For the above edge, it would look something like:
| Distance along edge (m) | Elevation (m) |
|---|---|
| 0 | 10 |
| 50 | 80 |
| 100 | 20 |
| 150 | 10 |
We determine the effective length for each segment along the height profile. There are three segments for the above edge, highlighted and labelled in red:

The effective length for a segment is based on the ratio of the average speed a user can maintain if the segment was flat to the average speed a user can maintain at the segment’s inclination.
\[l_{\textrm{effective}} = \frac{s_{\textrm{flat}}}{s_{\textrm{slope}}}\cdot l\]Where:
- \(l\) is the length of the segment, in metres
- \(l_{effective}\) is the effective length of the segment, in metres
- \(s_{flat}\) is the speed of the user traversing the segment if it were flat, in metres per second
- \(s_{slope}\) is the speed of the user traversing the segment, in metres per second
Calculating the speed of the user varies depending on the mode.
Calculating the speed of a bicycle on an incline
The speed of a bike at any slope is a function of the pedalling power and is found at https://www.sciencedirect.com/org/science/article/pii/S1556831822001757#fo0040.
\[W=\frac{V}{\eta}\left(mg\left(r+\frac{\theta}{100}\right)\right)+\frac{V}{\eta}\left(\frac{DA\rho}{2}(V+h)^2\right)\]Where:
- \(A\) is the frontal surface area, in square metres, with a constant value of 0.5 suggested by the source article
- \(D\) is the aerodynamic drag coefficient, with a constant, dimensionless value of 1.2 suggested by the source article
- \(g\) is the acceleration due to gravity, in metres per second squared, with a constant value of 9.8067
- \(h\) is the wind speed at 1.2m above ground, in metres per second, assumed to be 0 due to large variance between places and time of year
- \(m\) is the weight of the bike and user, in kilograms
- \(r\) is the coefficient of rolling resistance, with a constant, dimensionless value of 0.008 suggested by the source article
- \(V\) is the velocity of the bike, in metres per second
- \(W\) is the pedal power, in watts
- \(\eta\) is the mechanical efficiency of the bike, with a constant, dimensionless value of 0.95 suggested by the source article
- \(\theta\) is the percentage slope
- \(\rho\) is the density of air, in kilograms per cubic metre, with a constant value of 1.226 suggested by the source article
With the simplifications, the equation can be rewritten as:
\[W=\frac{V}{\eta}\left(mg\left(r+\frac{\theta}{100}\right)+\frac{DA\rho}{2}V^2\right)\]Multiplying by mechanical efficiency, \(\eta\), to get the actual power being applied by the bike to the road, \(P\)
\[P=W\eta=V\left(mg\left(r+\frac{\theta}{100}\right)+\frac{DA\rho}{2}V^2\right)\]Given that \(P=FV\), we can see that the bracketed part of the RHS represents our resisting forces, made up of weight, rolling resistance, and drag.
\[P=W\eta=V(F_{\textrm{W}}+F_{\textrm{R}}+F_{\textrm{D}})\]Drag, \(F_D\), is a function of velocity, where the constants can be collected into a single constant, \(\mu_D\):
\[F_D=\frac{DA\rho}{2}V^2=\mu_{\textrm{D}}V^2
\]
The final issue is that we don’t have a value for the “percentage slope”, \(\theta\). Instead, using standard formulas for the resistance forces, we can substitute in functions for the angle of the slope, \(\phi\):
| Resistance force | \(f(\theta)\) | \(f(\phi)\) |
|---|---|---|
| Weight, \(F_{\textrm{W}}\) | \(mg\frac{\theta}{100}\) | \(mg\sin(\phi)\) |
| Rolling resistance, \(F_{\textrm{R}}\) | \(mgr\) | \(mgr\cos(\phi)\) |
Where \(\phi\) is calculated as:
\[\phi=\tan\left(\frac{\Delta_{\textrm{elevation}}}{l_{\textrm{segment}}}\right)\]Where:
- \(l_{\textrm{segment}}\) is the length of the segment, in metres
- \(\Delta_{\textrm{elevation}}\) is the change in elevation along the segment, in metres
Combining the above:
\[\begin{aligned}
P&=V(F_{\textrm{W}}+F_{\textrm{R}}+\mu_{\textrm{D}}V)\\
F_W&=mg\sin(\phi)\\
F_R&=mgr\cos(\phi)\\
\mu_D&=\frac{DA\rho}{2}
\end{aligned}
\]
Finally, we arrive at a cubic relating the road power, \(P\), and velocity of the bike, \(V\):
\[\mu_{\textrm{D}}V^3+(F_{\textrm{W}}+F_{\textrm{R}})V-P=0\]Given a power, \(P\), we can find the roots of the equation to find a plausible velocity for the bike at any given angle of elevation, \(\phi\). The power of a human is approximately 140W.
To model an e-bike, we use exactly the same process, except provide an additional contribution to the power on top of the user’s leg power. As e-bikes stop providing power when the bike is travelling over 15.5mph, this additional power scales up to a maximum motor value, so that the e-bike tries to maintain a minimum of 15.5mph at all times.
Calculating the speed of a pedestrian on an incline
The speed of a pedestrian is well defined by Tobler’s hiking function:
\[V=6e^{-3.5\left|\tan(\phi)+0.05\right|}\]Where:
- \(V\) is the walking speed, in kilometres per hour
- \(\phi\) is the angle of the slope, in degrees