I'm using OSMnx to do routing between two nodes in a graph and an obvious route is not being returned as one of the shortest.
The relevant code:
start = (42.73843806388065, -84.51908658324757)
distance_max = 3163.834987247283
G = ox.graph_from_point(start, network_type="drive", dist=distance_max, simplify=False)#, truncate_by_edge=True)
G = ox.utils_graph.get_undirected(G)
lng_orig = -84.51858078986406
lat_orig = 42.73524265064318
lng_dest = -84.51910484455361
lat_dest = 42.753847179060145
orig = ox.distance.nearest_nodes(G, X=lng_orig, Y=lat_orig)
dest = ox.distance.nearest_nodes(G, X=lng_dest, Y=lat_dest)
k_routes = ox.k_shortest_paths(G, orig, dest, 30, weight="distance")
fig, ax = ox.plot_graph_routes(G, list(k_routes), route_colors='r', route_linewidth=5, node_size=20)
plt.show()
The top 30 routes are shown in the image below, I expected a best route to be the road to the right of the long north-south road in the top half of the image. The nodes appear to be connected.
top 30 routes shown in red
Fix
You want to use a weight of length, not distance.
k_routes = ox.k_shortest_paths(G, orig, dest, 30, weight="length")
routes with length metric
Explanation
This problem is caused by a weird interaction between the OSMnx and networkx libraries.
If you read the docs for k_shortest_paths(), it never mentions that you can pass weight="distance". It turns out that you get the behavior you're seeing if you pass any invalid weight.
k_routes = ox.k_shortest_paths(G, orig, dest, 30, weight="blah")
If you do this, the weight value put on every graph edge becomes None. OSMnx then calls networkx.shortest_simple_paths(). The docs for that function say that if every weight is None, then the graph is treated as if every edge has unit weight:
If None all edges are considered to have unit weight.
The kind of distance it's optimizing for here is graph distance. In graph distance, all of the edges between each point have distance one. A road with a curve in it has a distance equal to the number of points used to represent the curve. A long straight road has a distance of one.
Related
I am trying to compute the distance between 2 points (lat, lon), using osmnx package.
While testing osmnx.nearest_nodes() to firstly find the nearest node from a point, I noticed that it doesn't seem to take into account the street direction when computing the nearest node (for example when the point is on a one-way street).
For instance : if I take this (lat, lon) point (48.921281, 2.517598), located on a one-way street, the nearest node is found only considering the distance from it (the node n°OSMID 288965181 found with nearest_nodes() : 48.9217176, 2.5180361).
nearest_nodes() doesn't seem to consider the type of network that I have choosen (drive).
If so, the found node should have been node 288964629 (48.9203235, 2.5166429).
Why ? Because from the point (48.921281, 2.517598), the node 288965181 is not the closest node considering the driving network, while the node 288964629 is.
Here is the code example :
import osmnx as ox
import networkx as nx
#creating the graph from a point coordinates
centreCoord = (48.920576, 2.529185)
G= ox.graph_from_point(centreCoord, dist=2000, network_type='drive')
#origin and destination point
originCoord = (48.921281, 2.517598)
destinationCoord = (48.921454, 2.518696)
#calculating the nearest nodes from origin and destination points
origin_node = ox.nearest_nodes(G, originCoord[1], originCoord[0])
destination_node = ox.nearest_nodes(G, destinationCoord[1], destinationCoord[0])
#computing the driving path
route = nx.shortest_path(G, source=origin_node, target=destination_node, weight='length')
Here a simple plot where :
the red dot is the origin point (48.921281, 2.517598)
the blue dot is the destination point (48.921454, 2.518696)
the green dot is the node 288965181 (48.9217176, 2.5180361)
the black dot is the node 288965599, the closest dot from the destination point
the magenta line is the route computed with networkx.shortest_path()
I have added :
the orange dot which is the node 288964629 (48.9203235, 2.5166429) -> the node that I would like to have as the closest node from the origin point, regarding the direction of traffic (the choosen network type is 'drive')
the blue path -> the route that I should obtained, regarding the direction of traffic
Plot image
I might have missed sometimes or done something wrong.
I have read the osmnx documentation. I tried several combination (parameters network_type, ox.settings.bidirectional_network_types), tried to debug the computation in order to understand how the nodes are selected.
Before asking, I have searched for similar topics. A lot of interesting stuff but I didn't find relevant answers.
I am stuck on this for days.
Any help would be great !
A possible workaround could be the following:
find the nearest edge from the origin point, using ox.nearest_edges() function. You will get the one-way road;
the edge is defined by u,v nodes: u is the starting node of the way, v the ending node, following the street direction;
the v node is the node you want to be considered (288964629), so compute the route from the v node to the destination node.
# Get the nearest edge from the origin point
u, v, k = ox.nearest_edges(G, originCoord[1], originCoord[0])
destination_node = ox.nearest_nodes(G, destinationCoord[1], destinationCoord[0])
# computing the driving path
route = nx.shortest_path(G, source=v, target=destination_node, weight='length')
Here the plot, by using ox.plot_graph_route(G, route):
Note that the described approach works fine with one-way roads made of only two nodes.
I am working in a discrete 2D grid of points in which there are "shapes" that I would like to create points outside of. I have been able to identify the vertices of these points and take convex hulls. So far, this leads to this and all is good and well. The purple here is the shape in question and the red line is the convex contour I have computed.
What I would like to do now is create two neighborhoods of points outside this shape. The first one is a set of points directly outside (as close as the grid size will allow), the second is another set of points but offset some distance away (the distance is not fixed, but rather an input).
I have attempted to write this in Python and get okay results. Here is an example of my current output. The problem is I notice the offsets are not perfect, for example look at the bottom most point in the image I attached. It kinks downwards whereas the original shape does not. It's not too bad in this example, but in other cases where the shape is smaller or if I take a smaller offset it gets worse. I also have an issue where the offsets sometimes overlap, even if they are supposed to be some distance away. I would also like there to be one line in each section of the contour, not two lines (for example in the top left).
My current attempt uses the Shapely package to handle most of the computational geometry. An outline of what I do once I have found the vertices of the convex contour is to offset these vertices by some amount, and interpolate along each pair of vertices to obtain many points alone these lines. Afterwards I use a coordinate transform to identify all points to the nearest grid point. This is how I obtain my final set of points. Below is the actual code I have written.
How can I improve this so I don't run into the issues I described?
Function #1 - Computes the offset points
def OutsidePoints(vertices, dist):
poly_line = LinearRing(vertices)
poly_line_offset = poly_line.buffer(dist, resolution=1, join_style=2, mitre_limit=1).exterior
new_vertices = list(poly_line_offset.coords)
new_vertices = np.asarray(new_vertices)
shape = sg.Polygon(new_vertices)
points = []
for t in np.arange(0, shape.length, step_size):
temp_points = np.transpose(shape.exterior.interpolate(t).xy)
points.append(temp_points[0])
points = np.array(points)
points = np.unique(points, axis=0)
return points
Function #2 - Transforming these points into points that are on my grid
def IndexFinder(points):
index_points = invCoordinateTransform(points)
for i in range(len(index_points)):
for j in range(2):
index_points[i][j] = math.floor(index_points[i][j])
index_points = np.unique(index_points, axis=0)
return index_points
Many thanks!
I have a problem involving graph theory. To solve it, I would like to create a weighted graph using networkx. At the moment, I have a dictionnary where each key is a node, and each value is the associated weight (between 10 and 200 000 or so).
weights = {node: weight}
I believe I do not need to normalize the weights with networks.
At the moment, I create a non-weighted graph by adding the edges:
def create_graph(data):
edges = create_edges(data)
# Create the graph
G = nx.Graph()
# Add edges
G.add_edges_from(edges)
return G
From what I read, I can add a weight to the edge. However, I would prefer the weight to be applied to a specific node instead of an edge. How can I do that?
Idea: I create the graph by adding the nodes weighted, and then I add the edges between the nodes.
def create_graph(data, weights):
nodes = create_nodes(data)
edges = create_edges(data) # list of tuples
# Create the graph
G = nx.Graph()
# Add edges
for node in nodes:
G.add_node(node, weight=weights[node])
# Add edges
G.add_edges_from(edges)
return G
Is this approach correct?
Next step is to find the path between 2 nodes with the smallest weight. I found this function: networkx.algorithms.shortest_paths.generic.shortest_path which I think is doing the right thing. However, it uses weights on the edge instead of weights on the nodes. Could someone explain me what this function does, what the difference between wieghts on the nodes and weights on the edges is for networkx, and how I could achieve what I am looking for? Thanks :)
This generally looks right.
You might use bidirectional_dijkstra. It can be significantly faster if you know the source and target nodes of your path (see my comments at the bottom).
To handle the edge vs node weight issue, there are two options. First note that you are after the sum of the nodes along the path. If I give each edge a weight w(u,v) = w(u) + w(v) then the sum of weights along this is w(source) + w(target) + 2 sum(w(v)) where the nodes v are all nodes found along the way. Whatever has the minimum weight with these edge weights will have the minimum weight with the node weights.
So you could go and assign each edge the weight to be the sum of the two nodes.
for edge in G.edges():
G.edges[edge]['weight'] = G.nodes[edge[0]]['weight'] + G.nodes[edge[1]]['weight']
But an alternative is to note that the weight input into bidirectional_dijkstra can be a function that takes the edge as input. Define your own function to give the sum of the two node weights:
def f(edge):
u,v = edge
return G.nodes[u]['weight'] + G.nodes[v]['weight']
and then in your call do bidirectional_dijkstra(G, source, target, weight=f)
So the choices I'm suggesting are to either assign each edge a weight equal to the sum of the node weights or define a function that will give those weights just for the edges the algorithm encounters. Efficiency-wise I expect it will take more time to figure out which is better than it takes to code either algorithm. The only performance issue is that assigning all the weights will use more memory. Assuming memory isn't an issue, use whichever one you think is easiest to implement and maintain.
Some comments on bidirectional dijkstra: Imagine you have two points in space a distance R apart and you want to find the shortest distance between them. The dijkstra algorithm (which is the default of shortest_path) will explore every point within distance D of the source point. Basically it's like expanding a balloon centered at the first point until it reaches the other. This has a volume (4/3) pi R^3. With bidirectional_dijkstra we inflate balloons centered at each until they touch. They will each have radius R/2. So the volume is (4/3)pi (R/2)^3 + (4/3) pi (R/2)^3, which is a quarter the volume of the original balloon, so the algorithm has explored a quarter of the space. Since networks can have very high effective dimension, the savings is often much bigger.
OSMnx provides solution to calculate the shortest path between two nodes, but I would like to the same with points on streets (I have GPS coordinates recorded from vehicles). I know there is also a method to get the closest node, but I have two question for this problem of mine.
i) When closest node computed is the street where the point is also taken into consideration? (I assume not)
ii) If I wanted to implement something like this, I like to know how a street (edge) is represented as a curve (Bézier curve maybe?). Is it possible to get the curve (or the equation of the curve) of an edge?
I asked this question here, because the guidelines for contributing of OSMnx asked it.
Streets and node in OSMnx are shapely.geometry.LineString, and shapely.geometry.Point objects, so there is no curve, only sequence of coordinates. The technical term for what you described is Map Matching. There are different ways of map matching, the simplest one being geometric map matching in which you find nearest geometry (node or edge) to the GPS point. point to point map matching can be easily achieved using built-in osmnx function ox.get_nearest_node(). If you have a luxury of dense GPS tracks, this approach could work reasonably good. For point to line map matching you have to use shapely functions. The problem with this approach is that it is very slow. you can speed up the algorithm using spatial index, but still, it will not be fast enough for most purposes. Note that geometric map matching are least accurate among all approaches. I wrote a function a few weeks ago that does simple point to line map matching using edge GeoDataFrame and node GeoDataFrame that you can get from OSMnx. I abandoned this idea and now I am working on a new algorithm (hopefully much faster), which I will publish on GitHub upon completion. Meanwhile, this may be of some help for you or someone else, so I post it here. This is an early version of abandoned code, not tested enough and not optimized. give it a try and let me know if it works for you.
def GeoMM(traj, gdfn, gdfe):
"""
performs map matching on a given sequence of points
Parameters
----------
Returns
-------
list of tuples each containing timestamp, projected point to the line, the edge to which GPS point has been projected, the geometry of the edge))
"""
traj = pd.DataFrame(traj, columns=['timestamp', 'xy'])
traj['geom'] = traj.apply(lambda row: Point(row.xy), axis=1)
traj = gpd.GeoDataFrame(traj, geometry=traj['geom'], crs=EPSG3740)
traj.drop('geom', axis=1, inplace=True)
n_sindex = gdfn.sindex
res = []
for gps in traj.itertuples():
tm = gps[1]
p = gps[3]
circle = p.buffer(150)
possible_matches_index = list(n_sindex.intersection(circle.bounds))
possible_matches = gdfn.iloc[possible_matches_index]
precise_matches = possible_matches[possible_matches.intersects(circle)]
candidate_nodes = list(precise_matches.index)
candidate_edges = []
for nid in candidate_nodes:
candidate_edges.append(G.in_edges(nid))
candidate_edges.append(G.out_edges(nid))
candidate_edges = [item for sublist in candidate_edges for item in sublist]
dist = []
for edge in candidate_edges:
# get the geometry
ls = gdfe[(gdfe.u == edge[0]) & (gdfe.v == edge[1])].geometry
dist.append([ls.distance(p), edge, ls])
dist.sort()
true_edge = dist[0][1]
true_edge_geom = dist[0][2].item()
pp = true_edge_geom.interpolate(true_edge_geom.project(p)) # projected point
res.append((tm, pp, true_edge, true_edge_geom))
return res
OSMnx was recently updated since there have been a couple of requests in this direction (see https://github.com/gboeing/osmnx/pull/234 and references therein). So in the last update, you'll find a function like this:
ox.get_nearest_edge(G, (lat, lon))
It will give you the ID of the nearest edge, which is much better than nearest nodes.
However, I think it is more useful to also get the actual distance of the nearest edge in order to check whether or not your data point is on the road or a few thousand meters apart...
To do this, I followed the implementation from https://github.com/gboeing/osmnx/pull/231/files
# Convert Graph to graph data frame
gdf = ox.graph_to_gdfs(G, nodes=False, fill_edge_geometry=True)
# extract roads and some properties
roads = gdf[["geometry", "u", "v","ref","name","highway","lanes"]].values.tolist()
# calculate and attach distance
roads_with_distances = [(road, ox.Point(tuple(reversed((lat,lon)))).distance(road[0])) for road in roads]
# sort by distance
roads_with_distances = sorted(roads_with_distances, key=lambda x: x[1])
# Select closest road
closest_road = roads_with_distances[0]
# Check whether you are actually "on" the road
if closest_road[1] < 0.0001: print('Hit the road, Jack!')
I have the impression that a distance on the order of $10^{-5}$ means that the coordinate is actually "on" the road.
I searched but found there are many examples about how to create a graph with edges weight, but none of them shows how to create a graph with vertices weight. I start to wonder if it is possible.
If a vertices-weighted graph can be created with igraph, then is it possible to calculate the weighted independence or other weighted numbers with igraph?
As far as I know, there are no functions in igraph that accept arguments for weighted vertices. However, the SANTA package that is a part of the Bioconductor suite for R does have routines for weighted vertices, if you are willing to move to R for this. (Seems like maybe you can run bioconductor in python.)
Another hacky option is the use (when possible) unweighted routines from igraph and then back in the weights. E.g. something like this for weighted maximal independent sets:
def maxset(graph,weight):
ms = g.maximal_independent_vertex_sets()
w = []
t = []
for i in range(0, 150):
m = weights.loc[weights['ids'].isin(ms[i]),"weights"]
w.append(m)
s = sum(w[i])
t.append(s)
return(ms[t.index(max(t))])
maxset(g,weights)
(Where weights is a two column data frame with column 1 = vertex ids and column 2 = weights). This gets the maximal independent set taking vertex weights into consideration.
You want to use vs class to define vertices and their attributes in igraph.
As example for setting weight on vertices, taken from documentation:
http://igraph.org/python/doc/igraph.VertexSeq-class.html
g=Graph.Full(3) # generate a full graph as example
for idx, v in enumerate(g.vs):
v["weight"] = idx*(idx+1) # set the 'weight' of vertex to integer, in function of a progressive index
>>> g.vs["weight"]
[0, 2, 6]
Note that a sequence of vertices are called through g.vs, here g the instance of your Graph object.
I suggested you this page, I found it practical to look for iGraph methods here:
http://igraph.org/python/doc/identifier-index.html