Modifying speed data in OSM files

Living in rural Devon, we are surrounded by many roads that have a nominal speed limit of 60mph on which that would be a wholly inadvisable speed to travel, with likely speeds generally falling closer to the 20-40mph range.

For some journeys, this could have a significant impact when calculating the journey time using r5py and valhalla, which (presumably) assume free-flowing traffic at the maximum permitted speed for the road.

Tip

If you’re a bilingual R/Python user, you could choose to switch over to r5r at this point as they natively support modifying max car speeds, both at a granular level or applying a more general scaling factor to certain types of roads.

import r5py
import pandas as pd
import geopandas
import contextily as cx

from lokigi.travel_utils import process_uk_network, prepare_valhalla_network, \
    build_time_matrix_valhalla, WayInspector, \
valhalla_audit_route_speeds, valhalla_detailed_route, \
valhalla_maneuvers_to_gdf
process_uk_network(
    "../../datasets/devon-260422.osm.pbf",
    "../../datasets/devon-260422-modified.osm.pbf",
    debug=True,
    minor_road_cap=5,
    debug_limit=50
    )
Reading from C:\lokigi\examples\datasets\devon-260422.osm.pbf
Will write to C:\lokigi\examples\datasets\devon-260422-modified.osm.pbf
Processing C:\lokigi\examples\datasets\devon-260422.osm.pbf...
[WAY 2252] ALL TAGS:
    highway: residential
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Holland Road
[WAY 2252] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2252] residential | original='30 mph' (parsed=30.0) -> new='5.0 (5 mph)' | reasons=['minor road cap -> 5'] | inferred_missing=False
[WAY 2253] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Bradham Lane
    surface: asphalt
[WAY 2253] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2253] tertiary | original='30 mph' (parsed=30.0) -> new='5.0 (5 mph)' | reasons=['minor road cap -> 5'] | inferred_missing=False
[WAY 2255] ALL TAGS:
    foot: yes
    highway: cycleway
    segregated: yes
    smoothness: good
    surface: asphalt
[WAY 2256] ALL TAGS:
    foot: yes
    highway: cycleway
    lit: no
    railway: abandoned
    segregated: no
    smoothness: good
    surface: asphalt
[WAY 2470] ALL TAGS:
    highway: service
[WAY 2470] service | original='None' (parsed=60.0) -> new='5.0 (5 mph)' | reasons=['minor road cap -> 5'] | inferred_missing=True
[WAY 2471] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 20 mph
    name: Withycombe Village Road
    sidewalk:both: separate
    surface: asphalt
[WAY 2471] MAXSPEED-RELATED TAGS:
    maxspeed: 20 mph
[WAY 2471] tertiary | original='20 mph' (parsed=20.0) -> new='5.0 (5 mph)' | reasons=['minor road cap -> 5'] | inferred_missing=False
Debug output truncated at 50/50 changes
Done! Saved to C:\lokigi\examples\datasets\devon-260422-modified.osm.pbf
Total Changes made: 134159

And let’s repeat this applying a simple multiplier for traffic.

process_uk_network(
    "../../datasets/devon-260422.osm.pbf",
    "../../datasets/devon-260422-modified-traffic.osm.pbf",
    debug=True,
    traffic_multiplier=0.6, # Lower = slower max speed assumed
    debug_limit=50
    )
Reading from C:\lokigi\examples\datasets\devon-260422.osm.pbf
Will write to C:\lokigi\examples\datasets\devon-260422-modified-traffic.osm.pbf
Processing C:\lokigi\examples\datasets\devon-260422.osm.pbf...
[WAY 2252] ALL TAGS:
    highway: residential
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Holland Road
[WAY 2252] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2252] residential | original='30 mph' (parsed=30.0) -> new='18.0 (18 mph)' | reasons=['traffic multiplier x0.6'] | inferred_missing=False
[WAY 2253] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Bradham Lane
    surface: asphalt
[WAY 2253] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2253] tertiary | original='30 mph' (parsed=30.0) -> new='18.0 (18 mph)' | reasons=['traffic multiplier x0.6'] | inferred_missing=False
[WAY 2255] ALL TAGS:
    foot: yes
    highway: cycleway
    segregated: yes
    smoothness: good
    surface: asphalt
[WAY 2256] ALL TAGS:
    foot: yes
    highway: cycleway
    lit: no
    railway: abandoned
    segregated: no
    smoothness: good
    surface: asphalt
[WAY 2470] ALL TAGS:
    highway: service
[WAY 2470] service | original='None' (parsed=60.0) -> new='18.0 (18 mph)' | reasons=['minor road cap -> 30', 'traffic multiplier x0.6'] | inferred_missing=True
[WAY 2471] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 20 mph
    name: Withycombe Village Road
    sidewalk:both: separate
    surface: asphalt
[WAY 2471] MAXSPEED-RELATED TAGS:
    maxspeed: 20 mph
[WAY 2471] tertiary | original='20 mph' (parsed=20.0) -> new='12.0 (12 mph)' | reasons=['traffic multiplier x0.6'] | inferred_missing=False
Debug output truncated at 50/50 changes
Done! Saved to C:\lokigi\examples\datasets\devon-260422-modified-traffic.osm.pbf
Total Changes made: 141920

Confirming the change has worked

Let’s load this modified file back in to confirm the change has taken.

inspector = WayInspector([
    2253  # from the debug output
])
Original File
inspector.apply_file("../../datasets/devon-260422.osm.pbf")

================================================================================
WAY 2253
highway: tertiary
maxspeed: 30 mph

-- maxspeed-related tags --
  maxspeed: 30 mph
  maxspeed:type: GB:nsl_restricted

-- all tags --
  highway: tertiary
  lanes: 2
  lit: yes
  maxspeed: 30 mph
  maxspeed:type: GB:nsl_restricted
  name: Bradham Lane
  surface: asphalt
Modified file
inspector.apply_file("../../datasets/devon-260422-modified.osm.pbf")

================================================================================
WAY 2253
highway: tertiary
maxspeed: 5 mph

-- maxspeed-related tags --
  maxspeed: 5 mph
  maxspeed:motorcar: 5 mph

-- all tags --
  highway: tertiary
  lanes: 2
  lit: yes
  maxspeed: 5 mph
  maxspeed:motorcar: 5 mph
  name: Bradham Lane
  surface: asphalt

And our traffic file:

inspector.apply_file("../../datasets/devon-260422-modified-traffic.osm.pbf")

================================================================================
WAY 2253
highway: tertiary
maxspeed: 18 mph

-- maxspeed-related tags --
  maxspeed: 18 mph
  maxspeed:motorcar: 18 mph

-- all tags --
  highway: tertiary
  lanes: 2
  lit: yes
  maxspeed: 18 mph
  maxspeed:motorcar: 18 mph
  name: Bradham Lane
  surface: asphalt

Trying this out

Let’s now show the impact of this change. First, let’s do the calculation on the unedited file.

Generating origins and destinations in the expected format

Let’s just calculate a single route we know covers a lot of minor roads.

Travelling from Cullompton to Cheriton Fitzpaine takes us along a large number of minor roads, so is a good test case.

Let’s travel from EX15 1HN to EX17 4JG.

origins = pd.DataFrame([{'id':'EX15 1HN', 'longitude': -3.3964515912457207, 'latitude': 50.85510704952628},
                        ])

origins = geopandas.GeoDataFrame(
    origins,
    geometry = geopandas.points_from_xy(
        origins['longitude'],
        origins['latitude']
        ),
    crs = 'EPSG:4326'
    )

origins
id longitude latitude geometry
0 EX15 1HN -3.396452 50.855107 POINT (-3.39645 50.85511)
destinations = pd.DataFrame([{'id':'EX17 4JG', 'longitude': -3.6102812023506217, 'latitude': 50.844712763862496},
                             {'id':'Exeter Airport', 'longitude': -3.413323770890914, 'latitude':50.73714162801647},
                             {'id': "Cornish Arms Tavistock", 'longitude': -4.146750479096123, 'latitude': 50.551448737522115}])

destinations = geopandas.GeoDataFrame(
    destinations,
    geometry = geopandas.points_from_xy(
        destinations['longitude'],
        destinations['latitude']
        ),
    crs = 'EPSG:4326'
    )

destinations
id longitude latitude geometry
0 EX17 4JG -3.610281 50.844713 POINT (-3.61028 50.84471)
1 Exeter Airport -3.413324 50.737142 POINT (-3.41332 50.73714)
2 Cornish Arms Tavistock -4.146750 50.551449 POINT (-4.14675 50.55145)

Routingpy

First, let’s calculate the travel for our unedited pbf file.

transport_network = r5py.TransportNetwork(
    "../../datasets/devon-260422.osm.pbf",
)

travel_time_matrix_car = r5py.TravelTimeMatrix(
    transport_network,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR]
    )
travel_time_matrix_car
from_id to_id travel_time
0 EX15 1HN EX17 4JG 28
1 EX15 1HN Exeter Airport 28
2 EX15 1HN Cornish Arms Tavistock 62

Comparing these with real-world travel times, it looks like they’d be reasonably accurate for early morning free-flowing traffic, though optimistic in some cases and pessimistic in others.

travel_time_matrix_car_valhalla = build_time_matrix_valhalla(
    origins_gdf=origins,
    destinations_gdf=destinations,
    valhalla_config_path="../../datasets/devon-260422_valhalla.json",
    costing="auto"
)

Let’s take a look at a detailed breakdown of this journey.

Warning

This point-to-point router isn’t really r5py’s strong point! So these routes may look a bit squiffy - but they do show us something useful for our experiments, so I’ve left it in.

This is explained in the following github issues: https://github.com/conveyal/r5/issues/863, https://github.com/r5py/r5py/issues/386

We haven’t used the point-to-point routing server for many years and it is not actively maintained. This dates from a time when we were exploring use of R5 as a replacement for OpenTripPlanner, but there has since been a clearer division of roles between the two projects with OpenTripPlanner 2 handling passenger-facing and point-to-point routing, while R5 is specialized in many-to-many urban analytics use cases. The point-to-point routing code paths do work somewhat differently than the one-to-many routing we use heavily for travel time mapping and accessibility calculations. We have not (yet) removed the point-to-point functionality in case anyone finds it useful, but realistically we probably won’t spend time exploring issues with this part of the codebase.

detailed_itineraries = r5py.DetailedItineraries(
    transport_network,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR],
    snap_to_network=True,
)

detailed_itineraries
from_id to_id option segment transport_mode departure_time distance travel_time wait_time feed agency_id route_id start_stop_id end_stop_id geometry
0 EX15 1HN EX17 4JG 0 0 TransportMode.CAR NaT 20403.065 0 days 00:35:49 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
1 EX15 1HN Exeter Airport 0 0 TransportMode.CAR NaT 20527.246 0 days 00:26:13 0 days None None None None None LINESTRING (-3.39673 50.85539, -3.39671 50.855...
2 EX15 1HN Cornish Arms Tavistock 0 0 TransportMode.CAR NaT 86600.881 0 days 01:59:35 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
ax = detailed_itineraries.plot(column="to_id")

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=detailed_itineraries.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

Repeating this with the modified matrix

Now let’s repeat this with our new matrix.

transport_network_modified = r5py.TransportNetwork(
    "../../datasets/devon-260422-modified.osm.pbf",
)

Our travel times appear to be unchanged.

travel_time_matrix_car_modified = r5py.TravelTimeMatrix(
    transport_network_modified,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR]
    )

travel_time_matrix_car_modified
from_id to_id travel_time
0 EX15 1HN EX17 4JG 28
1 EX15 1HN Exeter Airport 28
2 EX15 1HN Cornish Arms Tavistock 62

What about the detailed routes - even if they’re probably not right?

