Add node between existing edge in Networkx Graph generated by OSMnx - python

I have gotten sensor location data from Highway England. I want to add these sensor locations to OSM multidigraph. How to do that?
import numpy as np
import pandas as pd
import networkx as nx
from shapely.geometry import Point, Polygon, LineString
import geopandas as gpd
import osmnx as ox
Graph data is
graph = ox.graph.graph_from_bbox(52.2, 51.85, -.6, -0.9, network_type='drive', simplify=False)
I want to add sensor = Point(-0.6116768, 51.8508765) on the edge nearest to it. Nearest edges to this sensor is n_edge = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False). Now, I need to bend this n_edge such that it passes through the given sensor point.
I found a way to solve this issue by creating a new node in graph, graph.add_node('sensor25', y= 51.8508765, x= -0.6116768, street_count = 2) then graph.add_edges_from([(n_edge[0], 'sensor25'), ('sensor25', n_edge[1)]). However, the node created by me (sensor25) is not identical to other nodes. How to make this node similar to existing nodes?
I have went through following questions
add attribute to node
add new node to existing edge in networkx
add random nodes on edges manually.

I'm not 100% certain what you need, what I understand: You want to add new edges with attributes: speed_limit, length, street number one way, copied from the edge you delete?
I assume that some of these attributes can be copied 1:1, like one way, while others will have to be recalulated. For simplicity, let's assume we have a function d(a, b) that takes (graph) nodes a and b, extracts their position, and calculates the air distance between them. Define other functions as required.
Then you could e.g. define the new edge like this:
# Get from/to id of closest edge
f, t = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False)[0]
c = 'sensor25' # Id of new node, c as in 'center'
edge_attrs = g[f][t] # Copy edge attributes
g.remove_edge(f, t) # Remove edge from graph
graph.add_node(c, y= 51.8508765, x= -0.6116768, street_count = 2)
# Add new edges, recalculating atttributes as required
g.add_edge(f, c, **{**edge_attrs, 'length': d(f, c)})
g.add_edge(c, t, **{**edge_attrs, 'length': d(c, t)})
Hope the syntax is clear, otherwise ask. It copies edge_attrs 1:1, except for attributes you specify after, like lenght. Probably you will have to define multiple functions like d, that also calculate the geometry etc.
The code isn't tested.

Related

Osmnx returns just the start and destination OSM ids when finding route between them

To get the route between two coordinates using osmnx, I used the following code:
import osmnx as ox
ox.config(use_cache=True, log_console=True)
G = ox.graph_from_place('Sydney,New South Wales,Australia', network_type='drive')
import networkx as nx
# find the nearest node to the start location
orig_node = ox.get_nearest_node(G,(intersections['lat'][0],intersections['lon'][0]))
# find the nearest node to the end location
dest_node = ox.get_nearest_node(G,(intersections['lat'][1],intersections['lon'][1]))
shortest_route=nx.shortest_path(G,orig_node,dest_node,weight='time')
where, intersections is a dataframe that contains the latitude and longitude of various intersections in sydney.
intersections['lat'][0],intersections['lon'][0] represents the latitude and longitude of the 0th entry and so on.
When I plot this, I do get the appropriate results:Plot showing the route
I get the OSM ids of the points in these routes as:
[771347, 1612748582]
But these seem to be the start and destination points itself.
Is there any way I can get all the coordinates in the route shown in the image above using osmnx itself. I'm aware I can use various APIs for this, but since I have 75000 points, and I need to find the routes between all these points(along with the coordinates that form the route), I would like a more efficient solution to this.
nx.shortest_path() returns a list of OSM ids of the nodes that form the route.
In osmnx, you can get OSM's nodes information using ox.graph_to_gdfs() that will return a GeoDataFrame with all the nodes of the graph.
Once you have all the nodes in a GeoDataFrame, you can easily extract the coordinates:
# Get the nodes given the graph, as a GeoDataFrame
nodes = ox.graph_to_gdfs(G, nodes=True, edges=False)
# Extract only the nodes that form your route
nodes = nodes.set_index('id').reindex(shortest_route).reset_index()
# Store all the route information into a DataFrame keeping only useful columns
route_df = nodes[['id', 'lon', 'lat']]
But these seem to be the start and destination points itself. Is there any way I can get all the coordinates in the route shown in the image above using osmnx itself.
You are getting all the nodes in the route. The origin and destination you provided as an example (OSM IDs 771347 and 1612748582) are adjacent nodes in the graph, because there is no other node between those two off-ramps along this expressway. Hence it is a path comprising only those two nodes.
I need to find the routes between all these points (along with the coordinates that form the route)
First off, you are using a very old version of OSMnx with several deprecated or obsolete functions in it. Your code can be much more efficient if you refactor it, and even more efficient still if you upgrade to the latest version (v1.2.2 as of this writing) to use its newer functionality. For example, you can find all your nearest nodes at once, using an efficient index, with the ox.nearest_nodes function. And you can solve all your shortest paths in parallel, using multiprocessing, with the ox.shortest_path function.
import osmnx as ox
ox.settings.use_cache = True
ox.settings.log_console = True
# get graph and add free-flow travel times to its edges
G = ox.graph_from_place("Sydney, New South Wales, Australia", network_type="drive")
G = ox.add_edge_travel_times(ox.add_edge_speeds(G))
# randomly select 1,000 origin/destination points, just for example
n = 1000
points = ox.utils_geo.sample_points(G, n)
orig_points = points[:int(n / 2)]
dest_points = points[int(n / 2):]
# find nearest node to each origin/dest point, then solve paths in parallel
orig_nodes = ox.nearest_nodes(G, X=orig_points.x, Y=orig_points.y)
dest_nodes = ox.nearest_nodes(G, X=dest_points.x, Y=dest_points.y)
paths = ox.shortest_path(G, orig_nodes, dest_nodes, weight="travel_time", cpus=None)
# convert node paths to lat-lng paths
paths_latlng = [[(G.nodes[node]["y"], G.nodes[node]["x"]) for node in path] for path in paths]
Note that if you have unsolvable routes in your graph (e.g., due to one-way edges and artificial perimeter effects), you'll need to handle (ignore/skip) those nulls when you convert nodes to lat-lng coordinates.

