【问题标题】:TypeError: unhashable type: 'LineString' when using ox.simplify_graph()TypeError: unhashable type: 'LineString' 当使用 ox.simplify_graph()
【发布时间】:2021-04-10 14:38:32
【问题描述】:

我有一个数据集,我从中构建了一个 NetworkX 兼容图。 shapefile 已转换为节点和边的字典,然后已转换为GeoDataFrame。从那以后,我使用ox.graph_from_gdfs() 创建了一个功能图。边缘GeoDataFrame 看起来像这样(第一行,简化):

            | 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

而节点GeoDataFrame 看起来像这样:

       | x    | y     | id    | geometry     
111604 | 10.5 | 60.4  | 11604 | POINT (10.5 60.4)

将这些转换为 MultiDiGraph 不会返回错误:

G = ox.graph_from_gdfs(gdf_nodes, gdf_edges)

从图形转换回 gdfs 时也会返回相同的数据。

但是,在简化 G 时,会出现以下错误:

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'**

我的猜测是gdf_nodesgdf_edges 中的部分数据格式不正确,或者缺少某些内容。但是,我无法弄清楚是什么。除了使用此功能时,我没有遇到任何其他 OSMnx 错误。


编辑 1:

这是一个重现错误的简单代码

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'


我怀疑存在一些具有不同 ID 的重复节点(请参阅 x,y 表示 111603 和 111604)。也许这可能是问题所在?

【问题讨论】:

  • 请提供完整的最小可重现代码sn-p。
  • 请注意,您只能简化图一次。后续尝试将引发错误。如果没有可重现的代码 sn-p,我最好的猜测就是这就是这里发生的事情。此外,最好将 graph_attrs 参数传递给 graph_from_gdfs 函数:示例参见 stackoverflow.com/a/65501746/7321942
  • 我添加了重现错误的代码;请参阅原始帖子中的编辑 1。图表没有被简化,因为它不是用 OSMnx 检索的,而是从本地数据集构建的。
  • 问题是简化尝试为简化的边创建一个新属性。为此,它需要散列所有值或原始 gdf ​​中的列。由于 LineString 不可散列(github.com/Toblerity/Shapely/issues/209),因此不起作用。如果您需要简化图形,则需要删除几何列。
  • 是的!那解决了它。删除几何属性后,该函数完美运行

标签: python gis networkx osmnx


【解决方案1】:

正如 Obeq 所指出的,解决方案是删除包含 Linestring 的属性。

遵循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)

【讨论】:

  • 该解决方案有效。但是,新图中完全忽略了线的拓扑结构,导致变形
  • @Hekmat,是的,边缘的拓扑属性被丢弃。此解决方案仅在您最感兴趣的是顶点之间的连接而不是边拓扑属性的情况下才可行。
  • 我建议对原始函数进行修改,以便保留边缘的几何形状。该解决方案作为问题发布在github.com/gboeing/osmnx/issues/744
【解决方案2】:

我有同样的问题。如果遵循此处提供的解决方案(删除几何点),图形就会变形。所以我通过修改原始函数'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

【讨论】:

    猜你喜欢
    • 2015-02-11
    • 1970-01-01
    • 2021-08-31
    • 2019-10-11
    • 2020-03-27
    • 2020-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多