detailed_itineraries_modified = r5py.DetailedItineraries(
    transport_network_modified,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR],
    snap_to_network=True,

)

These have changed!

detailed_itineraries_modified
from_id to_id option segment transport_mode departure_time distance travel_time wait_time feed agency_id route_id start_stop_id end_stop_id geometry
0 EX15 1HN EX17 4JG 0 0 TransportMode.CAR NaT 21388.334 0 days 00:35:47 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
1 EX15 1HN Exeter Airport 0 0 TransportMode.CAR NaT 20527.246 0 days 00:32:57 0 days None None None None None LINESTRING (-3.39673 50.85539, -3.39671 50.855...
2 EX15 1HN Cornish Arms Tavistock 0 0 TransportMode.CAR NaT 86600.881 0 days 01:58:39 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
ax = detailed_itineraries_modified.plot(column="to_id")

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=detailed_itineraries_modified.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

Repeating with our traffic-altered file

Finally, let’s repeat with our traffic-altered file.

transport_network_modified_traffic = r5py.TransportNetwork(
    "../../datasets/devon-260422-modified-traffic.osm.pbf",
)

Once again, our travel matrix times are unchanged.

travel_time_matrix_car_modified_traffic = r5py.TravelTimeMatrix(
    transport_network_modified_traffic,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR]
    )

travel_time_matrix_car_modified
from_id to_id travel_time
0 EX15 1HN EX17 4JG 28
1 EX15 1HN Exeter Airport 28
2 EX15 1HN Cornish Arms Tavistock 62

But our detailed itineraries have changed.

detailed_itineraries_modified_traffic = r5py.DetailedItineraries(
    transport_network_modified_traffic,
    origins=origins,
    destinations=destinations,
    transport_modes=[r5py.TransportMode.CAR],
    snap_to_network=True,

)
detailed_itineraries_modified_traffic
from_id to_id option segment transport_mode departure_time distance travel_time wait_time feed agency_id route_id start_stop_id end_stop_id geometry
0 EX15 1HN EX17 4JG 0 0 TransportMode.CAR NaT 21388.334 0 days 00:26:54 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
1 EX15 1HN Exeter Airport 0 0 TransportMode.CAR NaT 20527.246 0 days 00:25:06 0 days None None None None None LINESTRING (-3.39673 50.85539, -3.39671 50.855...
2 EX15 1HN Cornish Arms Tavistock 0 0 TransportMode.CAR NaT 85051.652 0 days 01:29:07 0 days None None None None None LINESTRING (-3.39673 50.85504, -3.39672 50.855...
ax = detailed_itineraries_modified_traffic.plot(column="to_id")

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=detailed_itineraries_modified_traffic.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

NoteWhat’s going on?

It looks like routingpy uses some other way of calculating travel time when using its matrix function.

And they’re not bad! But it does mean we won’t be able to use this method for things like traffic modifications either.

Valhalla

Let’s compare with valhalla, a routing engine that’s much more focussed on car travel times.

In the past, valhalla had to be set up in a fairly daunting way, but we now have the pyvalhalla option, which is much more approachable!

prepare_valhalla_network(
    osm_path="../../datasets/devon-260422.osm.pbf",
    output_dir="../../datasets/",
    output_name="devon-260422_valhalla.json"
)
Using existing Valhalla build
{'config_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla.json.json',
 'tile_dir': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla.json_tiles',
 'traffic_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla.json_traffic.tar'}
travel_time_matrix_car_valhalla = build_time_matrix_valhalla(
    origins_gdf=origins,
    destinations_gdf=destinations,
    valhalla_config_path="../../datasets/devon-260422_valhalla.json",
    costing="auto"
)
travel_time_matrix_car_valhalla
from_id to_id travel_time_minutes distance_km
0 EX15 1HN EX17 4JG 21.783333 26.503
1 EX15 1HN Exeter Airport 14.166667 17.259
2 EX15 1HN Cornish Arms Tavistock 54.383333 89.2

This has worked!

Although compared to google maps, it seems quite optimistic - more so than r5py was.

We can now repeat this with our modified files.

prepare_valhalla_network(
    osm_path="../../datasets/devon-260422-modified.osm.pbf",
    output_dir="../../datasets/",
    output_name="devon-260422_valhalla-modified"
)
Using existing Valhalla build
{'config_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified.json',
 'tile_dir': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified_tiles',
 'traffic_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified_traffic.tar'}
travel_time_matrix_car_valhalla_modified = build_time_matrix_valhalla(
    origins_gdf=origins,
    destinations_gdf=destinations,
    valhalla_config_path="../../datasets/devon-260422_valhalla-modified.json",
    costing="auto"
)

This looks a little better - but it’s still optimistic, and the journey to the Cornish Arms has actually become a tiny bit faster, which is unexpected!

travel_time_matrix_car_valhalla_modified
from_id to_id travel_time_minutes distance_km
0 EX15 1HN EX17 4JG 24.066667 20.382
1 EX15 1HN Exeter Airport 16.1 23.31
2 EX15 1HN Cornish Arms Tavistock 54.35 89.2

And finally, let’s do this with our very heavy traffic file that assumed all traffic would only be travelling at 60% of the listed road speed.

prepare_valhalla_network(
    osm_path="../../datasets/devon-260422-modified-traffic.osm.pbf",
    output_dir="../../datasets/",
    output_name="devon-260422_valhalla-modified-traffic"
)
Using existing Valhalla build
{'config_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified-traffic.json',
 'tile_dir': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified-traffic_tiles',
 'traffic_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-modified-traffic_traffic.tar'}
travel_time_matrix_car_valhalla_modified = build_time_matrix_valhalla(
    origins_gdf=origins,
    destinations_gdf=destinations,
    valhalla_config_path="../../datasets/devon-260422_valhalla-modified-traffic.json",
    costing="auto"
)
travel_time_matrix_car_valhalla_modified
from_id to_id travel_time_minutes distance_km
0 EX15 1HN EX17 4JG 35.05 18.699
1 EX15 1HN Exeter Airport 25.466667 23.309
2 EX15 1HN Cornish Arms Tavistock 88.566667 88.944

So we can conclude that valhalla definitely responds to the maximum speed tags in a way that routingpy doesn’t - though it’s still not a perfect fix.

Tip

Routingpy remains an extremely good option if you are interested in public transport.

Valhalla is probably the better choice if you are looking for car travel times.

Valhalla does also have the benefit of having no java requirements whatsoever - so it’s easier to install in locked down environments.

For some projects, you may end up using a mix of both!

Let’s do one more modified file to see if we can make a difference.

This time, we’ll apply our same substantial slowdown to unclassified roads.

On top of that, we’ll add the assumption that all journies are slower than the posted speed limit on average, so we’ll bring speeds down to 90% of reality.

process_uk_network(
    "../../datasets/devon-260422.osm.pbf",
    "../../datasets/devon-260422-combined-modified.osm.pbf",
    debug=True,
    traffic_multiplier=0.9, # Lower = slower max speed assumed
    minor_road_cap=30,
    debug_limit=100
    )
Reading from C:\lokigi\examples\datasets\devon-260422.osm.pbf
Will write to C:\lokigi\examples\datasets\devon-260422-combined-modified.osm.pbf
Processing C:\lokigi\examples\datasets\devon-260422.osm.pbf...
[WAY 2252] ALL TAGS:
    highway: residential
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Holland Road
[WAY 2252] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2252] residential | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 2253] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Bradham Lane
    surface: asphalt
[WAY 2253] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 2253] tertiary | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 2255] ALL TAGS:
    foot: yes
    highway: cycleway
    segregated: yes
    smoothness: good
    surface: asphalt
[WAY 2256] ALL TAGS:
    foot: yes
    highway: cycleway
    lit: no
    railway: abandoned
    segregated: no
    smoothness: good
    surface: asphalt
[WAY 2470] ALL TAGS:
    highway: service
[WAY 2470] service | original='None' (parsed=60.0) -> new='27.0 (27 mph)' | reasons=['minor road cap -> 30', 'traffic multiplier x0.9'] | inferred_missing=True
[WAY 2471] ALL TAGS:
    highway: tertiary
    lanes: 2
    lit: yes
    maxspeed: 20 mph
    name: Withycombe Village Road
    sidewalk:both: separate
    surface: asphalt
[WAY 2471] MAXSPEED-RELATED TAGS:
    maxspeed: 20 mph
[WAY 2471] tertiary | original='20 mph' (parsed=20.0) -> new='18.0 (18 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 3363] ALL TAGS:
    highway: unclassified
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Prince of Wales Drive
[WAY 3363] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 3363] unclassified | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 3364] ALL TAGS:
    highway: unclassified
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Prince of Wales Drive
[WAY 3364] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 3364] unclassified | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 3365] ALL TAGS:
    highway: unclassified
    maxspeed: 20 mph
    maxspeed:type: GB:zone20
    name: Moorfield Road
[WAY 3365] MAXSPEED-RELATED TAGS:
    maxspeed: 20 mph
    maxspeed:type: GB:zone20
[WAY 3365] unclassified | original='20 mph' (parsed=20.0) -> new='18.0 (18 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 3368] ALL TAGS:
    highway: residential
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Park Road
[WAY 3368] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 3368] residential | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 78851] ALL TAGS:
    highway: residential
    lit: yes
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
    name: Newlands Avenue
[WAY 78851] MAXSPEED-RELATED TAGS:
    maxspeed: 30 mph
    maxspeed:type: GB:nsl_restricted
[WAY 78851] residential | original='30 mph' (parsed=30.0) -> new='27.0 (27 mph)' | reasons=['traffic multiplier x0.9'] | inferred_missing=False
[WAY 78856] ALL TAGS:
Debug output truncated at 100/100 changes
Done! Saved to C:\lokigi\examples\datasets\devon-260422-combined-modified.osm.pbf
Total Changes made: 141920
prepare_valhalla_network(
    osm_path="../../datasets/devon-260422-combined-modified.osm.pbf",
    output_dir="../../datasets/",
    output_name="devon-260422_valhalla-combined-modified"
)
Using existing Valhalla build
{'config_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-combined-modified.json',
 'tile_dir': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-combined-modified_tiles',
 'traffic_path': 'C:\\lokigi\\examples\\datasets\\devon-260422_valhalla-combined-modified_traffic.tar'}
travel_time_matrix_car_valhalla_final = build_time_matrix_valhalla(
    origins_gdf=origins,
    destinations_gdf=destinations,
    valhalla_config_path="../../datasets/devon-260422_valhalla-combined-modified.json",
    costing="auto"
)

travel_time_matrix_car_valhalla_final
from_id to_id travel_time_minutes distance_km
0 EX15 1HN EX17 4JG 23.783333 18.699
1 EX15 1HN Exeter Airport 17.5 23.309
2 EX15 1HN Cornish Arms Tavistock 60.383333 89.2

While this still isn’t great for one of our journies, the other two are now quite accurate.

Advanced valhalla route inspection and debugging

We can do a bit more debugging…

from valhalla import Actor

result, summary = valhalla_detailed_route(engine=Actor("../../datasets/devon-260422_valhalla-combined-modified.json"),
                        origin=(origins.iloc[0].latitude, origins.iloc[0].longitude),
                        destination=(destinations.iloc[0].latitude, destinations.iloc[0].longitude),
                        )

result

=== OVERALL ===
Time: 23.8 min
Distance: 11.62 miles

--- LEG 0 ---
Time: 23.8 min
Distance: 11.62 miles
Speed: 29.3 miles/h