Return to original OSMnx graph from simplified graph with edge geometries

Is it possible to generate the original OSMnx graph from the simplified graph (which has the edge geometries preserved)?
For instance:
import osmnx as ox
place = 'Piedmont, California, USA'
G = ox.graph_from_place(place, network_type='drive', simplify=False)
G_simple = ox.simplify_graph(G)
G_simple has the original edge geometries of G stored as "geometry" on the simplified edges:
simple_nodes, simple_edges = ox.graph_to_gdfs(G_simple)
print(simple_edges.iloc[10].geometry)
# LINESTRING (-122.2429303 37.8205234, -122.2426591 37.8207235, -122.2424827 37.820899, -122.2421775 37.8212363, -122.2420372 37.8214758, -122.2420254 37.8215051, -122.2419343 37.8217305, -122.2418551 37.8218894, -122.2415415 37.8222826)
Would it be possible to generate the original graph G from the simplified one? I have many simplified graphs stored on disk, but unfortunately cannot regenerate the unsimplified graphs, so I need to find a way to "unsimplify" them.
This is a one-way destruction of information built into OSMnx. You could try to write your own script, but it would be nontrivial. You'd have to identify each vertex in each geometry of each edge, create a new node there, and break the edge into two at that vertex.

Networkx - problem while working big data

I am trying to triangulate a large amount of massive data using Delaunay to scipy.spatial for triangulation and networkx to get the node adjacency relations. My code works very well on small data sets but when I try to introduce volumes of about 2 miollion points I always get the following error:
raise NetworkXError(f"The node {n} is not in the graph.") from e
NetworkXError: The node 1 is not in the graph.
It seems like my graph store the first node and nothing more. When I did my research I found that networkx is well adapted to massive data
Here is my code :
import numpy as np
import networkx as nx
import scipy.spatial
points = np.genfromtxt('las1.xyz', delimiter = ';')
xy= points[:,0:2]
z= points[:,2]
delTri = scipy.spatial.Delaunay(xy)
edges = set()
for n in range(delTri.nsimplex):
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,1]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,1], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
pts_neigh = {}
graph = nx.Graph(list(edges))
for i in range(len(xy)):
pts_neigh[i] = list(graph.neighbors(i))
I still get the edges list from my networkx graph but it seems like it fails at the level of constructing the nodes.
I will be so grateful for your help.
Although it's possible to instantiate graph with specific data, the syntax can be a bit complex. An easier option is to explicitly add edges from a list:
graph = nx.Graph()
graph.add_edges_from(list(edges))

Python Netgraph Interactive Labels

