0

I have a GeoDataFrame of GPS points and I want to calculate the length and duration of trip from each point to all other points.

My input GeoDataFrame looks like this:

    id  Longitude   Latitude    geometry
0   1   -71.275580  46.837460   POINT (-71.27558 46.83746)
1   2   -71.225308  46.814997   POINT (-71.22531 46.81500)
2   3   -71.214790  46.811887   POINT (-71.21479 46.81189)
3   4   -71.215936  46.846704   POINT (-71.21594 46.84670)
4   5   -71.214600  46.812775   POINT (-71.21460 46.81278)
5   6   -71.227372  46.814365   POINT (-71.22737 46.81437)
6   7   -71.353066  46.821339   POINT (-71.35307 46.82134)
​

I have cross joined the gdf with itself to have a row of origin and destination points for all possible routes or trips. The crossed gdf looks like this:



   id_o Longitude_o Latitude_o  geometry_o                 id_d Longitude_d Latitude_d  geometry_d
1   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
2   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
3   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
4   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
5   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)
6   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
7   2   -71.225308  46.814997   POINT (-71.22531 46.81500)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
9   2   -71.225308  46.814997   POINT (-71.22531 46.81500)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
10  2   -71.225308  46.814997   POINT (-71.22531 46.81500)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
11  2   -71.225308  46.814997   POINT (-71.22531 46.81500)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
12  2   -71.225308  46.814997   POINT (-71.22531 46.81500)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)
13  2   -71.225308  46.814997   POINT (-71.22531 46.81500)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
14  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
15  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
17  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
18  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
19  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)
20  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
21  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
22  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
23  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
25  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
26  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)
27  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
28  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
29  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
30  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
31  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
33  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)
34  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
35  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
36  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
37  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
38  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
39  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
41  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)
42  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  1   -71.275580  46.837460   POINT (-71.27558 46.83746)
43  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)
44  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)
45  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)
46  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)
47  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)

Then, I used the following script to calculate the length and duration of trip for each row of this crossed gdf:


origins_x = list(gdf['geometry_o'].x)
origins_y = list(gdf['geometry_o'].y)
destinations_x = list(gdf['geometry_d'].x)
destinations_y = list(gdf['geometry_d'].y)


trans_modes = ['drive', 'bike', 'walk']

# Downloading network for each mode of transport
for mode in trans_modes:
    
    print("calculations for this mode of transoprt is started:  " + mode )
    G = ox.graph_from_bbox(bbx_north, bbx_south, bbx_east, bbx_west, network_type=mode)

    # impute speed on all edges missing data
    G = ox.add_edge_speeds(G)

    # calculate travel time (seconds) for all edges
    G = ox.add_edge_travel_times(G)


    origin_nodes = ox.distance.nearest_nodes(G, origins_x, origins_y)
    destination_nodes = ox.distance.nearest_nodes(G, destinations_x, destinations_y)


    routes = ox.shortest_path(G, origin_nodes, destination_nodes, weight='length')

#     gdf['routes'] = routes

    gdf['length_' + str(mode)] = [int(sum(ox.utils_graph.get_route_edge_attributes(G, routes[i], "length"))) for i in range(len(routes))]
    gdf['duration_' + str(mode)] = [int(sum(ox.utils_graph.get_route_edge_attributes(G, routes[i], "travel_time"))) for i in range(len(routes))]
    
    

then I have this gdf as output:



    id_o    Longitude_o Latitude_o  geometry_o           id_d   Longitude_d Latitude_d  geometry_d                len_dr  dur_dr    len_bi  dur_bi len_wa   dur_wa
1   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  2   -71.225308  46.814997   POINT (-71.22531 46.81500)  6791    386     7369    563     6916    538
2   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  3   -71.214790  46.811887   POINT (-71.21479 46.81189)  7559    441     8136    618     7800    603
3   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  4   -71.215936  46.846704   POINT (-71.21594 46.84670)  7450    509     8705    628     8515    627
4   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  5   -71.214600  46.812775   POINT (-71.21460 46.81278)  7922    469     8249    630     7858    609
5   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  6   -71.227372  46.814365   POINT (-71.22737 46.81437)  6429    360     7030    538     6707    522
6   1   -71.275580  46.837460   POINT (-71.27558 46.83746)  7   -71.353066  46.821339   POINT (-71.35307 46.82134)  7651    465     7938    630     9803    726



I have compared the results with google maps. It seems that length is almost correct for all of them. However, the duration is only correct for "dr" which is short for "drive" mode. Why "walk" and "bike" modes do not have correct durations!!

In fact, it seems that I have calculated the correct route length for bike and walk modes of transport, but at the end, I have calculated the duration of that length with the speed of car instead of bike or walk!!