Maneuvers:
23.279s | 0.1721 | Drive north on Shortlands Road.
177.943s | 1.3023 | Turn left onto Tiverton Road.
193.822s | 1.4366 | Bear left.
67.676s |  0.479 | Turn left.
8.794s | 0.0596 | Turn left.
41.361s | 0.2746 | Turn right.
13.657s |    0.1 | Turn left.
154.684s | 1.1414 | Turn right.
99.832s | 0.7052 | Turn right.
54.456s | 0.2516 | Turn right.
32.896s | 0.2491 | Turn right onto A396.
201.203s | 2.8471 | Turn left onto A3072.
47.666s | 0.3324 | Turn right onto Uppincott Lane.
56.956s | 0.4212 | Turn left.
240.096s | 1.7789 | Keep left at the fork.
12.825s | 0.0671 | Turn right.
 0.0s |    0.0 | Your destination is on the right.
{'trip': {'locations': [{'type': 'break',
    'lat': 50.855107,
    'lon': -3.396451,
    'side_of_street': 'right',
    'original_index': 0},
   {'type': 'break',
    'lat': 50.844712,
    'lon': -3.610281,
    'side_of_street': 'right',
    'original_index': 1}],
  'legs': [{'maneuvers': [{'type': 2,
      'instruction': 'Drive north on Shortlands Road.',
      'verbal_succinct_transition_instruction': 'Drive north. Then, in 900 feet, Turn left onto Tiverton Road.',
      'verbal_pre_transition_instruction': 'Drive north on Shortlands Road. Then, in 900 feet, Turn left onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for 900 feet.',
      'street_names': ['Shortlands Road'],
      'bearing_after': 4,
      'time': 23.279,
      'length': 0.1721,
      'cost': 34.167,
      'begin_shape_index': 0,
      'end_shape_index': 18,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left onto Tiverton Road.',
      'verbal_transition_alert_instruction': 'Turn left onto Tiverton Road.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for 1.5 miles.',
      'street_names': ['Tiverton Road'],
      'bearing_before': 14,
      'bearing_after': 284,
      'time': 177.943,
      'length': 1.3023,
      'cost': 194.035,
      'begin_shape_index': 18,
      'end_shape_index': 107,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 16,
      'instruction': 'Bear left.',
      'verbal_transition_alert_instruction': 'Bear left.',
      'verbal_succinct_transition_instruction': 'Bear left.',
      'verbal_pre_transition_instruction': 'Bear left.',
      'verbal_post_transition_instruction': 'Continue for 1.5 miles.',
      'bearing_before': 338,
      'bearing_after': 296,
      'time': 193.822,
      'length': 1.4366,
      'cost': 189.706,
      'begin_shape_index': 107,
      'end_shape_index': 168,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left.',
      'verbal_transition_alert_instruction': 'Turn left.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left.',
      'verbal_post_transition_instruction': 'Continue for a half mile.',
      'bearing_before': 334,
      'bearing_after': 235,
      'time': 67.676,
      'length': 0.479,
      'cost': 70.843,
      'begin_shape_index': 168,
      'end_shape_index': 215,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left.',
      'verbal_transition_alert_instruction': 'Turn left.',
      'verbal_succinct_transition_instruction': 'Turn left. Then, in 300 feet, Turn right.',
      'verbal_pre_transition_instruction': 'Turn left. Then, in 300 feet, Turn right.',
      'verbal_post_transition_instruction': 'Continue for 300 feet.',
      'bearing_before': 230,
      'bearing_after': 160,
      'time': 8.794,
      'length': 0.0596,
      'cost': 11.733,
      'begin_shape_index': 215,
      'end_shape_index': 221,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right.',
      'verbal_transition_alert_instruction': 'Turn right.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'bearing_before': 140,
      'bearing_after': 222,
      'time': 41.361,
      'length': 0.2746,
      'cost': 40.804,
      'begin_shape_index': 221,
      'end_shape_index': 237,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left.',
      'verbal_transition_alert_instruction': 'Turn left.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left.',
      'verbal_post_transition_instruction': 'Continue for 500 feet.',
      'bearing_before': 276,
      'bearing_after': 228,
      'time': 13.657,
      'length': 0.1,
      'cost': 16.631,
      'begin_shape_index': 237,
      'end_shape_index': 250,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right.',
      'verbal_transition_alert_instruction': 'Turn right.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right.',
      'verbal_post_transition_instruction': 'Continue for 1 mile.',
      'bearing_before': 203,
      'bearing_after': 251,
      'time': 154.684,
      'length': 1.1414,
      'cost': 154.73,
      'begin_shape_index': 250,
      'end_shape_index': 331,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right.',
      'verbal_transition_alert_instruction': 'Turn right.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right.',
      'verbal_post_transition_instruction': 'Continue for a half mile.',
      'bearing_before': 213,
      'bearing_after': 296,
      'time': 99.832,
      'length': 0.7052,
      'cost': 99.687,
      'begin_shape_index': 331,
      'end_shape_index': 386,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right.',
      'verbal_transition_alert_instruction': 'Turn right.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'bearing_before': 285,
      'bearing_after': 6,
      'time': 54.456,
      'length': 0.2516,
      'cost': 60.248,
      'begin_shape_index': 386,
      'end_shape_index': 414,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto A396.',
      'verbal_transition_alert_instruction': 'Turn right onto A396.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto A396.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'street_names': ['A396'],
      'bearing_before': 303,
      'bearing_after': 349,
      'time': 32.896,
      'length': 0.2491,
      'cost': 51.46,
      'begin_shape_index': 414,
      'end_shape_index': 436,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left onto A3072.',
      'verbal_transition_alert_instruction': 'Turn left onto A3072.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left onto A3072.',
      'verbal_post_transition_instruction': 'Continue for 3 miles.',
      'street_names': ['A3072'],
      'bearing_before': 357,
      'bearing_after': 288,
      'time': 201.203,
      'length': 2.8471,
      'cost': 186.702,
      'begin_shape_index': 436,
      'end_shape_index': 636,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Uppincott Lane.',
      'verbal_transition_alert_instruction': 'Turn right onto Uppincott Lane.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Uppincott Lane.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'street_names': ['Uppincott Lane'],
      'bearing_before': 273,
      'bearing_after': 344,
      'time': 47.666,
      'length': 0.3324,
      'cost': 56.611,
      'begin_shape_index': 636,
      'end_shape_index': 656,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left.',
      'verbal_transition_alert_instruction': 'Turn left.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left.',
      'verbal_post_transition_instruction': 'Continue for a half mile.',
      'bearing_before': 356,
      'bearing_after': 305,
      'time': 56.956,
      'length': 0.4212,
      'cost': 57.667,
      'begin_shape_index': 656,
      'end_shape_index': 679,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 24,
      'instruction': 'Keep left at the fork.',
      'verbal_transition_alert_instruction': 'Keep left at the fork.',
      'verbal_pre_transition_instruction': 'Keep left at the fork.',
      'verbal_post_transition_instruction': 'Continue for 2 miles.',
      'bearing_before': 293,
      'bearing_after': 279,
      'time': 240.096,
      'length': 1.7789,
      'cost': 251.375,
      'begin_shape_index': 679,
      'end_shape_index': 824,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right.',
      'verbal_transition_alert_instruction': 'Turn right.',
      'verbal_succinct_transition_instruction': 'Turn right. Then, in 400 feet, Your destination will be on the right.',
      'verbal_pre_transition_instruction': 'Turn right. Then, in 400 feet, Your destination will be on the right.',
      'verbal_post_transition_instruction': 'Continue for 400 feet.',
      'bearing_before': 242,
      'bearing_after': 319,
      'time': 12.825,
      'length': 0.0671,
      'cost': 17.142,
      'begin_shape_index': 824,
      'end_shape_index': 837,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 5,
      'instruction': 'Your destination is on the right.',
      'verbal_transition_alert_instruction': 'Your destination will be on the right.',
      'verbal_pre_transition_instruction': 'Your destination is on the right.',
      'bearing_before': 282,
      'time': 0.0,
      'length': 0.0,
      'cost': 0.0,
      'begin_shape_index': 837,
      'end_shape_index': 837,
      'travel_mode': 'drive',
      'travel_type': 'car'}],
    'summary': {'has_time_restrictions': False,
     'has_toll': False,
     'has_highway': False,
     'has_ferry': False,
     'min_lat': 50.836754,
     'min_lon': -3.610295,
     'max_lat': 50.867483,
     'max_lon': -3.396712,
     'time': 1427.153,
     'length': 11.6191,
     'cost': 1493.55},
    'shape': '}k}~_BhfinEWAqFWqG\\iBZyFpAeT|B{Cx@qB`CoExJsCjJcCfImEpJsBnByBz@wBTyEa@uNyDyWwHwC`]SbCe@lGoArLsC`UqF~[cG|XyF~PoBdGiCfHoDfJ{DbLqKv[eIvR_IrSqBdGcAxDmBnHkCpLeClMk@|FYpFOhF@lFFrGPbHb@vMP|FLlDv@xWXtKBrCCpCEtAQxHgA~e@SdJStJm@lXgAfXcAbOMbB_@fE}@pF{@rG}C~VwFrd@_F|X}I~a@_Jlh@cEh[_Kvt@_Jpp@cHnh@gGbd@kDv]cD|a@wCrYsCpTgDlR{J~c@cKha@a@dAgFzMcGjSiIjWcFpLcFtJ_GrKgHfL}K~OiEpFgGvJ}DrHuAhEiDzNyKht@mGt\\uHb^gGnS}FrSqAzCuBdD_DnDaEvCkLnFaVjJaSfIeZbPY`CaKr_@cCzRyB~XsAfW@bMfA|PrB|QvSfaBjLrx@fBnQ|@hN|Adc@dAb[\\xaA`@nYh@zZ`Bdj@nLho@vNpi@bD`XpBfY`EdvAt@lkAfB~lABtm@?rl@tCv}@nAly@yBj]aDjcAkBlr@}Dp}@oKlTuOpHgEfCgA|CKdF\\jKxGzaAfB`^FlKOlD}@pEuCnIqCfKgD~YqCj_@qBxS{BpNoD~OoErPgDlKcArFqB|EgBtCmExCcM|Her@x^k^rQ}QnMd@vB~HhR`LfVpGbRvFxSxEzWzFzVlAfFtAxCpE~HrV|b@jZpw@dTfm@bHzPbP|`@r@jAv@bAtHbIbA`ATRrBlBvGpFjIrGv@n@v@t@r@v@rBbCrApBpAxBhJxPp@bAr@~@pCdD~FbHxAnB|A|BrDrFpB`DrAlBzAnB~ExFj@t@l@hAl@vA|AvD|ChI~AbDvAs@xEwA~F_F`BuAhG}H~NaTfKhPvCdH|Erj@~En_@v@dEhGbSpJtWnQbh@bO~f@tHr^jInZlC`LdAlIX~GG~EU~ElAh@~@nDhDnO~DtOxE|OvAfEpBpEfBtB|C|B~EtCjBfA|KpEfAlAdAhF^pIErLtCxb@lBvN|AjHzA|EpEjIbG~H`AzBb@tCj@tCzTbx@dJjXdTbZpAbEnE|WvIxa@lAdLIlLhGnj@w@d_@jBdc@bIrl@cBf[mBjGu@hCwAhIRvJ|CnRhD`Td@zHThVBlV|@|ChB~CzH|NfHd^zEjXbErIlHjI|EtO~BjGdBjB~BlD`AxDfBrV|AfSf@fGt@vGfJxq@|Ips@z@bFtAlEjAzBbDxFjFrInBpC~DjJ`DxIhBfKjDj[bDdY`ElZhFx[dBjJfC|KpC|J`DtIlEjKbDhLzAtHN`Md@xTf@bS~BbUbC`PtClLrZng@d_@xi@`DnCYv@qArFWbHr@`UpAtLp@nIp@|Gf@xHVtSE|MMn[`@xQlB~QxF~WrDp`@xAbQJ~DoCtS[xFHlHVrM[z_@]|DeAfGoB`K_EtQkCtF{CnGgCrKcAdJc@pKO|IiBd{@kFpzAe@vNaBjKmAfF]hE|Alr@|BbX`@xJPzQElNKrTTfKzAje@zEdk@|E~c@bAnMXlHDvDEnBg@~Ea@fEGz@uEe@wFa@yCN_Cv@uDtC_ClAoBZiBp@yQ~LyPpJ{HdGoBzCcHfMu@W_C`NkEtRaDdRiEnS}BfGqCvEqChBwFhDkIvFsGlI}C|CuCzC[pBEjBwAG{A\\mB~@wBpBgDbJqKjVsHtW}CrLwQpy@yDdHcEnFcDzBmGjC}TpDyJnCsFbGuJp\\eB`EwBxAqAVaAAoBaAYpBe@~Dk@dEQhB]dHAxMBrG?XXdMX`Ff@nFnA`JnAtKRz@Zh@rBpAxAfA~BrDjBvDrCrFhCjEbDfClDlAhFb@zCZrD~@|IvDvD|CtDvDzB|B|DjEvHtHlEpC`BdBrGpFnOzExK~@jKvClHfGpEvLbDvHxCfFrDlCtFb@pIsFtMiO~IsBxHl@pG~DdHpItTp`@zErMfTf^vGdOnQjq@zYfu@pAnCnFbJlFrKfQnYnKzR`H`SzBjK|AvTfG~zA~IvfB|Bd\\lCtVxB|NjBpH~B~GhDhH|HhOvEpMpAlF|@vG~@lJdAl_@dBpi@r@x^hBnWhCjR~CfQjVnnApDfIbClDnAhA`Ax@`Dx@nP^tOoAbEQ`GbCnHlHjOlWrIvLnCpD|AxDfAfEv@nFt@vL`AjJz@bF~AdFtEbEl]~[xSfQ|ItCjW`I`MvEpJfCdJjDlFvBzDpBnItGrD`FlDjG`CtFnAtDbAhDtArG|C`RjDfTzGjb@rC|SfJvy@vEza@tBzNtBvIjCtHvBbFfCbEfNzOzG`IpEvI|B`IlE~MlD~IvDxGbClC~AxA`Aj@tCxArMhEdLdE``@fO~HvD|IhF`M`JvCvBbEnB`KhE~EdCbExCdC`CrB|CjB~E|AhFzAnM|A~UrDpo@vB|WbDrZ~Ef^dGlZdK`b@zLdh@~CjM`CxHbDbI~JpQxUdc@lI~NjHfJ|GzGnEhIhFdLnD`MxLll@|Ltv@tDfVpFvYhEbWhFn]hBdO`AxJ^bF^vEj@`Ih@~Mf@hSJrOGpNaAhi@yBhnA@p{@Bxb@uAt|@{@|XUvRqADsBl@iGbI}C|Iqb@ju@cN`^sOlZuGpFcM_@gJVuYtHmPx@sTT}N`DcKrHiKh@eU{J{CcAiAT}@x@iAtCo}@lxCsOzd@aQ|h@q@|CiB`KkCxQyC~OiDxMmHbUkIhXyApGwAvJeAdJ}@|KiB|WuBtQeDbUoA|F}DxKoDxIgDpMyB~Li@jL}@vb@mBba@gA~NuB`RcEhX{B|O}Hnl@_Dt]_AhZNhe@Jje@y@bb@y@xf@c@`Oo@xOGlHGbJBhIErQXlJd@jKJnH_@bP]xHe@|Gc@xK_@pe@m@jUiCxp@}@xT?xM\\`Xw@lVf@d\\b@jMJtFR~KC|IQ|KWdVc@tJw@zQQdTJnI^zD|@bHzBxIjCzJ|@zEP`E@lK[tKSvJiAzRkArNuB`RcCnR}@vFiCrLuAlK}@fKoAlRsA|QgApK}AtH{BzGkInRqJjVyFxQsF~QeD`NqC`NwCvPwBtO{AfQ_AxLcBh^oAj[MdNCjFAjB^~Jh@|GrAhN`BrP~Aj[MlPcCbi@ObDA`CPjHn@dN~AvHbGrRp@dBv@fCbI|XhCdLhAvFlAhI~AvKbDx[|C~]fC|RhB`Nx@hGrB`RvAxTXfI^hPD|FI|FJhIf@bKxAlNnBtRhAxLbAfKzDxXt@|FxDtSlB~SbBnOlCjVzAjN^nDpAjL`BrTj@rIX~ELrU^~MHhLb@r[BvO\\|MTnQp@bMp@bIv@fGbArHdDjRrDxSdAfDsGvJmCrB{CdAmAt@qAp@m@`@a@p@m@|AuB|Im@rD{@bF{@|JWjE'}],
  'summary': {'has_time_restrictions': False,
   'has_toll': False,
   'has_highway': False,
   'has_ferry': False,
   'min_lat': 50.836754,
   'min_lon': -3.610295,
   'max_lat': 50.867483,
   'max_lon': -3.396712,
   'time': 1427.153,
   'length': 11.6191,
   'cost': 1493.55},
  'status_message': 'Found route between points',
  'status': 0,
  'units': 'miles',
  'language': 'en-US'}}
