I'm new working with OSM data, OSMnx and networkx libraries, so if there's a better way to accomplish on getting shortest path from A to B locations, I'll appreciate any advice.
I'm trying to get shortest path from A to B. I successfully get the path, but length and time travel are very different comparing them with what Google Maps returns.
1. Why path length and travel time are very different from Google Maps / Bing Maps?
2. How can I improve accuracy?
Addresses used: 3115 W Bancroft St, Toledo, OH 43606 6721 Whiteford Center Rd, Lambertville, MI 48144
OSMnx length = 9135 mts, travel time = 8.3 min.
Google Maps length = 9334.2 mts, travel time = 12 min.
BingMaps length = 9334.2 mts, travel time = 13 min.
Difference in distance is 199.2 mts and aprox ~ 5 min
import sys
import osmnx as ox
import networkx as nx
from shapely.geometry import box, Point
ox.config(use_cache=True, log_console=True)
def geocode(address):
"""Geocode an address using OSMnx."""
try:
x, y = ox.geocode(address)
except Exception as e:
print(f'Error: {str(e)}')
return None, None
return x, y
def boundary_constructor(orig_x, orig_y, dest_x, dest_y):
"""Create a bounding box around two points."""
boundary_box = Point(orig_y, orig_x).buffer(0.001).union(Point(dest_y, dest_x).buffer(0.001)).bounds
minx, miny, maxx, maxy = boundary_box
bbox = box(*[minx, miny, maxx, maxy])
return bbox
def getting_osm(bbox, network_type, truncate_edges):
"""Retrieve OSM data (roads, edges, nodes) for a given bounding box and network type."""
G = ox.graph_from_polygon(bbox, retain_all=False, network_type=network_type, truncate_by_edge=truncate_edges)
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
roads = ox.graph_to_gdfs(G, nodes=False, edges=True)
return G, roads
def find_closest_node(G, lat, lng, distance):
"""Find the closest node in a graph to a given latitude and longitude."""
node_id, dist_to_loc = ox.distance.nearest_nodes(G, X=lat, Y=lng, return_dist=distance)
return node_id, dist_to_loc
def shortest_path(G, orig_node_id, dest_node_id, weight):
"""Find the shortest path between two nodes in a graph."""
try:
route = ox.shortest_path(G, orig_node_id, dest_node_id, weight=weight)
except nx.NetworkXNoPath as e:
print(f"No path found between {orig_node_id} and {dest_node_id}")
print(e)
return route
def find_length_and_time(G, travel_length, travel_time):
try:
route_length = int(sum(ox.utils_graph.route_to_gdf(G, travel_length, "length")["length"]))
route_time = int(sum(ox.utils_graph.route_to_gdf(G, travel_time, "travel_time")["travel_time"]))
except Exception as e:
print(f'Error: {e}')
return route_length, route_time
def route_plotting(G, travel_length, travel_time):
"""Plot the shortest path between two addresses."""
if travel_length and travel_time:
fig, ax = ox.plot_graph_routes(
G,
routes=[travel_length, travel_time],
route_colors=["r", "y"],
route_linewidth=6,
node_size=0
)
elif travel_length:
ox.plot_route_folium(G, travel_length, popup_attribute='length')
elif travel_time:
ox.plot_route_folium(G, travel_time, popup_attribute='travel_time')
def main():
# User address input
origin_address = str(input('Enter the origin address: '))
destination_address = str(input('Enter the destination adddress: '))
# Geocode addresses
orig_x, orig_y = geocode(origin_address)
dest_x, dest_y = geocode(destination_address)
# Check if geocoding was successful
if not all([orig_x, orig_y, dest_x, dest_y]):
print("Unable to geocode one or both addresses. Exiting...")
sys.exit()
# Create bounding box
bbox = boundary_constructor(orig_x, orig_y, dest_x, dest_y)
# Retrieve OSM data
G, roads = getting_osm(bbox, network_type='drive', truncate_edges='True')
# Find closest node
orig_node_id, dist_to_orig = find_closest_node(G, orig_y, orig_x, True)
dest_node_id, dist_to_dest = find_closest_node(G, dest_y, dest_x, True)
# find shortest path
travel_length = shortest_path(G, orig_node_id, dest_node_id, weight='length')
travel_time = shortest_path(G, orig_node_id, dest_node_id, weight='travel_time')
# find route length and route time
route_length, route_time = find_length_and_time(G, travel_length, travel_time)
if route_length and route_time:
print(f"Shortest travel length:{route_length: .2f} meters and takes {(route_time/60)} minutes")
elif route_length:
print(f"Shortest travel length: {route_length: .2f}. No travel time found")
elif route_time:
print(f"Shortest travel time: {(route_time/60)}. No travel length found")
# plot routes
route_plotting(G, travel_length, travel_time)
if __name__ == "__main__":
main()
Why the path length and travel time are so different from Google Maps and Bing Maps?
Broadly, it's some combination of those mapping providers having better data and better algorithms.
Starting with data, most streets in open street maps do not have the maximum legal speed annotated. For any of these streets, OSMnx attempts to guess the max speed by looking at the highway type, and looking at roads of the same highway type which do have maxspeed annotations. (Source.) The assumption is that if the average residential street in a city has a max speed of 20 mph, then a residential street with no known speed limit probably has a max speed of 20 mph. Of course, this assumption is not always true, and your route plan will be better quality if it knows the actual speed.
To improve this, you can provide the hwy_speeds
argument to add_edge_speeds()
, if you know what speed limits unposted streets have in your jurisdiction.
This gets into the next problem, which is that people don't always drive the speed limit. Sometimes they drive faster, or sometimes there's traffic and they have to slow down. This is hard to solve without some data source with traffic info.
Another issue is that OSMnx assumes that all intersections take zero seconds to cross. The algorithm that OSMnx uses to find the fastest route does not allow intersections (or nodes) to have any travel time. Even if that weren't a problem, one would need to figure out how long an intersection takes to cross. (Some questions you'd need to think about to solve this: does this intersection have a stoplight? Is this a right or left turn? Do you have right-of-way?)
In summary, there are four factors which can cause the travel time to differ:
Of these four factors, two of them can cause an underestimate of travel time. One of them can cause an overestimate. One of them can affect your estimate in either direction. In my experience, OSMnx usually underestimates travel time.