diff --git a/docs/public/images/graph_clean.png b/docs/public/images/graph_clean.png index 18d14a12..a89a51cf 100644 Binary files a/docs/public/images/graph_clean.png and b/docs/public/images/graph_clean.png differ diff --git a/pyproject.toml b/pyproject.toml index 8a4cc246..edd7689f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cityseer" -version = '4.16.25' +version = '4.16.26' description = "Computational tools for network-based pedestrian-scale urban analysis" readme = "README.md" requires-python = ">=3.10, <3.14" diff --git a/pysrc/cityseer/tools/graphs.py b/pysrc/cityseer/tools/graphs.py index 652c6719..c8bfcb9d 100644 --- a/pysrc/cityseer/tools/graphs.py +++ b/pysrc/cityseer/tools/graphs.py @@ -163,7 +163,8 @@ def nx_remove_filler_nodes(nx_multigraph: MultiGraph) -> MultiGraph: agg_geom: ListCoordsType = [] edge_info = util.EdgeInfo() while True: - edge_data: EdgeData = nx_multigraph[trailing_nd][next_link_nd][0] + # cast to list and take first in cases where key at index 0 may have been deleted + edge_data: EdgeData = list(nx_multigraph[trailing_nd][next_link_nd].values())[0] edge_info.gather_edge_info(edge_data) # aggregate the geom try: @@ -263,21 +264,7 @@ def nx_remove_dangling_nodes( f"specified by the remove_disconnected parameter, which is currently set to: {remove_disconnected}. " "Decrease the remove_disconnected parameter or set to zero to retain graph components." ) - # finds connected components - this behaviour changed with networkx v2.4 - connected_components: list[list[NodeKey]] = list(nx.algorithms.components.connected_components(nx_multigraph)) - # keep connected components greater than remove_disconnected param - large_components = [component for component in connected_components if len(component) >= remove_disconnected] - large_subgraphs = [nx.MultiGraph(nx_multigraph.subgraph(component)) for component in large_components] - if not large_subgraphs: - logger.warning( - f"An empty graph will be returned because all graph components had fewer than {remove_disconnected} nodes. " - "Decrease the remove_disconnected parameter or set to zero to retain graph components." - ) - # make a copy of the graph using the largest component - g_multi_copy = nx.MultiGraph() - for subgraph in large_subgraphs: - g_multi_copy.add_nodes_from(subgraph.nodes(data=True)) - g_multi_copy.add_edges_from(subgraph.edges(data=True)) + g_multi_copy = nx_multigraph.copy() # remove danglers if despine > 0: @@ -285,9 +272,10 @@ def nx_remove_dangling_nodes( nd_key: NodeKey for nd_key in tqdm(g_multi_copy.nodes(data=False), disable=config.QUIET_MODE): if nx.degree(g_multi_copy, nd_key) == 1: - # only a single neighbour, so index-in directly and update at key = 0 + # only a single neighbour, so index-in directly and update at first neighbour nb_nd_key: NodeKey = list(nx.neighbors(g_multi_copy, nd_key))[0] - edge_data = g_multi_copy[nd_key][nb_nd_key][0] + # cast to list and take first in cases where key at index 0 may have been deleted + edge_data = list(g_multi_copy[nd_key][nb_nd_key].values())[0] if ( edge_data["geom"].length <= despine or ("is_tunnel" in edge_data and edge_data["is_tunnel"] is True) @@ -296,8 +284,26 @@ def nx_remove_dangling_nodes( remove_nodes.append(nd_key) g_multi_copy.remove_nodes_from(remove_nodes) + # clean up nodes at ex-dangler intersections g_multi_copy = nx_remove_filler_nodes(g_multi_copy) + # finds connected components - this behaviour changed with networkx v2.4 + # do this after to prevent creation of new isolated components after dropping tunnels + connected_components: list[list[NodeKey]] = list(nx.algorithms.components.connected_components(g_multi_copy)) + # keep connected components greater than remove_disconnected param + large_components = [component for component in connected_components if len(component) >= remove_disconnected] + large_subgraphs = [nx.MultiGraph(g_multi_copy.subgraph(component)) for component in large_components] + if not large_subgraphs: + logger.warning( + f"An empty graph will be returned because all graph components had fewer than {remove_disconnected} nodes. " + "Decrease the remove_disconnected parameter or set to zero to retain graph components." + ) + # make a copy of the graph using the largest component + g_multi_copy = nx.MultiGraph() + for subgraph in large_subgraphs: + g_multi_copy.add_nodes_from(subgraph.nodes(data=True)) + g_multi_copy.add_edges_from(subgraph.edges(data=True)) + return g_multi_copy @@ -654,7 +660,19 @@ def _squash_adjacent( # find highest priority OSM highway tag if prioritise_by_hwy_tag: prioritise_tag = None - for osm_hwy_tag in ["motorway", "trunk", "primary", "secondary", "tertiary", "residential"]: + for osm_hwy_tag in [ + "motorway", + "motorway_link", + "trunk", + "trunk_link", + "primary", + "primary_link", + "secondary", + "secondary_link", + "tertiary", + "tertiary_link", + "residential", + ]: for nd_key in node_group: nb_hwy_tags = _gather_nb_tags(nx_multigraph, nd_key, "highways") if osm_hwy_tag in nb_hwy_tags: diff --git a/test.gpkg b/test.gpkg new file mode 100644 index 00000000..556a9532 Binary files /dev/null and b/test.gpkg differ