I am trying to create an interactive plot using netgraph and networkx.
I would like the plot to allow movement of the nodes and as the nodes move, the edges and edge_labels will also dynamically update.
Moving the nodes was addressed by the author of netgraph here . Now when I make a simpler plot and try to label an edge, the label stays static, and is sometimes not even on an edge.
It seems like handling edge_positions similar to node_positions on the last two lines should at least address the label not moving. Why the label isn't anchored to a particular edge is still puzzling. Does anyone know if the desired effect is achievable?
Here is a snip before moving anything:
Here is a snip after moving the bottom-right node to the top left:
Here is my current code:
import matplotlib.pyplot as plt
import networkx as nx
import netgraph # pip install netgraph
#Graph creation:
G=nx.Graph(type="")
for i in range(6):
G.add_node(i,shape="o")
#Changing shape for two nodes
G.nodes[1]['shape'] = "v"
G.nodes[5]['shape'] = "v"
#Add edges
G.add_edge(1,2)
G.add_edge(4,5)
G.add_edge(0,4)
G.add_edge(2,3)
G.add_edge(2,4)
labs={(1,2):"1 to 2"}
nx.draw_networkx_edge_labels(G, pos=nx.spring_layout(G),edge_labels=labs)
#Get node shapes
node_shapes = nx.get_node_attributes(G,"shape")
# Create an interactive plot.
# NOTE: you must retain a reference to the object instance!
# Otherwise the whole thing will be garbage collected after the initial draw
# and you won't be able to move the plot elements around.
pos = nx.layout.spring_layout(G)
######## drag nodes around #########
# To access the new node positions:
plot_instance = netgraph.InteractiveGraph(G, node_shape=node_shapes, node_positions=pos, edge_positions=pos)
node_positions = plot_instance.node_positions
edge_positions = plot_instance.edge_positions
To have it as formal answer, you need to add to the InteractiveGraph object the information that you want to draw (and move) the edge labels, i.e. the following
netgraph.InteractiveGraph(G,
node_shape=node_shapes,
node_positions=pos,
edge_positions=pos,
edge_labels=labs)
(With emphasis on the last parameter)
As you already, noticed then you don't need the call of nx.draw_networkx_edge_labels.

Is it possible to control the order that nodes are drawn using NetworkX in python?

I have a large graph object with many nodes that I am trying to graph. Due to the large number of nodes, many are being drawn one over another. This in itself is not a problem. However, a small percentage of nodes have node attributes which dictate their colour.
Ideally I would be able to draw the graph in such a way that nodes with this property are drawn last, on top of the other nodes, so that it is possible to see their distribution across the graph.
The code I have so far used to generate the graph is shown below:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import os
import pickle
from pathlib import Path
def openFileAtPath(filePath):
print('Opening file at: ' + filePath)
with open(filePath, 'rb') as input:
file = pickle.load(input)
return file
# Pre manipulation path
g = openFileAtPath('../initialGraphs/wordNetadj_dictionary1.11.pkl')
# Post manipulation path
# g = openFileAtPath('../manipulatedGraphs/wordNetadj_dictionary1.11.pkl')
print('Fetching SO scores')
scores = list()
for node in g.nodes:
scores.append(g.node[node]['weight'])
print('Drawing network')
nx.draw(g,
with_labels=False,
cmap=plt.get_cmap('RdBu'),
node_color=scores,
node_size=40,
font_size=8)
plt.show()
And currently the output is as shown:
This graph object itself has taken a relatively long time to generate and is computationally intensive, so ideally I wouldn't have to remake the graph from scratch.
However, I am fairly sure that the graph is drawn in the same order that the nodes were added to the graph object. I have searched for a way of changing the order that the nodes are stored within the graph object, but given directional graphs actually have an order, my searches always end up with answers showing me how to reverse the direction of a graph.
So, is there a way to dictate the order in which nodes are drawn, or alternatively, change the order that nodes are stored inside some graph object.
Potentially worthy of a second question, but the edges are also blocked out by the large number of nodes. Is there a way to draw the edges above the nodes behind them?
Piggybacking off Paul Brodersen's answer, if you want different nodes to be in the foreground and background, I think you should do the following:
For all nodes that belong in the same layer, draw the subgraph corresponding to the nodes, and set the , as follows:
pos = {...} # some dictionary of node positions, required for the function below
H = G.subgraph(nbunch)
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(zorder)
Do this for every group of nodes that belong in the same level. It's tedious, but it will do the trick. Here is a toy example that I created based on looking up this question as part of my own research
import matplotlib as mpl
mpl.use('agg')
import pylab
import networkx as nx
G = nx.Graph()
G.add_path([1, 2, 3, 4])
pos = {1 : (0, 0), 2 : (0.5, 0), 3 : (1, 0), 4 : (1.5, 0)}
for node in G.nodes():
H = G.subgraph([node])
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(node)
pylab.plot([0, 2], [0, 0], zorder=2.5)
pylab.savefig('nodes_zorder.pdf', format='pdf')
pylab.close()
This makes a graph, and then puts the each node at a successively higher level going from left to right, so the leftmost node is farthest in the background and the rightmost node is farthest in the foreground. It then draws a straight line whose zorder is 2. As a result, it comes in front of the two left nodes, and behind the two right nodes. Here is the result.
draw is a wrapper around draw_networkx_nodes and draw_networkx_edges.
Unlike draw, the two functions return their respective artists ( PathCollection and LineCollection, IIRC). These are your standard matplotlib artists, and as as such their relative draw order can be controlled via their zorder attribute.

Categories