in libs/apls/osmnx_funcs.py [0:0]
def simplify_graph(G, strict=True, verbose=False):
"""
Simplify a graph's topology by removing all nodes that are not intersections
or dead-ends.
Create an edge directly between the end points that encapsulate them,
but retain the geometry of the original edges, saved as attribute in new
edge.
Parameters
----------
G : networkx multidigraph
strict : bool
if False, allow nodes to be end points even if they fail all other rules
but have edges with different OSM IDs
Returns
-------
networkx multidigraph
"""
if is_simplified(G):
raise Exception('This graph has already been simplified, cannot simplify it again.')
if verbose:
print('Begin topologically simplifying the graph...')
G = G.copy()
initial_node_count = len(list(G.nodes()))
initial_edge_count = len(list(G.edges()))
all_nodes_to_remove = []
all_edges_to_add = []
# construct a list of all the paths that need to be simplified
paths = get_paths_to_simplify(G, strict=strict)
start_time = time.time()
for path in paths:
# add the interstitial edges we're removing to a list so we can retain
# their spatial geometry
edge_attributes = {}
for u, v in zip(path[:-1], path[1:]):
# there shouldn't be multiple edges between interstitial nodes
if not G.number_of_edges(u, v) == 1:
if verbose:
print('Multiple edges between "{}" and "{}" found when simplifying'.format(u, v), level=lg.WARNING)
# the only element in this list as long as above check is True
# (MultiGraphs use keys (the 0 here), indexed with ints from 0 and
# up)
# print(G.edges([u,v]))
try:
edge = G.edges[u, v, 0]
except:
edge = G.edges[u, v, "0"]
for key in edge:
if key in edge_attributes:
# if this key already exists in the dict, append it to the
# value list
edge_attributes[key].append(edge[key])
else:
# if this key doesn't already exist, set the value to a list
# containing the one value
edge_attributes[key] = [edge[key]]
for key in edge_attributes:
# print("key", key, "edge_attributes:", edge_attributes)
# print(" ", edge_attributes[key])
# don't touch the length attribute, we'll sum it at the end
if len(set(edge_attributes[key])) == 1 and not key == 'length':
# if there's only 1 unique value in this attribute list,
# consolidate it to the single value (the zero-th)
edge_attributes[key] = edge_attributes[key][0]
elif not key == 'length':
# otherwise, if there are multiple values, keep one of each value
edge_attributes[key] = list(set(edge_attributes[key]))
# construct the geometry and sum the lengths of the segments
edge_attributes['geometry'] = LineString([Point((G.nodes[node]['x'], G.nodes[node]['y'])) for node in path])
edge_attributes['length'] = sum(edge_attributes['length'])
# add the nodes and edges to their lists for processing at the end
all_nodes_to_remove.extend(path[1:-1])
all_edges_to_add.append({'origin':path[0],
'destination':path[-1],
'attr_dict':edge_attributes})
# for each edge to add in the list we assembled, create a new edge between
# the origin and destination
for edge in all_edges_to_add:
G.add_edge(edge['origin'], edge['destination'], **edge['attr_dict'])
# finally remove all the interstitial nodes between the new edges
G.remove_nodes_from(set(all_nodes_to_remove))
G.graph['simplified'] = True
msg = 'Simplified graph (from {:,} to {:,} nodes and from {:,} to {:,} edges) in {:,.2f} seconds'
if verbose:
print(msg.format(initial_node_count, len(list(G.nodes())), initial_edge_count, len(list(G.edges())), time.time()-start_time))
return G