summary
{'total_time_s': 1427.153,
 'total_dist': 11.6191,
 'legs': [{'leg_index': 0,
   'time_s': 1427.153,
   'distance': 11.6191,
   'speed': 29.309233137582304}]}
valhalla_audit_route_speeds(result)

=== ROUTE SUMMARY ===
Total time: 23.8 min
Total dist: 11.62 km

=== SPEED DISTRIBUTION ===
< 20 km/h (urban/stop-start)        | time:    1.1 min (  4.7%) | dist:    0.3 km (  2.7%)
20–40 km/h (dense urban)            | time:   19.3 min ( 81.2%) | dist:    8.5 km ( 72.8%)
40–60 km/h (suburban)               | time:    3.4 min ( 14.1%) | dist:    2.8 km ( 24.5%)
60–90 km/h (A-road)                 | time:    0.0 min (  0.0%) | dist:    0.0 km (  0.0%)
90+ km/h (motorway-like)            | time:    0.0 min (  0.0%) | dist:    0.0 km (  0.0%)
{'total_time_s': 1427.146,
 'total_dist_km': 11.6182,
 'band_stats': {'20–40 km/h (dense urban)': {'time': 1158.6619999999998,
   'dist': 8.452399999999999},
  '< 20 km/h (urban/stop-start)': {'time': 67.281, 'dist': 0.3187},
  '40–60 km/h (suburban)': {'time': 201.203, 'dist': 2.8471},
  '60–90 km/h (A-road)': {'time': 0.0, 'dist': 0.0},
  '90+ km/h (motorway-like)': {'time': 0.0, 'dist': 0.0}}}
result_gdf = valhalla_maneuvers_to_gdf(result)

ax = result_gdf.plot(column="speed_kmh", legend=True, figsize=(8,4))

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=result_gdf.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

# Grab the auto-generated attribution object from matplotlib axes
txt = ax.texts[-1]
# Adjust its position to outside the map, just below the bottom edge
txt.set_position([0.5, -0.1])
txt.set_ha('center') # Center the text
txt.set_va('top')

Let’s repeat this for our other routes.

To Exeter Airport

result, summary = valhalla_detailed_route(engine=Actor("../../datasets/devon-260422_valhalla-combined-modified.json"),
                        origin=(origins.iloc[0].latitude, origins.iloc[0].longitude),
                        destination=(destinations.iloc[1].latitude, destinations.iloc[1].longitude),
                        )

result

=== OVERALL ===
Time: 17.5 min
Distance: 14.48 miles

--- LEG 0 ---
Time: 17.5 min
Distance: 14.48 miles
Speed: 49.6 miles/h