How can I calculate the correct duration for bike and walk?

2 Answers 2

2

According to the documentation of OSMNX, your line with G = ox.add_edge_speeds(G) will :

Add edge speeds (km per hour) to graph as new speed_kph edge attributes.

By default, this imputes free-flow travel speeds for all edges via the mean maxspeed value of the edges of each highway type. For highway types in the graph that have no maxspeed value on any edge, it assigns the mean of all maxspeed values in graph.

So I would assume that it just takes the max speed from the OSM data which would be the legal speed limit (so for cars). Which would explain your results.

This example calculates the walking speeds, and adds it to each edge directly (instead of G = ox.add_edge_travel_times(G) in your code)

travel_speed = 4.5 #walking speed in km/hour
# add an edge attribute for time in minutes required to traverse each edge
meters_per_minute = travel_speed * 1000 / 60 #km per hour to m per minute
for u, v, k, data in G.edges(data=True, keys=True):
    data['time'] = data['length'] / meters_per_minute

Another option would be to redefine the max speeds (20 is a decent speed for biking in the city)

travel_speeds = {'residential': 20,
              'secondary': 20,
              'tertiary': 20}
G = ox.add_edge_speeds(G, travel_speeds)
G = ox.add_edge_travel_times(G)

You could also combine that with some elevation matching (as cycling uphill is slower), number of traffic lights, ... to make it more realistic.

Maybe there is a setting for osmx to directly use walking/biking speeds, but I did not find it. It seems it uses the transport-mode information to create the edges of the graph, but forgets about it for the other calculations.

0

I have done the same solution. I have assumed the average bike and walk speed and then based on those values I divided the total length of the route by that speed.




#######################################################################################################################
#################################       Set the average bike and walk speeds    #######################################
#######################################################################################################################
avg_bike_speed = 4.5
avg_walk_speed = 1.3


columns = ['id', 'Longitude', 'Latitude']
df = pd.DataFrame(list_of_list, columns = columns)

gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.Longitude, df.Latitude), crs='EPSG:4326')
# gdf.drop(['Longitude', 'Latitude'], axis=1, inplace=True)

#######################################################################################################################
    ###################################       For Each Mode of Transport    #########################################
#######################################################################################################################

# looping over the modes of transport

trans_modes = ['drive', 'bike', 'walk']


bbx = gdf.total_bounds
# print(bbx)

bbx_north=bbx[3]
bbx_south=bbx[1]
bbx_east=bbx[2]
bbx_west=bbx[0]


# Cross join the geodataframe with itself
gdf = pd.merge(gdf, gdf, how="cross", suffixes=("_o", "_d"))
# Delete the rows with the same point
gdf = gdf[gdf["id_o"] != gdf["id_d"]]


origins_x = list(gdf['geometry_o'].x)
origins_y = list(gdf['geometry_o'].y)
destinations_x = list(gdf['geometry_d'].x)
destinations_y = list(gdf['geometry_d'].y)

# Downloading network for each mode of transport
for mode in trans_modes:
    
    print("calculations for this mode of transoprt is started:  " + mode )
    G = ox.graph_from_bbox(bbx_north, bbx_south, bbx_east, bbx_west, network_type=mode)
    

    origin_nodes = ox.distance.nearest_nodes(G, origins_x, origins_y)
    destination_nodes = ox.distance.nearest_nodes(G, destinations_x, destinations_y)


    routes = ox.shortest_path(G, origin_nodes, destination_nodes, weight='length')

#     gdf['routes'] = routes

    gdf['length_' + str(mode)] = [int(sum(ox.utils_graph.get_route_edge_attributes(G, routes[i], "length"))) for i in range(len(routes))]
    
    if mode == 'drive':
        # impute speed on all edges missing data
        G = ox.add_edge_speeds(G)

        # calculate travel time (seconds) for all edges
        G = ox.add_edge_travel_times(G) 
        gdf['duration_' + str(mode)] = [int(sum(ox.utils_graph.get_route_edge_attributes(G, routes[i], "travel_time"))) for i in range(len(routes))]

    elif mode == 'bike':
        gdf['duration_' + str(mode)] = gdf['length_' + str(mode)] / avg_bike_speed
        gdf['duration_' + str(mode)] = gdf['duration_' + str(mode)].astype('int')

        
    elif mode == 'walk':
        gdf['duration_' + str(mode)] = gdf['length_' + str(mode)] / avg_walk_speed
        gdf['duration_' + str(mode)] = gdf['duration_' + str(mode)].astype('int')



Now, my results are similar to those of google maps. I assume the difference is due to the fact that google's model may consider the slop and other factors in determining the speed of walking and biking in a segment or edge of graph.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.