Reputation: 231
I have dataset from which I have constructed a NetworkX compatible graph. A shapefile has been converted to dictionaries for nodes and edges, which has then been converted to a GeoDataFrame
. From there on, I have used ox.graph_from_gdfs()
to create a functioning graph. The edge GeoDataFrame
looks something like this (first row, simplified):
| id | ref | name | speedlim | length| geometry | u | v | key
1193,2716,0 | 11452 | ref1 | name1 | 50 | 15 | LINESTRING (10.5 60.4, 10.5 60.4) | 1193 | 2716| 0
while the node GeoDataFrame
looks like this:
| x | y | id | geometry
111604 | 10.5 | 60.4 | 11604 | POINT (10.5 60.4)
Converting these to MultiDiGraph
returns no errors:
G = ox.graph_from_gdfs(gdf_nodes, gdf_edges)
Same data is also returned when converting back from graph to gdfs.
However, when simplifying G, the following error is raised:
G = ox.simplify_graph(G)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-18-e400610fe7d3> in <module>
----> 1 F = ox.simplify_graph(G)
~\anaconda3\envs\ox\lib\site-packages\osmnx\simplification.py in simplify_graph(G, strict, remove_rings)
276 for key in edge_attributes:
277 # don't touch the length attribute, we'll sum it at the end
--> 278 if len(set(edge_attributes[key])) == 1 and not key == "length":
279 # if there's only 1 unique value in this attribute list,
280 # consolidate it to the single value (the zero-th)
**TypeError: unhashable type: 'LineString'**
My guess would be that parts of the data in gdf_nodes
and gdf_edges
are not in the correct format, or that something is missing. However, I can't figure out what. I have not encountered any other errors with OSMnx apart from when using this function.
EDIT 1:
Here is a simple code to reproduce the error
import geopandas as gpd
import osmnx as ox
import networkx as nx
from shapely.geometry import Point, LineString
# Sample dictionary containing edge data (copy from first elements in dataset)
edges_test = {
(111603,111604,0) : {"id": 11452, "ref":"Mohagavegen", "name":"Mohagavegen", "speedlim":50, "length":15.1, "geometry":LineString([(10.55351,60.40720), (10.55375,60.40714)]), "u":111603, "v":111604, "key":0},
(111604,111605,0) : {"id": 11453, "ref":"Mohagavegen", "name":"Mohagavegen", "speedlim":50, "length":120.8, "geometry":LineString([Point(10.553752594 ,60.407140812), Point(10.554987804,60.406802271), Point(10.555623630,60.406579470)]), "u":111604, "v":111605, "key":0},
(111605,111606,0) : {"id": 11454, "ref":"Mohagavegen", "name":"Mohagavegen", "speedlim":50, "length":14.2, "geometry":LineString([Point(10.55562 ,60.40658), Point(10.55584 ,60.40651)]), "u":111605, "v":111606, "key":0}
}
# Sample dictionary containing node data (copy from first elements in dataset)
nodes_test = {
11603: {"x":10.5538, "y":60.4071, "id":111603, "geometry":Point((10.55375,60.40714))},
11604: {"x":10.5538, "y":60.4071, "id":111604, "geometry":Point((10.55375,60.40714))},
11605: {"x":10.5556, "y":60.4066, "id":111605, "geometry":Point((10.5556,60.4066))},
11606: {"x":10.5558, "y":60.4065, "id":111606, "geometry":Point((10.5558,60.4065))}
}
# Convert edges into geodataframe
gdf_edges = gpd.GeoDataFrame(edges_test, crs = crs).T
gdf_edges = gpd.GeoDataFrame(
edges_df, geometry=gdf_edges['geometry'])
# Convert nodes into geodataframe
gdf_nodes = gpd.GeoDataFrame(nodes_test, crs = crs).T
gdf_nodes = gpd.GeoDataFrame(
nodes_df, geometry=gdf_nodes['geometry'])
# Build graph from geodataframes
F = ox.graph_from_gdfs(gdf_nodes, gdf_edges)
# Plotting will show that there is one intersectial node present
# ox.plot_graph(F)
# Simplify graph
F = ox.simplify_graph(F)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-113-f81732e4921a> in <module>
41
42 # Simplify graph
---> 43 F = ox.simplify_graph(F)
~\anaconda3\envs\ox\lib\site-packages\osmnx\simplification.py in simplify_graph(G, strict, remove_rings)
276 for key in edge_attributes:
277 # don't touch the length attribute, we'll sum it at the end
--> 278 if len(set(edge_attributes[key])) == 1 and not key == "length":
279 # if there's only 1 unique value in this attribute list,
280 # consolidate it to the single value (the zero-th)
TypeError: unhashable type: 'LineString'
I suspect there are some duplicated nodes with different IDs (see x,y for 111603 and 111604). Maybe this could be the issue?
Upvotes: 2
Views: 808
Reputation: 309
I had the same issue. If one follows the solution provided here (dropping the geometry points) the graph gets deformed. So I fix that by modifying the original function 'simplify_graph()'.
def simplify_graph_modified(G, strict=True, remove_rings=True):
# define edge segment attributes to sum upon edge simplification
attrs_to_sum = {"length", "travel_time"}
# make a copy to not mutate original graph object caller passed in
G = G.copy()
initial_node_count = len(G)
initial_edge_count = len(G.edges)
all_nodes_to_remove = []
all_edges_to_add = []
for path in _get_paths_to_simplify(G, strict=strict):
path_attributes = dict()
for u, v in zip(path[:-1], path[1:]):
edge_count = G.number_of_edges(u, v)
if edge_count != 1:
utils.log(f"Found {edge_count} edges between {u} and {v} when simplifying")
edge_data = G.edges[u, v, 0]
edge_data['geometry'] = list(edge_data['geometry'].coords) # -> new code line
for attr in edge_data:
if attr in path_attributes:
path_attributes[attr].append(edge_data[attr])
else:
path_attributes[attr] = [edge_data[attr]]
#list of lists to one list # -> new line
path_attributes['geometry'] = sum(path_attributes['geometry'], [])
# consolidate the path's edge segments' attribute values
for attr in path_attributes:
if attr in attrs_to_sum:
path_attributes[attr] = sum(path_attributes[attr])
elif attr == 'geometry': # -> new code line
path_attributes[attr] = LineString([Point(node) for node in path_attributes[attr]])
elif len(set(path_attributes[attr])) == 1:
path_attributes[attr] = path_attributes[attr][0]
else:
path_attributes[attr] = list(set(path_attributes[attr]))
# construct the new consolidated edge's geometry for this path
# -> not required anymore
#path_attributes["geometry"] = LineString(
# [Point((G.nodes[node]["x"], G.nodes[node]["y"])) for node in path])
# add the nodes and edge 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": path_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))
if remove_rings:
# remove any connected components that form a self-contained ring
# without any endpoints
wccs = nx.weakly_connected_components(G)
nodes_in_rings = set()
for wcc in wccs:
if not any(_is_endpoint(G, n) for n in wcc):
nodes_in_rings.update(wcc)
G.remove_nodes_from(nodes_in_rings)
return G
Upvotes: 0
Reputation: 231
As pointed out by Obeq, the solution was to remove the attribute containing Linestring.
Following the solution from https://stackoverflow.com/questions/50314296/pythonic-way-to-delete-edge-attributes
:
att_list = ['geometry']
for n1, n2, d in G.edges(data=True):
for att in att_list:
d.pop(att, None)
# Simplify after removing attribute
G = ox.simplify_graph(G)
Upvotes: 1