Maneuvers:
23.279s | 0.1721 | Drive north on Shortlands Road.
32.749s | 0.2025 | Turn right onto Tiverton Road.
17.196s |  0.118 | Turn left onto High Street/B3181.
23.658s | 0.1584 | Turn right onto Station Road/B3181.
1.606s | 0.0118 | Enter the roundabout and take the 1st exit onto Station Road/B3181.
9.096s | 0.0671 | Exit the roundabout onto Station Road/B3181.
9.91s | 0.0223 | Enter the roundabout and take the 2nd exit onto Station Road/B3181.
17.397s | 0.1273 | Exit the roundabout onto Station Road/B3181.
3.722s | 0.0223 | Enter the roundabout and take the 3rd exit onto Station Road/A373.
6.367s | 0.0466 | Exit the roundabout onto Station Road/A373. Continue on A373.
574.004s | 9.9431 | Turn right to take the M5 ramp.
16.461s | 0.2858 | Take exit 29 toward Honiton/Exeter Airport.
73.52s | 1.1346 | Bear left onto A30/Honiton Road. Continue on A30.
17.467s | 0.2317 | Take the exit.
2.167s | 0.0273 | Enter the roundabout and take the 1st exit onto Clyst Honiton Bypass/B3174.
134.147s | 1.2595 | Exit the roundabout onto Clyst Honiton Bypass/B3174. Continue on B3174.
3.628s | 0.0242 | Enter the roundabout and take the 2nd exit onto B3174.
17.728s | 0.1764 | Exit the roundabout onto B3174.
66.635s | 0.4524 | Turn right onto Treasbeare Lane.
 0.0s |    0.0 | Your destination is on the right.
{'trip': {'locations': [{'type': 'break',
    'lat': 50.855107,
    'lon': -3.396451,
    'side_of_street': 'right',
    'original_index': 0},
   {'type': 'break',
    'lat': 50.737141,
    'lon': -3.413323,
    'side_of_street': 'right',
    'original_index': 1}],
  'legs': [{'maneuvers': [{'type': 2,
      'instruction': 'Drive north on Shortlands Road.',
      'verbal_succinct_transition_instruction': 'Drive north. Then, in 900 feet, Turn right onto Tiverton Road.',
      'verbal_pre_transition_instruction': 'Drive north on Shortlands Road. Then, in 900 feet, Turn right onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for 900 feet.',
      'street_names': ['Shortlands Road'],
      'bearing_after': 4,
      'time': 23.279,
      'length': 0.1721,
      'cost': 34.167,
      'begin_shape_index': 0,
      'end_shape_index': 18,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Tiverton Road.',
      'verbal_transition_alert_instruction': 'Turn right onto Tiverton Road.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'street_names': ['Tiverton Road'],
      'bearing_before': 14,
      'bearing_after': 104,
      'time': 32.749,
      'length': 0.2025,
      'cost': 44.81,
      'begin_shape_index': 18,
      'end_shape_index': 29,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left onto High Street/B3181.',
      'verbal_transition_alert_instruction': 'Turn left onto High Street.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left onto High Street, B3181.',
      'verbal_post_transition_instruction': 'Continue for 600 feet.',
      'street_names': ['High Street', 'B3181'],
      'bearing_before': 83,
      'bearing_after': 4,
      'time': 17.196,
      'length': 0.118,
      'cost': 27.109,
      'begin_shape_index': 29,
      'end_shape_index': 44,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Turn right onto Station Road.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 800 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 14,
      'bearing_after': 80,
      'time': 23.658,
      'length': 0.1584,
      'cost': 31.815,
      'begin_shape_index': 44,
      'end_shape_index': 63,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 1st exit onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 1st exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 1st exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 1st exit onto Station Road, B3181.',
      'bearing_before': 51,
      'bearing_after': 39,
      'time': 1.606,
      'length': 0.0118,
      'cost': 7.375,
      'begin_shape_index': 63,
      'end_shape_index': 68,
      'roundabout_exit_count': 1,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/B3181.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 400 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 39,
      'bearing_after': 19,
      'time': 9.096,
      'length': 0.0671,
      'cost': 14.64,
      'begin_shape_index': 68,
      'end_shape_index': 80,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 2nd exit onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 2nd exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 2nd exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 2nd exit onto Station Road, B3181.',
      'bearing_before': 57,
      'bearing_after': 345,
      'time': 9.91,
      'length': 0.0223,
      'cost': 9.038,
      'begin_shape_index': 80,
      'end_shape_index': 91,
      'roundabout_exit_count': 2,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/B3181.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 700 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 143,
      'bearing_after': 100,
      'time': 17.397,
      'length': 0.1273,
      'cost': 27.233,
      'begin_shape_index': 91,
      'end_shape_index': 105,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 3rd exit onto Station Road/A373.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 3rd exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 3rd exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 3rd exit onto Station Road, A373.',
      'bearing_before': 55,
      'bearing_after': 47,
      'time': 3.722,
      'length': 0.0223,
      'cost': 4.038,
      'begin_shape_index': 105,
      'end_shape_index': 114,
      'roundabout_exit_count': 3,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/A373. Continue on A373.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout. Then, in 200 feet, Turn right to take the M5 ramp.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, A373. Then, in 200 feet, Turn right to take the M5 ramp.',
      'verbal_post_transition_instruction': 'Continue on A373 for 200 feet.',
      'street_names': ['A373'],
      'begin_street_names': ['Station Road', 'A373'],
      'bearing_before': 146,
      'bearing_after': 123,
      'time': 6.367,
      'length': 0.0466,
      'cost': 6.013,
      'begin_shape_index': 114,
      'end_shape_index': 119,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 18,
      'instruction': 'Turn right to take the M5 ramp.',
      'verbal_transition_alert_instruction': 'Turn right to take the M5 ramp.',
      'verbal_pre_transition_instruction': 'Turn right to take the M5 ramp.',
      'verbal_post_transition_instruction': 'Continue for 10 miles.',
      'street_names': ['M5'],
      'bearing_before': 92,
      'bearing_after': 167,
      'time': 574.004,
      'length': 9.9431,
      'cost': 560.112,
      'begin_shape_index': 119,
      'end_shape_index': 297,
      'highway': True,
      'sign': {'exit_branch_elements': [{'text': 'M5'}]},
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 21,
      'instruction': 'Take exit 29 toward Honiton/Exeter Airport.',
      'verbal_transition_alert_instruction': 'Take exit 29.',
      'verbal_pre_transition_instruction': 'Take exit 29 toward Honiton, Exeter Airport.',
      'street_names': ['M5'],
      'bearing_before': 186,
      'bearing_after': 173,
      'time': 16.461,
      'length': 0.2858,
      'cost': 17.625,
      'begin_shape_index': 297,
      'end_shape_index': 309,
      'sign': {'exit_number_elements': [{'text': '29'}],
       'exit_toward_elements': [{'text': 'Honiton', 'consecutive_count': 1},
        {'text': 'Exeter Airport', 'consecutive_count': 1},
        {'text': 'Exeter'}],
       'exit_name_elements': [{'text': 'Sowton Interchange'}]},
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 16,
      'instruction': 'Bear left onto A30/Honiton Road. Continue on A30.',
      'verbal_transition_alert_instruction': 'Bear left onto A30.',
      'verbal_succinct_transition_instruction': 'Bear left.',
      'verbal_pre_transition_instruction': 'Bear left onto A30, Honiton Road.',
      'verbal_post_transition_instruction': 'Continue on A30 for 1 mile.',
      'street_names': ['A30'],
      'begin_street_names': ['A30', 'Honiton Road'],
      'bearing_before': 131,
      'bearing_after': 85,
      'time': 73.52,
      'length': 1.1346,
      'cost': 116.382,
      'begin_shape_index': 309,
      'end_shape_index': 344,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 21,
      'instruction': 'Take the exit.',
      'verbal_transition_alert_instruction': 'Take the exit.',
      'verbal_pre_transition_instruction': 'Take the exit.',
      'bearing_before': 95,
      'bearing_after': 90,
      'time': 17.467,
      'length': 0.2317,
      'cost': 16.567,
      'begin_shape_index': 344,
      'end_shape_index': 356,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 1st exit onto Clyst Honiton Bypass/B3174.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 1st exit onto Clyst Honiton Bypass.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 1st exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 1st exit onto Clyst Honiton Bypass, B3174.',
      'bearing_before': 39,
      'bearing_after': 7,
      'time': 2.167,
      'length': 0.0273,
      'cost': 7.229,
      'begin_shape_index': 356,
      'end_shape_index': 362,
      'roundabout_exit_count': 1,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Clyst Honiton Bypass/B3174. Continue on B3174.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Clyst Honiton Bypass, B3174.',
      'verbal_post_transition_instruction': 'Continue on B3174 for 1.5 miles.',
      'street_names': ['B3174'],
      'begin_street_names': ['Clyst Honiton Bypass', 'B3174'],
      'bearing_before': 75,
      'bearing_after': 44,
      'time': 134.147,
      'length': 1.2595,
      'cost': 143.657,
      'begin_shape_index': 362,
      'end_shape_index': 439,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 2nd exit onto B3174.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 2nd exit onto B3174.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 2nd exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 2nd exit onto B3174.',
      'bearing_before': 17,
      'bearing_after': 19,
      'time': 3.628,
      'length': 0.0242,
      'cost': 7.799,
      'begin_shape_index': 439,
      'end_shape_index': 449,
      'roundabout_exit_count': 2,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto B3174.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto B3174.',
      'verbal_post_transition_instruction': 'Continue for 900 feet.',
      'street_names': ['B3174'],
      'bearing_before': 128,
      'bearing_after': 109,
      'time': 17.728,
      'length': 0.1764,
      'cost': 24.746,
      'begin_shape_index': 449,
      'end_shape_index': 468,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Treasbeare Lane.',
      'verbal_transition_alert_instruction': 'Turn right onto Treasbeare Lane.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Treasbeare Lane.',
      'verbal_post_transition_instruction': 'Continue for a half mile.',
      'street_names': ['Treasbeare Lane'],
      'bearing_before': 52,
      'bearing_after': 143,
      'time': 66.635,
      'length': 0.4524,
      'cost': 75.167,
      'begin_shape_index': 468,
      'end_shape_index': 488,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 5,
      'instruction': 'Your destination is on the right.',
      'verbal_transition_alert_instruction': 'Your destination will be on the right.',
      'verbal_pre_transition_instruction': 'Your destination is on the right.',
      'bearing_before': 145,
      'time': 0.0,
      'length': 0.0,
      'cost': 0.0,
      'begin_shape_index': 488,
      'end_shape_index': 488,
      'travel_mode': 'drive',
      'travel_type': 'car'}],
    'summary': {'has_time_restrictions': False,
     'has_toll': False,
     'has_highway': True,
     'has_ferry': False,
     'min_lat': 50.729028,
     'min_lon': -3.462135,
     'max_lat': 50.861101,
     'max_lon': -3.38319,
     'time': 1050.746,
     'length': 14.4843,
     'cost': 1175.531},
    'shape': '}k}~_BhfinEWAqFWqG\\iBZyFpAeT|B{Cx@qB`CoExJsCjJcCfImEpJsBnByBz@wBTyEa@uNyDyWwHhBkT`AuUh@mQCk[EmDQmPy@ey@yAc~@wB{]k@}NImEyAImDO_KsAmDu@iDcAs@UuG_CmB_AqLoGyGqDsJ}HmDiBwDyBaCoAeF{ANmCc@kFaEaYeBiLgBiMiBgOuB}WoAuNeCeUIw@y@gHo@{BcB}E{BsEeCqDyEaFcEoDuAyBa@sBq@VeAOw@aAa@aB?mByASsDiB{DoCmBqBkAqBgBqDmAiDaCkGeCeH{C_Kw@cC{@qCs@d@y@Fw@Wm@w@_@kAKyAHaB^sAp@{@z@W|@L\\uB^eGBgGEoK_AgReAmWeAyUy@oRq@kHS}Hg@sPoA}GaB{EyAgDi@y@a@eA_@sBMuDPeA`@qBn@sAx@_AbAe@`A_EbDcJt@g]J_ED{HjBqApBu@pDIzWfD~d@zGfH`Ax^xFf_B~RzPrHdhCjPxo@rEdt@lIjm@~Lrf@dLpe@~Ox[tKr[xN|X`Nte@bWxIhFb}@rn@fk@jd@z]tVre@dW`WbLjUpItY`IhWtE~ThDrg@~DrsAnBnm@p@~i@jFte@lIxm@hQpf@lT|c@|Xrf@`_@v`@v_@|c@~i@``@rf@tgAxyA||@~jAja@de@`f@fe@xc@n^fe@n[rpAjr@`kAfn@bf@|Y`e@n\\te@pa@la@l`@tmA|rA|dAtiAzcA`iAndAviAbhAdmAnPlRbn@bs@xFtHx`@th@h`@~k@bRb]lc@hx@d_@xt@v]l{@d_@ldAvUdt@te@tyA~q@nkBno@z}A`v@z`B~y@|bBjbAhdBtTx^p]hj@rs@xlAbcAz`B|y@ztAh}@|xA`z@puAz~@l{A`y@ltAlx@frAbfA|dB|^jg@jZj`@v`@rd@t^na@ze@rb@|b@v]jc@hZxPhK|GfEzHxEfd@fU|g@hTta@nNrk@`Qtb@fJdf@lGbNpAfZ|Czp@lI`VxBpn@zI|ZzFhZbH`\\dJxg@lQzOrGv[hNh_@vRpk@f]~^xW`f@f`@p\\tYj_Axz@ty@nv@byAhsAbQ~OjIvHduAzoAzpBfiBfzAvsAvo@dg@zq@dc@np@f]zo@bXl`@tMr_@zJh[vHn`@~Gvg@fG`d@rDnv@lBhf@Qdj@kBt^oCp_@gEj^wF`a@{Hhw@{QvaBsa@`IoBtbFwnAnuAaYbvAsTjjAwLbjAeH|gAkCpdAa@t{@hBpPt@t]dBj`@pCj]|CtO|A|PbBv_A~Ljb@|HzJlBjnArWzhAv\\tw@rYn}@j]taD|xAxi@|T~MdGxPtFdNxD`fBde@zg@fLr}@rNz|@zKf_@tCn]tC|SiChZ?hz@h@xd@BrY{Avs@kG|HoDtBmA|AcBx@_A~@oBjE}Jq@w[oDgv@i@mLQyEUuFw@cR_@sK{Aec@qCwv@aCac@yFg|@qH_|@oFoh@sRagB}NorAuHw|@eDek@eBwb@cBkm@s@aj@Skp@^{h@dAcl@dBci@rCin@tAyYlEcp@lJi`BrL_cB|E{r@lB}ZbB_^zBuh@jCet@x@o_@GyhBc@en@aBsm@yBwVcD}YuCsPoA}GqCcLgB{F_BwEgAsB{DoFyC[uBiAiBsBsAyCy@sDY}CwF{JwAgBqGsE}EcC_EFuDNaE@{Cv@wCvAgi@vY_VbLwMvEqNtDqRnCkZbGgXjH_HhBgPpC_BNqLjAaITkJM{Hk@wScAwDyBgG_AiB_@mFgBqHkCsNgG}GkDiRoLyEg@qFmF}AwAkBcDkAsBmCIkBgBkFuFyE{GgD_KuG}RgEkRoMy_@uMyb@oI}VwKmZmI{TyF_RiBeFgJmWiGoPyFsPuM_`@qQce@kDmKeS_j@yDwJqGmP{IuSkSya@aRi[}R{\\eNgV{CsGoGcNsb@yeAwLa`@mTqx@{Hq`@iGa^wAmG_BeBqAcA}CeBoEgAeAWaAo@s@aAk@qA]aBOgBAkBJgBZaBp@cBr@sEb@oCZiDh@_FC}GKgGc@uGa@_De@{CgEiWsAqHsAqFoEmRgAgEeC{IuBsHcFcQcLq[{Lm[n@u@~@uAhAwAZWjAgAbGeDxQkIzQ_Ktw@_a@pJ{D|NqDd~@gNbS}DjJyDnTeJxF{DbEgFvn@{aAdp@kv@??'}],
  'summary': {'has_time_restrictions': False,
   'has_toll': False,
   'has_highway': True,
   'has_ferry': False,
   'min_lat': 50.729028,
   'min_lon': -3.462135,
   'max_lat': 50.861101,
   'max_lon': -3.38319,
   'time': 1050.746,
   'length': 14.4843,
   'cost': 1175.531},
  'status_message': 'Found route between points',
  'status': 0,
  'units': 'miles',
  'language': 'en-US'}}
summary
{'total_time_s': 1050.746,
 'total_dist': 14.4843,
 'legs': [{'leg_index': 0,
   'time_s': 1050.746,
   'distance': 14.4843,
   'speed': 49.625199620079435}]}
valhalla_audit_route_speeds(result)

=== ROUTE SUMMARY ===
Total time: 17.5 min
Total dist: 14.48 km

=== SPEED DISTRIBUTION ===
< 20 km/h (urban/stop-start)        | time:    0.2 min (  0.9%) | dist:    0.0 km (  0.2%)
20–40 km/h (dense urban)            | time:    6.0 min ( 34.0%) | dist:    2.8 km ( 19.6%)
40–60 km/h (suburban)               | time:    1.6 min (  8.9%) | dist:    1.4 km (  9.6%)
60–90 km/h (A-road)                 | time:    9.8 min ( 56.2%) | dist:   10.2 km ( 70.6%)
90+ km/h (motorway-like)            | time:    0.0 min (  0.0%) | dist:    0.0 km (  0.0%)
{'total_time_s': 1050.737,
 'total_dist_km': 14.483400000000001,
 'band_stats': {'20–40 km/h (dense urban)': {'time': 357.20799999999997,
   'dist': 2.8386},
  '< 20 km/h (urban/stop-start)': {'time': 9.91, 'dist': 0.0223},
  '60–90 km/h (A-road)': {'time': 590.465, 'dist': 10.2289},
  '40–60 km/h (suburban)': {'time': 93.154, 'dist': 1.3936000000000002},
  '90+ km/h (motorway-like)': {'time': 0.0, 'dist': 0.0}}}
result_gdf = valhalla_maneuvers_to_gdf(result)

ax = result_gdf.plot(column="speed_kmh", legend=True, figsize=(10,6))

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=result_gdf.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

# Grab the auto-generated attribution object from matplotlib axes
txt = ax.texts[-1]
# Adjust its position to outside the map, just below the bottom edge
txt.set_position([0.5, -0.1])
txt.set_ha('center') # Center the text
txt.set_va('top')

Cullompton to Tavistock (Cornish Arms)

result, summary = valhalla_detailed_route(
    engine=Actor("../../datasets/devon-260422_valhalla-combined-modified.json"),
    origin=(origins.iloc[0].latitude, origins.iloc[0].longitude),
    destination=(destinations.iloc[2].latitude, destinations.iloc[2].longitude),
    )

result

=== OVERALL ===
Time: 60.4 min
Distance: 55.43 miles

--- LEG 0 ---
Time: 60.4 min
Distance: 55.43 miles
Speed: 55.1 miles/h

Maneuvers:
23.279s | 0.1721 | Drive north on Shortlands Road.
32.749s | 0.2025 | Turn right onto Tiverton Road.
17.196s |  0.118 | Turn left onto High Street/B3181.
23.658s | 0.1584 | Turn right onto Station Road/B3181.
1.606s | 0.0118 | Enter the roundabout and take the 1st exit onto Station Road/B3181.
9.096s | 0.0671 | Exit the roundabout onto Station Road/B3181.
9.91s | 0.0223 | Enter the roundabout and take the 2nd exit onto Station Road/B3181.
17.397s | 0.1273 | Exit the roundabout onto Station Road/B3181.
3.722s | 0.0223 | Enter the roundabout and take the 3rd exit onto Station Road/A373.
6.367s | 0.0466 | Exit the roundabout onto Station Road/A373. Continue on A373.
843.429s | 14.6388 | Turn right to take the M5 ramp.
54.772s | 0.7083 | Keep left to take exit 31 onto A30.
1525.255s | 26.5816 | Continue on A30.
13.947s | 0.2429 | Take the Sourton Cross exit.
973.709s | 11.8563 | Turn left onto A386.
26.07s | 0.1926 | Bear left onto A386/Dolvin Road. Continue on Dolvin Road.
7.953s | 0.0211 | Enter Whitchurch Road and take the 2nd exit onto A386/Abbey Place.
33.781s | 0.2352 | Exit the roundabout onto A386/Abbey Place.
 0.0s |    0.0 | You have arrived at your destination.
{'trip': {'locations': [{'type': 'break',
    'lat': 50.855107,
    'lon': -3.396451,
    'side_of_street': 'right',
    'original_index': 0},
   {'type': 'break', 'lat': 50.551448, 'lon': -4.14675, 'original_index': 1}],
  'legs': [{'maneuvers': [{'type': 2,
      'instruction': 'Drive north on Shortlands Road.',
      'verbal_succinct_transition_instruction': 'Drive north. Then, in 900 feet, Turn right onto Tiverton Road.',
      'verbal_pre_transition_instruction': 'Drive north on Shortlands Road. Then, in 900 feet, Turn right onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for 900 feet.',
      'street_names': ['Shortlands Road'],
      'bearing_after': 4,
      'time': 23.279,
      'length': 0.1721,
      'cost': 34.167,
      'begin_shape_index': 0,
      'end_shape_index': 18,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Tiverton Road.',
      'verbal_transition_alert_instruction': 'Turn right onto Tiverton Road.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Tiverton Road.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'street_names': ['Tiverton Road'],
      'bearing_before': 14,
      'bearing_after': 104,
      'time': 32.749,
      'length': 0.2025,
      'cost': 44.81,
      'begin_shape_index': 18,
      'end_shape_index': 29,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left onto High Street/B3181.',
      'verbal_transition_alert_instruction': 'Turn left onto High Street.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left onto High Street, B3181.',
      'verbal_post_transition_instruction': 'Continue for 600 feet.',
      'street_names': ['High Street', 'B3181'],
      'bearing_before': 83,
      'bearing_after': 4,
      'time': 17.196,
      'length': 0.118,
      'cost': 27.109,
      'begin_shape_index': 29,
      'end_shape_index': 44,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 10,
      'instruction': 'Turn right onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Turn right onto Station Road.',
      'verbal_succinct_transition_instruction': 'Turn right.',
      'verbal_pre_transition_instruction': 'Turn right onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 800 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 14,
      'bearing_after': 80,
      'time': 23.658,
      'length': 0.1584,
      'cost': 31.815,
      'begin_shape_index': 44,
      'end_shape_index': 63,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 1st exit onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 1st exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 1st exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 1st exit onto Station Road, B3181.',
      'bearing_before': 51,
      'bearing_after': 39,
      'time': 1.606,
      'length': 0.0118,
      'cost': 7.375,
      'begin_shape_index': 63,
      'end_shape_index': 68,
      'roundabout_exit_count': 1,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/B3181.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 400 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 39,
      'bearing_after': 19,
      'time': 9.096,
      'length': 0.0671,
      'cost': 14.64,
      'begin_shape_index': 68,
      'end_shape_index': 80,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 2nd exit onto Station Road/B3181.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 2nd exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 2nd exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 2nd exit onto Station Road, B3181.',
      'bearing_before': 57,
      'bearing_after': 345,
      'time': 9.91,
      'length': 0.0223,
      'cost': 9.038,
      'begin_shape_index': 80,
      'end_shape_index': 91,
      'roundabout_exit_count': 2,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/B3181.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, B3181.',
      'verbal_post_transition_instruction': 'Continue for 700 feet.',
      'street_names': ['Station Road', 'B3181'],
      'bearing_before': 143,
      'bearing_after': 100,
      'time': 17.397,
      'length': 0.1273,
      'cost': 27.233,
      'begin_shape_index': 91,
      'end_shape_index': 105,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter the roundabout and take the 3rd exit onto Station Road/A373.',
      'verbal_transition_alert_instruction': 'Enter the roundabout and take the 3rd exit onto Station Road.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 3rd exit.',
      'verbal_pre_transition_instruction': 'Enter the roundabout and take the 3rd exit onto Station Road, A373.',
      'bearing_before': 55,
      'bearing_after': 47,
      'time': 3.722,
      'length': 0.0223,
      'cost': 4.038,
      'begin_shape_index': 105,
      'end_shape_index': 114,
      'roundabout_exit_count': 3,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto Station Road/A373. Continue on A373.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout. Then, in 200 feet, Turn right to take the M5 ramp.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto Station Road, A373. Then, in 200 feet, Turn right to take the M5 ramp.',
      'verbal_post_transition_instruction': 'Continue on A373 for 200 feet.',
      'street_names': ['A373'],
      'begin_street_names': ['Station Road', 'A373'],
      'bearing_before': 146,
      'bearing_after': 123,
      'time': 6.367,
      'length': 0.0466,
      'cost': 6.013,
      'begin_shape_index': 114,
      'end_shape_index': 119,
      'verbal_multi_cue': True,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 18,
      'instruction': 'Turn right to take the M5 ramp.',
      'verbal_transition_alert_instruction': 'Turn right to take the M5 ramp.',
      'verbal_pre_transition_instruction': 'Turn right to take the M5 ramp.',
      'verbal_post_transition_instruction': 'Continue for 15 miles.',
      'street_names': ['M5'],
      'bearing_before': 92,
      'bearing_after': 167,
      'time': 843.429,
      'length': 14.6388,
      'cost': 841.844,
      'begin_shape_index': 119,
      'end_shape_index': 396,
      'highway': True,
      'sign': {'exit_branch_elements': [{'text': 'M5'}]},
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 24,
      'instruction': 'Keep left to take exit 31 onto A30.',
      'verbal_transition_alert_instruction': 'Keep left to take exit 31.',
      'verbal_pre_transition_instruction': 'Keep left to take exit 31 onto A30.',
      'street_names': ['M5'],
      'bearing_before': 227,
      'bearing_after': 220,
      'time': 54.772,
      'length': 0.7083,
      'cost': 53.393,
      'begin_shape_index': 396,
      'end_shape_index': 435,
      'sign': {'exit_number_elements': [{'text': '31'}],
       'exit_branch_elements': [{'text': 'A30', 'consecutive_count': 1}],
       'exit_name_elements': [{'text': "Pearce's Hill"}]},
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 8,
      'instruction': 'Continue on A30.',
      'verbal_transition_alert_instruction': 'Continue on A30.',
      'verbal_pre_transition_instruction': 'Continue on A30.',
      'verbal_post_transition_instruction': 'Continue for 27 miles.',
      'street_names': ['A30'],
      'bearing_before': 324,
      'bearing_after': 324,
      'time': 1525.255,
      'length': 26.5816,
      'cost': 1418.813,
      'begin_shape_index': 435,
      'end_shape_index': 1004,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 21,
      'instruction': 'Take the Sourton Cross exit.',
      'verbal_transition_alert_instruction': 'Take the Sourton Cross exit.',
      'verbal_pre_transition_instruction': 'Take the Sourton Cross exit.',
      'bearing_before': 234,
      'bearing_after': 226,
      'time': 13.947,
      'length': 0.2429,
      'cost': 12.194,
      'begin_shape_index': 1004,
      'end_shape_index': 1022,
      'sign': {'exit_name_elements': [{'text': 'Sourton Cross'}]},
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 15,
      'instruction': 'Turn left onto A386.',
      'verbal_transition_alert_instruction': 'Turn left onto A386.',
      'verbal_succinct_transition_instruction': 'Turn left.',
      'verbal_pre_transition_instruction': 'Turn left onto A386.',
      'verbal_post_transition_instruction': 'Continue for 12 miles.',
      'street_names': ['A386'],
      'bearing_before': 266,
      'bearing_after': 190,
      'time': 973.709,
      'length': 11.8563,
      'cost': 880.727,
      'begin_shape_index': 1022,
      'end_shape_index': 1510,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 16,
      'instruction': 'Bear left onto A386/Dolvin Road. Continue on Dolvin Road.',
      'verbal_transition_alert_instruction': 'Bear left onto A386.',
      'verbal_succinct_transition_instruction': 'Bear left.',
      'verbal_pre_transition_instruction': 'Bear left onto A386, Dolvin Road.',
      'verbal_post_transition_instruction': 'Continue on Dolvin Road for a quarter mile.',
      'street_names': ['Dolvin Road'],
      'begin_street_names': ['A386', 'Dolvin Road'],
      'bearing_before': 265,
      'bearing_after': 229,
      'time': 26.07,
      'length': 0.1926,
      'cost': 27.779,
      'begin_shape_index': 1510,
      'end_shape_index': 1522,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 26,
      'instruction': 'Enter Whitchurch Road and take the 2nd exit onto A386/Abbey Place.',
      'verbal_transition_alert_instruction': 'Enter Whitchurch Road and take the 2nd exit onto A386.',
      'verbal_succinct_transition_instruction': 'Enter the roundabout and take the 2nd exit.',
      'verbal_pre_transition_instruction': 'Enter Whitchurch Road and take the 2nd exit onto A386, Abbey Place.',
      'street_names': ['Whitchurch Road'],
      'bearing_before': 175,
      'bearing_after': 194,
      'time': 7.953,
      'length': 0.0211,
      'cost': 8.6,
      'begin_shape_index': 1522,
      'end_shape_index': 1534,
      'roundabout_exit_count': 2,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 27,
      'instruction': 'Exit the roundabout onto A386/Abbey Place.',
      'verbal_succinct_transition_instruction': 'Exit the roundabout.',
      'verbal_pre_transition_instruction': 'Exit the roundabout onto A386, Abbey Place.',
      'verbal_post_transition_instruction': 'Continue for a quarter mile.',
      'street_names': ['A386'],
      'begin_street_names': ['A386', 'Abbey Place'],
      'bearing_before': 325,
      'bearing_after': 311,
      'time': 33.781,
      'length': 0.2352,
      'cost': 55.038,
      'begin_shape_index': 1534,
      'end_shape_index': 1563,
      'travel_mode': 'drive',
      'travel_type': 'car'},
     {'type': 4,
      'instruction': 'You have arrived at your destination.',
      'verbal_transition_alert_instruction': 'You will arrive at your destination.',
      'verbal_pre_transition_instruction': 'You have arrived at your destination.',
      'bearing_before': 335,
      'time': 0.0,
      'length': 0.0,
      'cost': 0.0,
      'begin_shape_index': 1563,
      'end_shape_index': 1563,
      'travel_mode': 'drive',
      'travel_type': 'car'}],
    'summary': {'has_time_restrictions': False,
     'has_toll': False,
     'has_highway': True,
     'has_ferry': False,
     'min_lat': 50.548826,
     'min_lon': -4.146689,
     'max_lat': 50.861101,
     'max_lon': -3.38319,
     'time': 3623.905,
     'length': 55.4261,
     'cost': 3504.635},
    'shape': '}k}~_BhfinEWAqFWqG\\iBZyFpAeT|B{Cx@qB`CoExJsCjJcCfImEpJsBnByBz@wBTyEa@uNyDyWwHhBkT`AuUh@mQCk[EmDQmPy@ey@yAc~@wB{]k@}NImEyAImDO_KsAmDu@iDcAs@UuG_CmB_AqLoGyGqDsJ}HmDiBwDyBaCoAeF{ANmCc@kFaEaYeBiLgBiMiBgOuB}WoAuNeCeUIw@y@gHo@{BcB}E{BsEeCqDyEaFcEoDuAyBa@sBq@VeAOw@aAa@aB?mByASsDiB{DoCmBqBkAqBgBqDmAiDaCkGeCeH{C_Kw@cC{@qCs@d@y@Fw@Wm@w@_@kAKyAHaB^sAp@{@z@W|@L\\uB^eGBgGEoK_AgReAmWeAyUy@oRq@kHS}Hg@sPoA}GaB{EyAgDi@y@a@eA_@sBMuDPeA`@qBn@sAx@_AbAe@`A_EbDcJt@g]J_ED{HjBqApBu@pDIzWfD~d@zGfH`Ax^xFf_B~RzPrHdhCjPxo@rEdt@lIjm@~Lrf@dLpe@~Ox[tKr[xN|X`Nte@bWxIhFb}@rn@fk@jd@z]tVre@dW`WbLjUpItY`IhWtE~ThDrg@~DrsAnBnm@p@~i@jFte@lIxm@hQpf@lT|c@|Xrf@`_@v`@v_@|c@~i@``@rf@tgAxyA||@~jAja@de@`f@fe@xc@n^fe@n[rpAjr@`kAfn@bf@|Y`e@n\\te@pa@la@l`@tmA|rA|dAtiAzcA`iAndAviAbhAdmAnPlRbn@bs@xFtHx`@th@h`@~k@bRb]lc@hx@d_@xt@v]l{@d_@ldAvUdt@te@tyA~q@nkBno@z}A`v@z`B~y@|bBjbAhdBtTx^p]hj@rs@xlAbcAz`B|y@ztAh}@|xA`z@puAz~@l{A`y@ltAlx@frAbfA|dB|^jg@jZj`@v`@rd@t^na@ze@rb@|b@v]jc@hZxPhK|GfEzHxEfd@fU|g@hTta@nNrk@`Qtb@fJdf@lGbNpAfZ|Czp@lI`VxBpn@zI|ZzFhZbH`\\dJxg@lQzOrGv[hNh_@vRpk@f]~^xW`f@f`@p\\tYj_Axz@ty@nv@byAhsAbQ~OjIvHduAzoAzpBfiBfzAvsAvo@dg@zq@dc@np@f]zo@bXl`@tMr_@zJh[vHn`@~Gvg@fG`d@rDnv@lBhf@Qdj@kBt^oCp_@gEj^wF`a@{Hhw@{QvaBsa@`IoBtbFwnAnuAaYbvAsTjjAwLbjAeH|gAkCpdAa@t{@hBpPt@t]dBj`@pCj]|CtO|A|PbBv_A~Ljb@|HzJlBjnArWzhAv\\tw@rYn}@j]taD|xAxi@|T~MdGxPtFdNxD`fBde@zg@fLr}@rNz|@zKf_@tCn]tCxaAfFt`AnBdjBA~f@Of}@M`hBmA`p@sA`hGqCpb@DnxBkBd}@r@bZtAjm@~Dph@zGtt@tLnn@|Ndd@|OhR|GjU|K~a@fSrYzOdzAx{@noAtt@duAnx@bc@`W`h@~Xh[vNz`Avc@pN~FvhAn`@phBxi@x`C|~@bu@`^bz@tc@fdAvo@pcAzs@zkAjaA`kA~gAzmAbsAbXb\\fe@zk@tRtYt`@po@n\\rj@l[hm@fc@l}@r^|z@zOha@pOva@b_@rkArPzj@lJb]pTr}@tUxlA|Ifn@|Gtn@fAfOvA~RlAfSnAzWjA~Vf@pOd@vN\\dOZ~NLpNJpNHlNFxPGhRIdO]vSg@nRo@fOiDf}@cAbUqAjRcAnOuCfg@uCzh@eBn^eBje@yAhy@Gtd@Th\\p@dm@bCds@fCrd@~E|o@hHdo@vIpl@xLvr@pNxn@fNph@vRrm@bNp^bSdd@jN`YpOf[ve@pr@`W~^jO~S|NhRjIbJ`JjHjHxEjGlC|FnBhHbBbHhAxI\\fRQdRe@lIl@hHfAxGbCtGdFjEhGrDpHdCtIvB|JfAhKb@lKQ|Ku@zIuAvJmBhIeCvGeDtG_FfHkG~Gwz@|n@wt@th@eM|JgM|JwYlWeZ~Z}ObReHtI{IvKkC`Do]rf@cWpb@yIzPgMtU}Uvg@wQpc@yPre@aMz_@kIvXiLfa@eVfbA_c@dgBsXnlAuZxlA}YjnAcTx{@wKjb@wJv^gJpZeJjXgLtYeNn\\yHlPaHpNuGxMmLzSoTz]aUh[wIlKeHhIkTfUiRtQmTzQmN|J}QdLy_@dU_rB|cAemBlaAwt@n_@gb@lSwr@j`@iZlR}RfNuf@bb@cJzIgQpQmWl[aLlOmFjIqIfMkFdIoF|IwF`JmFjJiFvJkFfK_FzJcFlKeOp\\aHnP}A`EyClIg_@jiAsI~[{In]yIv`@eL|m@uJjm@yKp|@{I`_A_LhuA}K~rAgXv}CcLjdAsLhs@gGbZsEfTuDpO{C~LiC|ImQfj@aQfe@kWni@eNfWmXjb@eWv]{b@~c@oUnTyl@z_@aPrHiWvKuQfIaU`Km_@bSyUfOkVxPac@ba@mLtMaM~Nef@|s@}Yph@q]xm@}h@jy@o^tb@{~@lfA_e@dr@sOzYiUdk@kFrO}GhU}GvYgHx[aG|\\aE|[wDh^oCxd@{Aja@o@xf@Znf@RlYvBdg@jD|c@lHnw@xMhiAzSx{B`Fr~@|Ch~@`Ab|@eBhrAuA|z@_Ezz@gO|yAcUt_BmTjhAeXlfAeaAdfDeW|fAsUplAiErT{Lf{@{Hhi@mKz`AuGnt@gDpd@wFd~@sDr~@sCteAgA|hAPtcA|@ly@|Ax{@xCfz@lFjiBlEjzAzApu@Xbt@KbdAwAtmAuDliA}Ej`AeFvw@uI~`AwKjaAqJ`s@oJto@oQfjAuUd{AkNl|@cNjbA{LzbAuGjo@qIflA}FziAoDxkAaBroAQxj@Ldg@|AdkAfDhhAlNbsCrJzoB~Ad`@bBlf@HhDlBddA^ds@A|i@Axe@q@pn@{B~jA_FlkAiBz]eEvt@wIngA_Wb{BuRvoAwNxw@mOlr@}S`}@kf@tpBcK~b@cUncAcS|eAqStsAaNlcAgHps@kLzuAcH|lAcCvi@sBlu@wAnm@k@~u@]|v@\\~b@d@fh@fB||@pDleAtHvyApTjcDtVtaDxOfkBv\\`pDv_@~xDnb@tqDnPryApHzs@bJpaAnFht@xFx|@`Dvq@xCzv@jBzn@dCfjAz@|aAFjaBOvm@mAzgBqBxaCoA`eAyA~gAyBjnASzMKnIqFztBmGhhAcI~|@_Gdh@kD`YoMnw@uP`z@uNpm@}S`u@eXd{@}Utw@cYhdAwGn[}U~fAwNd`A{VdlBeLvgAwE`q@iMvaC_IfoCaBttCHzrAjA`tAvC|rAjHbuAxEfy@rGty@`S|`BjWf~Avm@puC||@duD~e@dwBle@nyB~fAdxFdFrVxg@ppCx\\fnBv^v{Br}@lwFdT`lAlWzmA`Obn@vO`o@`d@n}AxaAfcDr`@tsAj^`qArQrv@`Sb{@zOtv@rSzkAxQ~kAhNrbApOrwA`WbrCpGvbA|Eb}@pCxm@|DnxAvAtp@t@bk@d@jpB[rcAe@lfAgBldA}CrlAaIxyCyJtxCmKzlCsMzsCgeAtoOwD|p@wCfu@aAtUmCdbBoE`eEeCfdAwBl{@}Dh}@kFty@wEfp@gFdl@aBxO}Or~@eK`a@yIpZ{J|XwKjXgQr^yY~h@{{BjeDwu@hgAmt@`eAia@|l@q]xe@ke@|v@kPbXiFdKqTpd@uArCmRdf@{Uvw@eStv@cRnaA{Lju@{Jzv@yUlwBkRffBcS|wBwKnaBcGjlA}Bv{@oC~_B}Aj}@gCpn@_Dtf@sBjYmG~|@kMnlA_L`bAuqAdfLsRt_B}Rt`BiFt]yJxd@eUlz@uVdr@_Tnd@}Tdb@o_A`_BiYnk@c]~z@sOpd@mLv`@yNdk@eZd`BqMjeAmIzgAsE|eAkB~jAeGhbDgMp{HiE|jAgH|cAsOhtAmHfh@o\\hcCaKnaAwJvfAgOzcCgF`~AeEbiCDpaC^ve@p@xTpBhv@tI`yBpFfaAhIvdAjXdgCr`@~hDrKr~@`m@zeFpLtxAnHhvA~BtmBL|p@kAbs@eGbwAgItdAaJt{@uSr}AwHdi@aI~p@}H~v@oFbq@eGraA}B`r@eCjeAiBfsAHnnAp@zm@tApr@`Cfn@d@dUjEbx@`JhoAlKlfAbQbrAtRtlAnLdj@vKpd@~Mje@rSbn@d^f_Alk@|cAdObUbGjIxFfIlG~I`J`Njb@hk@tRlXxWhd@xJpQ`KbRhJjS`KxV|KpYzIdXnK|`@nLbg@|Hrd@nGla@hJlw@`Gvk@tFhi@fMztAjIpu@dLpfArJlz@xJr{@pH|p@xKr_AxKnt@jHnj@pKfs@jLxu@hMpq@rLhn@fKnd@lOxj@|Pxi@vSxi@zIvTbJlSjRh_@bYrh@vWde@fRd^xRtb@zM|ZxKlY~FjRhKz_@jGfXxD~QnC|NdDhSpEz\\~ChWfEra@bCjY`A|LjBzU`Dva@vDtc@dEle@|Ffn@pGfp@nFlh@hFtd@fDjYrEb_@nFz_@nE~[nHrg@tGrc@fHtd@~Gxa@|BzN`ExWhHta@~G~_@nNvs@~Gl]~Ipb@tL`h@|Jfd@rMxh@~Lng@fS`x@tXdlA~G`[j]~}AhR~|@pL~l@~Hd`@rN`t@xBfMbDvRbCvMtCtRxGrc@vIfj@lFd_@dSrwAtGhg@vDtZhJlz@|Kp{@xGtc@pLnq@nCtPdJne@|Hl_@nJ|`@zNti@lLpa@fSdk@bRvf@vV~k@nErJ`Sj`@nPnZ~Vbb@|Yrd@l{FnwIxjA|gBtSj^xO|ZbOd]|Lz\\zL`VzNhd@`Npd@`F`OzE|LfFhLjD~GrE`HzIvL|HnK~HhMdEpJjDpKjCvMnA|Gz@vJEhFIfChP~CpVbEdThDrVhEvPdEtOdErIfDxKbFhItEbQpLlQxNtVjWln@vr@ld@nh@fk@tn@xhA~nAzn@ds@lS~TzPn\\~d@xhAzQjd@dBfEzQzc@jKxUzInVrNfb@bNlb@zClJ~FdMfQ|Xd@`AbGdJbFtJnCzEhGjKhO~UvFtGxG|GfJlHpL`If@LhHhCrFvAzGdApLhAt^~Dvc@~DjHn@zT~@nOjAhOpC`JzCpBp@|LbHl|@zm@`RrL|MbGb[fN`h@lZ`MlHx@f@fF`DnGtEnMdM~LxMt]h^vM|Q|OzZtBxDh^pg@bc@fg@r]b`@~LvMhExCdKvFpXjGbZ|Ejs@nHbGn@pPh@pQIrCAxFIr@BdP|@~g@pDp]jA|It@nA`@|DnA`NtHlP`LtKzGbStJtGvD|FnEdUrQnNxHrObH|XtKjLbCnIh@hQYzAEzM[|K`AbPzClEfBdFtBfTtO|JhE~JnArBIl^kB|BEhDEtFG|HJtLz@|KbAhJP|J{@fP}DtWuGth@gPpQaI`P}IlOqHn@]zo@s[pV_MxHoBhIeA`UMvaAc@v_@D``@kAfK[fKHhG^pGnA|GzBbGzCzDvBjFrDtMzMjMfLnJzHpIvGhLrHfVzJfc@jPzN`Gvk@tTdNpFjChAxB`AlT`JvVfJpRhHjPbEtHxAvo@jN|g@rKn|@pRbDr@hAV`a@lJxOjDlJfBhFd@fKj@zYY`SYpNC`ABfz@|ChRr@leBlF`N`@pENnFRpEPzCLll@`CrI\\zBHpp@zBrVfBjLdCfNxFxVtNdCpBrD`Cli@~\\pGxEb[`RvQzMl`B~dAra@bX`aAtm@fGtDhEbBnCv@vF\\hHe@~L{CjNiFhi@wVhGwBtD_AhEQbFFvCFjAJbAH^Dr@HnA`@~CrAxCvCbDjGtD~KdDhNj@rCnG|[bNn|@|AtFjC|FrFjIvBtBlClBnDbBzChA~A`@`ATfDv@tFZrGDlZQlGMxEWnLeB|OwDrYcJzIc@lF?lD?dZpCfgBxRfg@tFzdAlLz|@|IjIlA|BZdOlBnf@tFbuAvNr`@jEdBR`AJjJbAbXnC`XlCnf@nFt^xDdWjFbFvBxDdBzBpAh@Z|LpIzVxXfRnWrQtVvVpYlT|OnbAro@heCf_B`q@zb@xeA~q@bb@xXrw@|f@ny@hh@xu@|e@vp@xa@tw@ng@~QhLvLjHhIlF`EjCbCzAtO`K`WpOvi@zYpjC`wAtzApx@ffCxtA~SlLld@tVnBfAps@f_@tq@|_@l~@dh@xe@tW~dAbd@zmAxi@pXrLxCp@lB`@rBd@hGRlIDhA?lCBtQEL?pe@aAho@sA~A@vCE|Zu@bMYnn@eAhJ]rQ_@jDSxCg@~FeCxBqAjZkQhJgE`MgEdYwJjNyEbq@gUjUwJbRyKfM_JpLqKnk@sn@lR_PxMgJjR}JhRqHbMoDlPwDdQ{BbTwBjQmAbTwAhSp@dZtCzVvCtOfClFpAlZnH`^fFls@tI`IhA~B\\b}AlShcAdMhOtBjQvCxUbH~IhC~LrArz@nDfk@rDbJbAjK|BlIfGdFbHd^ln@`HfKnErF`EdDfJ`Frg@lPnKxBvHZfl@gC`\\BbImAdOsHv_@sUdLeJjQ}HfLeDlS_GzNuCzLSfJr@x]vGvR~EfP`GxFnFhGdJ`O~UvKtXZt@lG`VdEpSrCdVhBlPzBlMhDjKlD~ErDdEdHhDlz@hXbRxJfNrJlEvDrGfHjv@xgAdLlQtGhMrCdGjNda@fTli@jNt[dObUlX~YbSfTzPxQhSrU`IxMrKpVjKrVfF|LbOn\\tOtWdLxOpJzLbHrHpIlFvGfB`E^lGL|Qf@hPl@df@tEzWrDfTnFf\\~Ij_@vK~X`KrZbK|F|B|L|ElLvG`P`L~B|BzG|GdVb^fCtDtTz\\rQ`XdJxP|GnLrK~NnQrRxWlUlZxVpW|WrQ`TdGtKxElKzBnHdErMrJ|\\bFfOtEpJdFnItFdJnGjMnGnOvYb~@pH`Uj@dDpClPlA`UX|u@Rl[B`FVdDbAtCdDfGlGzN`Rd^pRh`@fMrUfGdIpKdJnMpIpSzKhE?zBi@dAeAr@Mp@Ll@f@b@z@RjADrAMpAWx@]j@e@^i@Jm@nA[t@w@|CgDtHKTs@z@_GlJkCrEqBpCgH|J}BzDmDpAwKjJoMlLuBlA{BzBm\\~TsA|AcBlBk@`A_BrCyCbJyBtJuBhMiC|I}DbJsE`HuGrE_Ab@'}],
  'summary': {'has_time_restrictions': False,
   'has_toll': False,
   'has_highway': True,
   'has_ferry': False,
   'min_lat': 50.548826,
   'min_lon': -4.146689,
   'max_lat': 50.861101,
   'max_lon': -3.38319,
   'time': 3623.905,
   'length': 55.4261,
   'cost': 3504.635},
  'status_message': 'Found route between points',
  'status': 0,
  'units': 'miles',
  'language': 'en-US'}}
summary
{'total_time_s': 3623.905,
 'total_dist': 55.4261,
 'legs': [{'leg_index': 0,
   'time_s': 3623.905,
   'distance': 55.4261,
   'speed': 55.06048309765294}]}
valhalla_audit_route_speeds(result)

=== ROUTE SUMMARY ===
Total time: 60.4 min
Total dist: 55.43 km

=== SPEED DISTRIBUTION ===
< 20 km/h (urban/stop-start)        | time:    0.3 min (  0.5%) | dist:    0.0 km (  0.1%)
20–40 km/h (dense urban)            | time:    3.2 min (  5.4%) | dist:    1.4 km (  2.4%)
40–60 km/h (suburban)               | time:   17.1 min ( 28.4%) | dist:   12.6 km ( 22.7%)
60–90 km/h (A-road)                 | time:   39.7 min ( 65.7%) | dist:   41.5 km ( 74.8%)
90+ km/h (motorway-like)            | time:    0.0 min (  0.0%) | dist:    0.0 km (  0.0%)
{'total_time_s': 3623.896,
 'total_dist_km': 55.42519999999999,
 'band_stats': {'20–40 km/h (dense urban)': {'time': 194.921, 'dist': 1.3539},
  '< 20 km/h (urban/stop-start)': {'time': 17.863, 'dist': 0.0434},
  '60–90 km/h (A-road)': {'time': 2382.6310000000003, 'dist': 41.4633},
  '40–60 km/h (suburban)': {'time': 1028.481, 'dist': 12.564599999999999},
  '90+ km/h (motorway-like)': {'time': 0.0, 'dist': 0.0}}}
result_gdf = valhalla_maneuvers_to_gdf(result)

ax = result_gdf.plot(column="speed_kmh", legend=True, figsize=(8,4))

cx.add_basemap(
    ax, # the figure we created using our plot method
    crs=result_gdf.crs.to_string(), # we can pull the CRS out of the geodataframe!
    )

# Grab the auto-generated attribution object from matplotlib axes
txt = ax.texts[-1]
# Adjust its position to outside the map, just below the bottom edge
txt.set_position([0.5, -0.1])
txt.set_ha('center') # Center the text
txt.set_va('top')

If we are familiar with these routes, this might help us better understand where valhalla is overestimating or underestimating speeds.

Back to top