From fea224c047d3008d897a41bb9ea1c5f4628b14e5 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 21 Aug 2024 14:29:25 +0200 Subject: [PATCH 01/33] test: graph updated --- .../test_network_graph_simplificator.py | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index d1b496d67..7cfd28145 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -1,11 +1,10 @@ import math -import random from typing import Callable, Iterator import networkx as nx import numpy as np import pytest -from shapely.geometry import LineString, Point +from shapely.geometry import LineString from ra2ce.network import add_missing_geoms_graph from ra2ce.network.network_simplification.network_graph_simplificator import ( @@ -132,7 +131,7 @@ def _get_network_simplification_with_attribute_exclusion( def _get_nx_digraph_factory(self) -> Iterator[Callable[[], nx.MultiDiGraph]]: def create_nx_multidigraph(): _nx_digraph = nx.MultiDiGraph() - for i in range(1, 16): + for i in range(1, 19): _nx_digraph.add_node(i, x=i, y=i * 10) _nx_digraph.add_edge(1, 2, a=np.nan) @@ -152,6 +151,10 @@ def create_nx_multidigraph(): _nx_digraph.add_edge(13, 14, a="yes") _nx_digraph.add_edge(14, 15, a="yes") _nx_digraph.add_edge(15, 11, a="yes") + _nx_digraph.add_edge(1, 16, a="yes") + _nx_digraph.add_edge(16, 1, a="yes") + _nx_digraph.add_edge(16, 17, a="yes") + _nx_digraph.add_edge(16, 18, a="yes") _nx_digraph = add_missing_geoms_graph(_nx_digraph, "geometry") _nx_digraph.graph["crs"] = "EPSG:4326" @@ -167,7 +170,8 @@ def _get_expected_result_graph_fixture( ) -> nx.MultiGraph: _nx_digraph = nx_digraph_factory() _result_digraph = nx.MultiGraph() - node_ids_degrees = {2: 1, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2} + # node_ids_degrees = {2: 1, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2} + node_ids_degrees = {2: 1, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2, 16: 4, 17: 1, 18: 1} for node_id, degree in node_ids_degrees.items(): node_data = _nx_digraph.nodes[node_id] node_data["id"] = node_id @@ -190,6 +194,21 @@ def _get_expected_result_graph_fixture( ), ) + _result_digraph.add_edge( + 2, + 16, + a="None", + from_node=2, + to_node=16, + geometry=LineString( + [ + _nx_digraph.nodes[2]["geometry"], + _nx_digraph.nodes[1]["geometry"], + _nx_digraph.nodes[16]["geometry"], + ] + ), + ) + _result_digraph.add_edge( 4, 7.0, @@ -276,6 +295,50 @@ def _get_expected_result_graph_fixture( ), ) + _result_digraph.add_edge( + 16, + 4.0, + a="None", + from_node=16, + to_node=4, + geometry=LineString( + [ + _nx_digraph.nodes[16]["geometry"], + _nx_digraph.nodes[1]["geometry"], + _nx_digraph.nodes[2]["geometry"], + _nx_digraph.nodes[3]["geometry"], + _nx_digraph.nodes[4]["geometry"], + ] + ), + ) + + _result_digraph.add_edge( + 16, + 17, + a="None", + from_node=16, + to_node=17, + geometry=LineString( + [ + _nx_digraph.nodes[16]["geometry"], + _nx_digraph.nodes[17]["geometry"], + ] + ), + ) + + _result_digraph.add_edge( + 16, + 18, + a="None", + from_node=16, + to_node=18, + geometry=LineString( + [ + _nx_digraph.nodes[16]["geometry"], + _nx_digraph.nodes[18]["geometry"], + ] + ), + ) _result_digraph.graph["crs"] = "EPSG:4326" snkit_network = NxToSnkitNetworkConverter( From fd43031d7f79f73e80590c2f899fab2b7989ba07 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 21 Aug 2024 14:29:49 +0200 Subject: [PATCH 02/33] feat: deg_4 node filtering added --- .../snkit_network_merge_wrapper.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 3923a8324..b2501869f 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -26,6 +26,8 @@ from snkit.network import Network as SnkitNetwork from tqdm import tqdm +from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper + NxGraph = nx.Graph | nx.MultiGraph | nx.MultiDiGraph @@ -94,6 +96,22 @@ def _get_merged_edges( updated_edges_gdf = updated_edges_gdf.drop(columns=["id"]) return updated_edges_gdf + def _filter_degree_4(degree_4s: set) -> set: + degree_4_filtered = set() + snkit_network_wrapper = SnkitNetworkWrapper( + snkit_network=snkit_network, + ) + networkx_graph = snkit_network_wrapper.to_networkx() + for degree_4_node_id in degree_4s: + # Get the predecessors (antecedents) and successors (precedents) + predecessors = list(networkx_graph.predecessors(degree_4_node_id)) + successors = list(networkx_graph.successors(degree_4_node_id)) + + # Check if there is exactly one predecessor and one successor: 1->2, 2->3, 2->1, 3->2 => filters on 2 + if len(predecessors) == 1 and len(successors) == 1: + degree_4_filtered.add(degree_4_node_id) + return degree_4_filtered + def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: # Convert edges to an adjacency list using vectorized operations edge_dict = defaultdict(set) @@ -137,6 +155,12 @@ def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) degree_2_set = set(degree_2) + + degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) + degree_4_set = set(degree_4) + degree_4 = _filter_degree_4(degree_4_set) + + # ToDo: Add the filtered degree_2 and 4 sets to get the edge paths edge_paths = _get_edge_paths(degree_2_set, snkit_network) edge_ids_to_update = _get_edge_ids_to_update(edge_paths) From 0d8faaaaa2c72053a7dd61a35ef5c25302332800 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 28 Aug 2024 11:28:16 +0200 Subject: [PATCH 03/33] chore: update the directionality of a few relevant functions --- .../snkit_network_merge_wrapper.py | 38 ++++++++++++++----- .../snkit_to_nx_network_converter.py | 6 +-- .../test_network_graph_simplificator.py | 14 +++---- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index b2501869f..e9aea62ec 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -21,13 +21,12 @@ import geopandas as gpd import networkx as nx import pandas as pd +from networkx import MultiGraph from shapely.geometry import LineString, MultiLineString, MultiPoint, Point from shapely.ops import linemerge from snkit.network import Network as SnkitNetwork from tqdm import tqdm -from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper - NxGraph = nx.Graph | nx.MultiGraph | nx.MultiDiGraph @@ -97,6 +96,8 @@ def _get_merged_edges( return updated_edges_gdf def _filter_degree_4(degree_4s: set) -> set: + from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper + degree_4_filtered = set() snkit_network_wrapper = SnkitNetworkWrapper( snkit_network=snkit_network, @@ -108,11 +109,13 @@ def _filter_degree_4(degree_4s: set) -> set: successors = list(networkx_graph.successors(degree_4_node_id)) # Check if there is exactly one predecessor and one successor: 1->2, 2->3, 2->1, 3->2 => filters on 2 - if len(predecessors) == 1 and len(successors) == 1: + if sorted(predecessors) == sorted(successors): degree_4_filtered.add(degree_4_node_id) return degree_4_filtered def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: + def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set: + return set(gdf[from_col]) ^ set(gdf[to_col]) # Convert edges to an adjacency list using vectorized operations edge_dict = defaultdict(set) from_ids = snkit_network.edges["from_id"].values @@ -125,7 +128,9 @@ def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: edge_paths = [] while node_set: + intermediates = set() popped_node = node_set.pop() + intermediates.add(popped_node) node_path = {popped_node} candidates = {popped_node} while candidates: @@ -135,17 +140,28 @@ def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: for match in matches: if match in node_set: candidates.add(match) + intermediates.add(match) node_path.add(match) node_set.remove(match) else: node_path.add(match) if len(node_path) >= 2: - edge_paths.append( - snkit_network.edges.loc[ - (snkit_network.edges.from_id.isin(node_path)) - & (snkit_network.edges.to_id.isin(node_path)) + intermediate_edges = snkit_network.edges[ + snkit_network.edges.from_id.isin(intermediates) & + snkit_network.edges.to_id.isin(intermediates) ] - ) + + unique_from_id = set(intermediate_edges["from_id"]) - set(intermediate_edges["to_id"]) + entering_edge = snkit_network.edges[snkit_network.edges.to_id.isin(unique_from_id)] + + unique_to_id = set(intermediate_edges["to_id"]) - set(intermediate_edges["from_id"]) + leaving_edge = snkit_network.edges[snkit_network.edges.from_id.isin(unique_to_id)] + + # Add the unique edges to the intermediate edges + edge_paths_gdf = intermediate_edges.append([entering_edge, leaving_edge]) + + # Append the result to edge_paths + edge_paths = edge_paths.append(edge_paths_gdf) return edge_paths if "degree" not in snkit_network.nodes.columns: @@ -158,10 +174,12 @@ def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) degree_4_set = set(degree_4) - degree_4 = _filter_degree_4(degree_4_set) + filtered_degree_4_set = _filter_degree_4(degree_4_set) + + nodes_of_interest = degree_2_set | filtered_degree_4_set # ToDo: Add the filtered degree_2 and 4 sets to get the edge paths - edge_paths = _get_edge_paths(degree_2_set, snkit_network) + edge_paths = _get_edge_paths(sorted(nodes_of_interest), snkit_network) edge_ids_to_update = _get_edge_ids_to_update(edge_paths) edges_to_keep = snkit_network.edges[ diff --git a/ra2ce/network/network_simplification/snkit_to_nx_network_converter.py b/ra2ce/network/network_simplification/snkit_to_nx_network_converter.py index 15f9128bd..7b3e0af21 100644 --- a/ra2ce/network/network_simplification/snkit_to_nx_network_converter.py +++ b/ra2ce/network/network_simplification/snkit_to_nx_network_converter.py @@ -33,7 +33,7 @@ class SnkitToNxNetworkConverter: edge_from_id_column_name: str = "from_id" edge_to_id_column_name: str = "to_id" - def convert(self) -> nx.MultiGraph: + def convert(self) -> nx.MultiDiGraph: """ Converts the given `snkit.network.Network` into a matching `networkx.MultiGraph`. @@ -42,10 +42,10 @@ def convert(self) -> nx.MultiGraph: snkit_network (SnkitNetwork): The snkit network to convert. Returns: - `networkx.MultiGraph`: The converted graph. + `networkx.MultiDiGraph`: The converted graph. """ # Define new graph - _nx_graph = nx.MultiGraph() + _nx_graph = nx.MultiDiGraph() _crs = self.snkit_network.edges.crs # Add nodes to the graph diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 7cfd28145..27a999473 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -151,10 +151,10 @@ def create_nx_multidigraph(): _nx_digraph.add_edge(13, 14, a="yes") _nx_digraph.add_edge(14, 15, a="yes") _nx_digraph.add_edge(15, 11, a="yes") - _nx_digraph.add_edge(1, 16, a="yes") - _nx_digraph.add_edge(16, 1, a="yes") - _nx_digraph.add_edge(16, 17, a="yes") - _nx_digraph.add_edge(16, 18, a="yes") + _nx_digraph.add_edge(1, 16, a=np.nan) + _nx_digraph.add_edge(16, 1, a=np.nan) + _nx_digraph.add_edge(16, 17, a=np.nan) + _nx_digraph.add_edge(16, 18, a=np.nan) _nx_digraph = add_missing_geoms_graph(_nx_digraph, "geometry") _nx_digraph.graph["crs"] = "EPSG:4326" @@ -297,17 +297,15 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, - 4.0, + 2.0, a="None", from_node=16, - to_node=4, + to_node=2, geometry=LineString( [ _nx_digraph.nodes[16]["geometry"], _nx_digraph.nodes[1]["geometry"], _nx_digraph.nodes[2]["geometry"], - _nx_digraph.nodes[3]["geometry"], - _nx_digraph.nodes[4]["geometry"], ] ), ) From 8088794c502531c19283ad636c3d760419e786ba Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 28 Aug 2024 17:04:48 +0200 Subject: [PATCH 04/33] feat: degree-4 path finding completed --- .../snkit_network_merge_wrapper.py | 94 ++++++++++++++----- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index e9aea62ec..67528e615 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -95,6 +95,24 @@ def _get_merged_edges( updated_edges_gdf = updated_edges_gdf.drop(columns=["id"]) return updated_edges_gdf + def _filter_degree_2(degree_2s: set) -> set: + from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper + + degree_2_filtered = set() + snkit_network_wrapper = SnkitNetworkWrapper( + snkit_network=snkit_network, + ) + networkx_graph = snkit_network_wrapper.to_networkx() + for degree_2_node_id in degree_2s: + # Get the predecessors (antecedents) and successors (precedents) + predecessors = list(networkx_graph.predecessors(degree_2_node_id)) + successors = list(networkx_graph.successors(degree_2_node_id)) + + # Check if there is exactly one predecessor and one successor: 1->2, 2->3, 2->1, 3->2 => filters on 2 + if len(predecessors) == len(successors) == 1: + degree_2_filtered.add(degree_2_node_id) + return degree_2_filtered + def _filter_degree_4(degree_4s: set) -> set: from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper @@ -116,6 +134,47 @@ def _filter_degree_4(degree_4s: set) -> set: def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set: return set(gdf[from_col]) ^ set(gdf[to_col]) + + def _find_and_append_degree_4_paths( + _edge_paths: list + ) -> None: + def retrieve_edge(node1: int|float, node2: int|float) -> gpd.GeoDataFrame: + """Retrieve the edge from snkit_network.edges GeoDataFrame between two nodes.""" + edge = snkit_network.edges[ + (snkit_network.edges['from_id'] == node1) & + (snkit_network.edges['to_id'] == node2) + ] + return edge if not edge.empty else None + + boundary_nodes = node_path.difference(filtered_degree_4_set) + intermediates_list = list(intermediates) + if len(boundary_nodes) == 2: + from_node, to_node = boundary_nodes + + # Forward path: from_node -> intermediates -> to_node + forward_path = [ + retrieve_edge(intermediates_list[i], intermediates_list[i + 1]) + for i in range(len(intermediates) - 1) + ] + forward_path.insert(0, retrieve_edge(from_node, intermediates_list[0])) + forward_path.append(retrieve_edge(intermediates_list[-1], to_node)) + + if all(edge is not None for edge in forward_path): + forward_gdf = pd.concat(forward_path) # Combine edges into a single GeoDataFrame + _edge_paths.append(forward_gdf) + + # Backward path: to_node -> intermediates (reversed) -> from_node + backward_path = [ + retrieve_edge(intermediates_list[i + 1], intermediates_list[i]) + for i in range(len(intermediates) - 1) + ] + backward_path.insert(0, retrieve_edge(to_node, intermediates_list[-1])) + backward_path.append(retrieve_edge(intermediates_list[0], from_node)) + + if all(edge is not None for edge in backward_path): + backward_gdf = pd.concat(backward_path) # Combine edges into a single GeoDataFrame + _edge_paths.append(backward_gdf) + # Convert edges to an adjacency list using vectorized operations edge_dict = defaultdict(set) from_ids = snkit_network.edges["from_id"].values @@ -125,7 +184,7 @@ def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set edge_dict[from_id].add(to_id) edge_dict[to_id].add(from_id) - edge_paths = [] + _edge_paths = [] while node_set: intermediates = set() @@ -138,31 +197,24 @@ def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set matches = edge_dict[popped_cand] matches = matches - node_path for match in matches: + intermediates.add(popped_cand) if match in node_set: candidates.add(match) - intermediates.add(match) node_path.add(match) node_set.remove(match) else: node_path.add(match) if len(node_path) >= 2: - intermediate_edges = snkit_network.edges[ - snkit_network.edges.from_id.isin(intermediates) & - snkit_network.edges.to_id.isin(intermediates) - ] - - unique_from_id = set(intermediate_edges["from_id"]) - set(intermediate_edges["to_id"]) - entering_edge = snkit_network.edges[snkit_network.edges.to_id.isin(unique_from_id)] - - unique_to_id = set(intermediate_edges["to_id"]) - set(intermediate_edges["from_id"]) - leaving_edge = snkit_network.edges[snkit_network.edges.from_id.isin(unique_to_id)] - - # Add the unique edges to the intermediate edges - edge_paths_gdf = intermediate_edges.append([entering_edge, leaving_edge]) - - # Append the result to edge_paths - edge_paths = edge_paths.append(edge_paths_gdf) - return edge_paths + if any(node_path.intersection(filtered_degree_4_set)): + _find_and_append_degree_4_paths(_edge_paths) + else: + edge_paths_gdf = snkit_network.edges[ + snkit_network.edges.from_id.isin(intermediates) | + snkit_network.edges.to_id.isin(intermediates) + ] + # Append the result to edge_paths + _edge_paths.append(edge_paths_gdf) + return _edge_paths if "degree" not in snkit_network.nodes.columns: snkit_network.nodes["degree"] = snkit_network.nodes[id_col].apply( @@ -171,14 +223,14 @@ def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) degree_2_set = set(degree_2) + filtered_degree_2_set = _filter_degree_2(degree_2_set) degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) degree_4_set = set(degree_4) filtered_degree_4_set = _filter_degree_4(degree_4_set) - nodes_of_interest = degree_2_set | filtered_degree_4_set + nodes_of_interest = filtered_degree_2_set | filtered_degree_4_set - # ToDo: Add the filtered degree_2 and 4 sets to get the edge paths edge_paths = _get_edge_paths(sorted(nodes_of_interest), snkit_network) edge_ids_to_update = _get_edge_ids_to_update(edge_paths) From 8e0794310c1fec30f203355ae908d0ac0a20f405 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 28 Aug 2024 17:40:18 +0200 Subject: [PATCH 05/33] chore: test updated --- .../test_network_graph_simplificator.py | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 27a999473..879c51c91 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -170,8 +170,7 @@ def _get_expected_result_graph_fixture( ) -> nx.MultiGraph: _nx_digraph = nx_digraph_factory() _result_digraph = nx.MultiGraph() - # node_ids_degrees = {2: 1, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2} - node_ids_degrees = {2: 1, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2, 16: 4, 17: 1, 18: 1} + node_ids_degrees = {2: 3, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2, 16: 4, 17: 1, 18: 1} for node_id, degree in node_ids_degrees.items(): node_data = _nx_digraph.nodes[node_id] node_data["id"] = node_id @@ -182,7 +181,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 2, 4.0, - a="None", + a=np.nan, from_node=2, to_node=4, geometry=LineString( @@ -196,8 +195,8 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 2, - 16, - a="None", + 16.0, + a=np.nan, from_node=2, to_node=16, geometry=LineString( @@ -227,7 +226,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 7, 8.0, - a="None", + a=np.nan, from_node=7, to_node=8, geometry=LineString( @@ -239,8 +238,8 @@ def _get_expected_result_graph_fixture( ) _result_digraph.add_edge( 8, - 11.0, - a="None", + 11, + a=np.nan, from_node=8, to_node=11, geometry=LineString( @@ -254,7 +253,7 @@ def _get_expected_result_graph_fixture( ) _result_digraph.add_edge( 8, - 11.0, + 11, a="yes", from_node=8, to_node=11, @@ -270,8 +269,8 @@ def _get_expected_result_graph_fixture( ) _result_digraph.add_edge( 8, - 12.0, - a="None", + 12, + a=np.nan, from_node=8, to_node=12, geometry=LineString( @@ -283,7 +282,7 @@ def _get_expected_result_graph_fixture( ) _result_digraph.add_edge( 11, - 12.0, + 12, a="yes", from_node=11, to_node=12, @@ -298,7 +297,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 2.0, - a="None", + a=np.nan, from_node=16, to_node=2, geometry=LineString( @@ -313,7 +312,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 17, - a="None", + a=np.nan, from_node=16, to_node=17, geometry=LineString( @@ -327,7 +326,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 18, - a="None", + a=np.nan, from_node=16, to_node=18, geometry=LineString( @@ -365,7 +364,5 @@ def test_simplify_graph( assert _graph_simple.nodes(data=True) == expected_result_graph_fixture.nodes( data=True ) - # Compare edges topology - assert set(_graph_simple.edges()) == set(expected_result_graph_fixture.edges()) # Compare edges with attributes assert _detailed_edge_comparison(_graph_simple, expected_result_graph_fixture) From 6c19b6386b6520eeaa81ca097d523e1b57f41a29 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 28 Aug 2024 23:42:00 +0200 Subject: [PATCH 06/33] chore: test updated --- .../test_network_graph_simplificator.py | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 879c51c91..0da811513 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -26,19 +26,27 @@ def _detailed_edge_comparison( graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: def _dicts_comparison( - graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph + _graph1: nx.MultiDiGraph | nx.MultiGraph, _graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: - for u, v, k, data1 in graph1.edges(keys=True, data=True): - data2 = graph2.get_edge_data(u, v, k) - for key1, value1 in data1.items(): - if key1 not in data2: - return False - if isinstance(value1, float) and math.isnan(value1): - if not math.isnan(data2[key1]): - return False - continue - if value1 != data2[key1]: - return False + for u, v, k, data1 in _graph1.edges(keys=True, data=True): + geom1 = data1['geometry'] + geom_found = 0 + + data2_dict = _graph2.get_edge_data(u, v) + for _, data2 in data2_dict.items(): + if data2['geometry'] == geom1: + geom_found = 1 + for key1, value1 in data1.items(): + if key1 not in data2: + return False + if isinstance(value1, float) and math.isnan(value1): + if not math.isnan(data2[key1]): + return False + continue + if value1 != data2[key1]: + return False + if geom_found == 0: + return False return True check_1_2 = _dicts_comparison(graph1, graph2) @@ -134,27 +142,27 @@ def create_nx_multidigraph(): for i in range(1, 19): _nx_digraph.add_node(i, x=i, y=i * 10) - _nx_digraph.add_edge(1, 2, a=np.nan) - _nx_digraph.add_edge(2, 1, a=np.nan) - _nx_digraph.add_edge(2, 3, a=np.nan) - _nx_digraph.add_edge(3, 4, a=np.nan) + _nx_digraph.add_edge(1, 2, a='None') + _nx_digraph.add_edge(2, 1, a='None') + _nx_digraph.add_edge(2, 3, a='None') + _nx_digraph.add_edge(3, 4, a='None') _nx_digraph.add_edge(4, 5, a="yes") _nx_digraph.add_edge(5, 6, a="yes") _nx_digraph.add_edge(6, 7, a="yes") - _nx_digraph.add_edge(7, 8, a=np.nan) - _nx_digraph.add_edge(8, 9, a=np.nan) - _nx_digraph.add_edge(8, 12, a=np.nan) + _nx_digraph.add_edge(7, 8, a='None') + _nx_digraph.add_edge(8, 9, a='None') + _nx_digraph.add_edge(8, 12, a='None') _nx_digraph.add_edge(8, 13, a="yes") - _nx_digraph.add_edge(9, 10, a=np.nan) - _nx_digraph.add_edge(10, 11, a=np.nan) + _nx_digraph.add_edge(9, 10, a='None') + _nx_digraph.add_edge(10, 11, a='None') _nx_digraph.add_edge(11, 12, a="yes") _nx_digraph.add_edge(13, 14, a="yes") _nx_digraph.add_edge(14, 15, a="yes") _nx_digraph.add_edge(15, 11, a="yes") - _nx_digraph.add_edge(1, 16, a=np.nan) - _nx_digraph.add_edge(16, 1, a=np.nan) - _nx_digraph.add_edge(16, 17, a=np.nan) - _nx_digraph.add_edge(16, 18, a=np.nan) + _nx_digraph.add_edge(1, 16, a='None') + _nx_digraph.add_edge(16, 1, a='None') + _nx_digraph.add_edge(16, 17, a='None') + _nx_digraph.add_edge(16, 18, a='None') _nx_digraph = add_missing_geoms_graph(_nx_digraph, "geometry") _nx_digraph.graph["crs"] = "EPSG:4326" @@ -169,7 +177,7 @@ def _get_expected_result_graph_fixture( self, nx_digraph_factory: nx.MultiDiGraph ) -> nx.MultiGraph: _nx_digraph = nx_digraph_factory() - _result_digraph = nx.MultiGraph() + _result_digraph = nx.MultiDiGraph() node_ids_degrees = {2: 3, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2, 16: 4, 17: 1, 18: 1} for node_id, degree in node_ids_degrees.items(): node_data = _nx_digraph.nodes[node_id] @@ -181,7 +189,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 2, 4.0, - a=np.nan, + a='None', from_node=2, to_node=4, geometry=LineString( @@ -196,7 +204,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 2, 16.0, - a=np.nan, + a='None', from_node=2, to_node=16, geometry=LineString( @@ -226,7 +234,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 7, 8.0, - a=np.nan, + a='None', from_node=7, to_node=8, geometry=LineString( @@ -239,7 +247,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 8, 11, - a=np.nan, + a='None', from_node=8, to_node=11, geometry=LineString( @@ -270,7 +278,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 8, 12, - a=np.nan, + a='None', from_node=8, to_node=12, geometry=LineString( @@ -297,7 +305,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 2.0, - a=np.nan, + a='None', from_node=16, to_node=2, geometry=LineString( @@ -312,7 +320,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 17, - a=np.nan, + a='None', from_node=16, to_node=17, geometry=LineString( @@ -326,7 +334,7 @@ def _get_expected_result_graph_fixture( _result_digraph.add_edge( 16, 18, - a=np.nan, + a='None', from_node=16, to_node=18, geometry=LineString( From 9b15a735f95478a3980b88f2940a623076d07e29 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Thu, 29 Aug 2024 11:27:37 +0200 Subject: [PATCH 07/33] chore: forward and backward formation updated --- .../snkit_network_merge_wrapper.py | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 67528e615..b7d02010a 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -132,9 +132,6 @@ def _filter_degree_4(degree_4s: set) -> set: return degree_4_filtered def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: - def unique_ids_from_to(gdf: gpd.GeoDataFrame, from_col: str, to_col: str) -> set: - return set(gdf[from_col]) ^ set(gdf[to_col]) - def _find_and_append_degree_4_paths( _edge_paths: list ) -> None: @@ -145,36 +142,45 @@ def retrieve_edge(node1: int|float, node2: int|float) -> gpd.GeoDataFrame: (snkit_network.edges['to_id'] == node2) ] return edge if not edge.empty else None - - boundary_nodes = node_path.difference(filtered_degree_4_set) - intermediates_list = list(intermediates) + + def construct_path(start_node: int|float, end_node: int|float, intermediates: list): + path = [] + current_node = start_node + _intermediates = intermediates.copy() + + while _intermediates: + for next_node in _intermediates: + edge = retrieve_edge(current_node, next_node) + if edge is not None: + path.append(edge) + _intermediates.remove(next_node) + current_node = next_node + break + + final_edge = retrieve_edge(current_node, end_node) + if final_edge is not None: + path.append(final_edge) + + if len(path) > 0 and all(edge is not None for edge in path): + return pd.concat(path) # Combine edges into a single GeoDataFrame + return None + + boundary_nodes = list(node_path - filtered_degree_4_set) if len(boundary_nodes) == 2: from_node, to_node = boundary_nodes + intermediates = list(node_path & filtered_degree_4_set) - # Forward path: from_node -> intermediates -> to_node - forward_path = [ - retrieve_edge(intermediates_list[i], intermediates_list[i + 1]) - for i in range(len(intermediates) - 1) - ] - forward_path.insert(0, retrieve_edge(from_node, intermediates_list[0])) - forward_path.append(retrieve_edge(intermediates_list[-1], to_node)) - - if all(edge is not None for edge in forward_path): - forward_gdf = pd.concat(forward_path) # Combine edges into a single GeoDataFrame + # Construct and append the forward path + forward_gdf = construct_path(from_node, to_node, intermediates) + if forward_gdf is not None: _edge_paths.append(forward_gdf) - # Backward path: to_node -> intermediates (reversed) -> from_node - backward_path = [ - retrieve_edge(intermediates_list[i + 1], intermediates_list[i]) - for i in range(len(intermediates) - 1) - ] - backward_path.insert(0, retrieve_edge(to_node, intermediates_list[-1])) - backward_path.append(retrieve_edge(intermediates_list[0], from_node)) - - if all(edge is not None for edge in backward_path): - backward_gdf = pd.concat(backward_path) # Combine edges into a single GeoDataFrame + # Construct and append the backward path + backward_gdf = construct_path(to_node, from_node, intermediates) + if backward_gdf is not None: _edge_paths.append(backward_gdf) + # Convert edges to an adjacency list using vectorized operations edge_dict = defaultdict(set) from_ids = snkit_network.edges["from_id"].values From 7f6423c5055f25e5631bc6a87c06655cfed294f3 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 15:29:33 +0200 Subject: [PATCH 08/33] fix: networkx_graph property added to snkit_network_wrapper --- .../snkit_network_merge_wrapper.py | 14 ++------------ .../snkit_network_wrapper.py | 9 +++++++++ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index b7d02010a..8ea3538a1 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -41,6 +41,7 @@ def merge_edges( snkit_network: SnkitNetwork, + networkx_graph: NxGraph, aggregate_func: str | dict, by: str | list, id_col: str, @@ -50,6 +51,7 @@ def merge_edges( Args: snkit_network (SnkitNetwork): network to merge. + networkx_graph (NxGraph): networkx graph to merge aggregate_func (str | dict): Aggregation function to apply. by (str | list): Arguments (column names). id_col (str, optional): Name of the column representing the 'id'. @@ -96,13 +98,7 @@ def _get_merged_edges( return updated_edges_gdf def _filter_degree_2(degree_2s: set) -> set: - from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper - degree_2_filtered = set() - snkit_network_wrapper = SnkitNetworkWrapper( - snkit_network=snkit_network, - ) - networkx_graph = snkit_network_wrapper.to_networkx() for degree_2_node_id in degree_2s: # Get the predecessors (antecedents) and successors (precedents) predecessors = list(networkx_graph.predecessors(degree_2_node_id)) @@ -114,13 +110,7 @@ def _filter_degree_2(degree_2s: set) -> set: return degree_2_filtered def _filter_degree_4(degree_4s: set) -> set: - from ra2ce.network.network_simplification.snkit_network_wrapper import SnkitNetworkWrapper - degree_4_filtered = set() - snkit_network_wrapper = SnkitNetworkWrapper( - snkit_network=snkit_network, - ) - networkx_graph = snkit_network_wrapper.to_networkx() for degree_4_node_id in degree_4s: # Get the predecessors (antecedents) and successors (precedents) predecessors = list(networkx_graph.predecessors(degree_4_node_id)) diff --git a/ra2ce/network/network_simplification/snkit_network_wrapper.py b/ra2ce/network/network_simplification/snkit_network_wrapper.py index 4e9803736..58054dcae 100644 --- a/ra2ce/network/network_simplification/snkit_network_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_wrapper.py @@ -47,6 +47,14 @@ class SnkitNetworkWrapper: edge_from_id_column_name: str = "from_id" edge_to_id_column_name: str = "to_id" + def __init__(self, snkit_network: Network, node_id_column_name: str = "id", + edge_from_id_column_name: str = "from_id", edge_to_id_column_name: str = "to_id") -> None: + self.snkit_network = snkit_network + self.node_id_column_name = node_id_column_name + self.edge_from_id_column_name = edge_from_id_column_name + self.edge_to_id_column_name = edge_to_id_column_name + self.networkx_graph: NxGraph = self.to_networkx() + @classmethod def from_networkx( cls, @@ -91,6 +99,7 @@ def filter_excluded_attributes() -> list[str]: # Overwrite the existing network with the merged edges. self.snkit_network = merge_edges( snkit_network=self.snkit_network, + networkx_graph=self.networkx_graph, aggregate_func=_aggregate_function, by=_attributes_to_exclude, id_col="id", From e2b69920e8841b4b6b77cf427333ba29a25649da Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 15:37:24 +0200 Subject: [PATCH 09/33] doc: comment added for clarification of _filter_degree_2 --- .../network_simplification/snkit_network_merge_wrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 8ea3538a1..0638f988c 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -100,11 +100,12 @@ def _get_merged_edges( def _filter_degree_2(degree_2s: set) -> set: degree_2_filtered = set() for degree_2_node_id in degree_2s: - # Get the predecessors (antecedents) and successors (precedents) + # Get the predecessors (antecedents) and successors (precedents) to make sure for each node with the degree + # of 2 there is only one predecessor and successor. E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 predecessors = list(networkx_graph.predecessors(degree_2_node_id)) successors = list(networkx_graph.successors(degree_2_node_id)) - # Check if there is exactly one predecessor and one successor: 1->2, 2->3, 2->1, 3->2 => filters on 2 + # Check if there is one predecessor and one successor. if len(predecessors) == len(successors) == 1: degree_2_filtered.add(degree_2_node_id) return degree_2_filtered From c345d0538d153c6be719a1a62a92ab6748cd1f2e Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 15:39:57 +0200 Subject: [PATCH 10/33] doc: comment added for clarification of _filter_degree_4 --- .../network_simplification/snkit_network_merge_wrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 0638f988c..264ed62b0 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -113,11 +113,12 @@ def _filter_degree_2(degree_2s: set) -> set: def _filter_degree_4(degree_4s: set) -> set: degree_4_filtered = set() for degree_4_node_id in degree_4s: - # Get the predecessors (antecedents) and successors (precedents) + # Get the predecessors (antecedents) and successors (precedents) for filtering. + # E.g. : 1->2, 2->3, 2->1, 3->2 => filters on 2 predecessors = list(networkx_graph.predecessors(degree_4_node_id)) successors = list(networkx_graph.successors(degree_4_node_id)) - # Check if there is exactly one predecessor and one successor: 1->2, 2->3, 2->1, 3->2 => filters on 2 + # Check if the predecessors and successors are the same nodes if sorted(predecessors) == sorted(successors): degree_4_filtered.add(degree_4_node_id) return degree_4_filtered From c125d6d7f6a4420eb7205d8e98594802f291df88 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 15:54:13 +0200 Subject: [PATCH 11/33] doc: minor --- .../network_simplification/snkit_network_merge_wrapper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 264ed62b0..850c285ef 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -117,7 +117,6 @@ def _filter_degree_4(degree_4s: set) -> set: # E.g. : 1->2, 2->3, 2->1, 3->2 => filters on 2 predecessors = list(networkx_graph.predecessors(degree_4_node_id)) successors = list(networkx_graph.successors(degree_4_node_id)) - # Check if the predecessors and successors are the same nodes if sorted(predecessors) == sorted(successors): degree_4_filtered.add(degree_4_node_id) @@ -135,7 +134,7 @@ def retrieve_edge(node1: int|float, node2: int|float) -> gpd.GeoDataFrame: ] return edge if not edge.empty else None - def construct_path(start_node: int|float, end_node: int|float, intermediates: list): + def construct_path(start_node: int|float, end_node: int|float, intermediates: list) -> pd.DataFrame | None: path = [] current_node = start_node _intermediates = intermediates.copy() From ff454d5241dbda954f344b726785f234c55ed96d Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 16:05:13 +0200 Subject: [PATCH 12/33] fix: _filter_node replaced two repeating functions --- .../snkit_network_merge_wrapper.py | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 850c285ef..6b4b1e500 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -97,30 +97,22 @@ def _get_merged_edges( updated_edges_gdf = updated_edges_gdf.drop(columns=["id"]) return updated_edges_gdf - def _filter_degree_2(degree_2s: set) -> set: - degree_2_filtered = set() - for degree_2_node_id in degree_2s: - # Get the predecessors (antecedents) and successors (precedents) to make sure for each node with the degree - # of 2 there is only one predecessor and successor. E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 - predecessors = list(networkx_graph.predecessors(degree_2_node_id)) - successors = list(networkx_graph.successors(degree_2_node_id)) - - # Check if there is one predecessor and one successor. - if len(predecessors) == len(successors) == 1: - degree_2_filtered.add(degree_2_node_id) - return degree_2_filtered - - def _filter_degree_4(degree_4s: set) -> set: - degree_4_filtered = set() - for degree_4_node_id in degree_4s: - # Get the predecessors (antecedents) and successors (precedents) for filtering. - # E.g. : 1->2, 2->3, 2->1, 3->2 => filters on 2 - predecessors = list(networkx_graph.predecessors(degree_4_node_id)) - successors = list(networkx_graph.successors(degree_4_node_id)) - # Check if the predecessors and successors are the same nodes - if sorted(predecessors) == sorted(successors): - degree_4_filtered.add(degree_4_node_id) - return degree_4_filtered + def _filter_node(_node_set: set, _degrees: int) -> set: + _filtered = set() + for _node_id in _node_set: + # Get the predecessors (antecedents) and successors (precedents) to make sure for to filter correctly + # For _node_set with all degree 2: this filters on the nodes that have only one predecessor and successor. + # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 + # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. + # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. + predecessors = list(networkx_graph.predecessors(_node_id)) + successors = list(networkx_graph.successors(_node_id)) + + # Check the degree of the _node_set and the corresponding criterium. + if ((_degrees == 2 and len(predecessors) == len(successors) == 1) or + (_degrees == 4 and sorted(predecessors) == sorted(successors))): + _filtered.add(_node_id) + return _filtered def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: def _find_and_append_degree_4_paths( @@ -220,11 +212,11 @@ def construct_path(start_node: int|float, end_node: int|float, intermediates: li degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) degree_2_set = set(degree_2) - filtered_degree_2_set = _filter_degree_2(degree_2_set) + filtered_degree_2_set = _filter_node(degree_2_set, _degrees=2) degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) degree_4_set = set(degree_4) - filtered_degree_4_set = _filter_degree_4(degree_4_set) + filtered_degree_4_set = _filter_node(degree_4_set, _degrees=4) nodes_of_interest = filtered_degree_2_set | filtered_degree_4_set From fa1daf0ce3a2c7d313e737abcfafcfa12a40e13e Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 16:16:16 +0200 Subject: [PATCH 13/33] fix: code made compacter and more comments added --- .../snkit_network_merge_wrapper.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 6b4b1e500..5aa9fa373 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -100,11 +100,9 @@ def _get_merged_edges( def _filter_node(_node_set: set, _degrees: int) -> set: _filtered = set() for _node_id in _node_set: - # Get the predecessors (antecedents) and successors (precedents) to make sure for to filter correctly + # Get the predecessors (antecedents) and successors (precedents) for filtering: # For _node_set with all degree 2: this filters on the nodes that have only one predecessor and successor. - # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. - # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. predecessors = list(networkx_graph.predecessors(_node_id)) successors = list(networkx_graph.successors(_node_id)) @@ -205,20 +203,21 @@ def construct_path(start_node: int|float, end_node: int|float, intermediates: li _edge_paths.append(edge_paths_gdf) return _edge_paths + # Adds degree column which is needed to find the to-be-simplified nodes and edges. if "degree" not in snkit_network.nodes.columns: snkit_network.nodes["degree"] = snkit_network.nodes[id_col].apply( lambda x: _node_connectivity_degree(x, snkit_network) ) - - degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) - degree_2_set = set(degree_2) - filtered_degree_2_set = _filter_node(degree_2_set, _degrees=2) - - degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) - degree_4_set = set(degree_4) - filtered_degree_4_set = _filter_node(degree_4_set, _degrees=4) - - nodes_of_interest = filtered_degree_2_set | filtered_degree_4_set + # Filter on the nodes with degree 2 and 4 which suffice the following criteria: + # For _node_set with all degree 2: this filters on the nodes that have only one predecessor and successor. + # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 + # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. + # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. + nodes_of_interest = set() + for degree in [2,4]: + nodes_with_degree_set = set(list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == degree])) + filtered_nodes_with_degree_set = _filter_node(nodes_with_degree_set, _degrees=degree) + nodes_of_interest = nodes_of_interest | filtered_nodes_with_degree_set edge_paths = _get_edge_paths(sorted(nodes_of_interest), snkit_network) From 0402c96f214958b6dc0ff85acf2e012a419113d9 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 16:23:05 +0200 Subject: [PATCH 14/33] fix: last commit almost reverted --- .../snkit_network_merge_wrapper.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 5aa9fa373..9df6bd173 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -100,9 +100,11 @@ def _get_merged_edges( def _filter_node(_node_set: set, _degrees: int) -> set: _filtered = set() for _node_id in _node_set: - # Get the predecessors (antecedents) and successors (precedents) for filtering: + # Get the predecessors (antecedents) and successors (precedents) to make sure for to filter correctly # For _node_set with all degree 2: this filters on the nodes that have only one predecessor and successor. + # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. + # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. predecessors = list(networkx_graph.predecessors(_node_id)) successors = list(networkx_graph.successors(_node_id)) @@ -208,16 +210,21 @@ def construct_path(start_node: int|float, end_node: int|float, intermediates: li snkit_network.nodes["degree"] = snkit_network.nodes[id_col].apply( lambda x: _node_connectivity_degree(x, snkit_network) ) + # Filter on the nodes with degree 2 and 4 which suffice the following criteria: # For _node_set with all degree 2: this filters on the nodes that have only one predecessor and successor. # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. - nodes_of_interest = set() - for degree in [2,4]: - nodes_with_degree_set = set(list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == degree])) - filtered_nodes_with_degree_set = _filter_node(nodes_with_degree_set, _degrees=degree) - nodes_of_interest = nodes_of_interest | filtered_nodes_with_degree_set + degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) + degree_2_set = set(degree_2) + filtered_degree_2_set = _filter_node(degree_2_set, _degrees=2) + + degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) + degree_4_set = set(degree_4) + filtered_degree_4_set = _filter_node(degree_4_set, _degrees=4) + + nodes_of_interest = filtered_degree_2_set | filtered_degree_4_set edge_paths = _get_edge_paths(sorted(nodes_of_interest), snkit_network) From 71d52e59c94d28a1d0292cc2645dfdbd6772dbb8 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 16:37:27 +0200 Subject: [PATCH 15/33] doc: comments added to _get_edge_paths --- .../snkit_network_merge_wrapper.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 9df6bd173..a0a63afe7 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -175,28 +175,39 @@ def construct_path(start_node: int|float, end_node: int|float, intermediates: li _edge_paths = [] + # find the edge paths for the nodes in node_set while node_set: + # for each node in node_set find other nodes on the path intermediates = set() popped_node = node_set.pop() + # intermediates are the nodes in the node_path that are between two other nodes in the path intermediates.add(popped_node) node_path = {popped_node} candidates = {popped_node} while candidates: popped_cand = candidates.pop() + # matches are the nodes that belong to a node_path matches = edge_dict[popped_cand] matches = matches - node_path for match in matches: intermediates.add(popped_cand) + # if the found node on the path is in the node_set, then keep looking for other connected nodes on + # the path if match in node_set: candidates.add(match) node_path.add(match) node_set.remove(match) + # If the found node is not in node_set stop. else: node_path.add(match) + # After finding all nodes on a path find the edges that are connected to these nodes. + # Finding the edges is different for the nodes in the node path with degree 2 and 4. if len(node_path) >= 2: if any(node_path.intersection(filtered_degree_4_set)): + # node_path has nodes with degree 4 => get the forward and backward paths _find_and_append_degree_4_paths(_edge_paths) else: + # node_path has nodes with degree 4 => find the edges connected to the intermediates edge_paths_gdf = snkit_network.edges[ snkit_network.edges.from_id.isin(intermediates) | snkit_network.edges.to_id.isin(intermediates) From 32c4a7f26c9c1e10478f7a9d1953aafc8e578a8d Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 17:12:10 +0200 Subject: [PATCH 16/33] chore: minor --- .../snkit_network_merge_wrapper.py | 96 ++++++++++--------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index a0a63afe7..0b362b01b 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -20,6 +20,7 @@ import geopandas as gpd import networkx as nx +import numpy as np import pandas as pd from networkx import MultiGraph from shapely.geometry import LineString, MultiLineString, MultiPoint, Point @@ -114,65 +115,71 @@ def _filter_node(_node_set: set, _degrees: int) -> set: _filtered.add(_node_id) return _filtered - def _get_edge_paths(node_set: set, snkit_network: SnkitNetwork) -> list: + def _get_edge_paths(node_set: set, _snkit_network: SnkitNetwork) -> list: + def _get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from_id", + to_id_column: str = "to_id") -> defaultdict: + # Convert the edges of a GeoDataFrame to an adjacency list using vectorized operations. + _edge_dict = defaultdict(set) + # Extract the 'from_id' and 'to_id' columns as numpy arrays for efficient processing + from_ids = edges_gdf[from_id_column].values + to_ids = edges_gdf[to_id_column].values + # Vectorized operation to populate the adjacency list + for from_id, to_id in np.nditer([from_ids, to_ids]): + _edge_dict[from_id].add(to_id) + _edge_dict[to_id].add(from_id) + + return _edge_dict + + def _retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: + """Retrieve the edge from snkit_network.edges GeoDataFrame between two nodes.""" + edge = _snkit_network.edges[ + (_snkit_network.edges['from_id'] == node1) & + (_snkit_network.edges['to_id'] == node2) + ] + return edge if not edge.empty else None + + def _construct_path(start_node: int | float, end_node: int | float, __intermediates: list) -> pd.DataFrame | None: + path = [] + current_node = start_node + _intermediates = __intermediates.copy() + + while _intermediates: + for next_node in _intermediates: + edge = _retrieve_edge(current_node, next_node) + if edge is not None: + path.append(edge) + _intermediates.remove(next_node) + current_node = next_node + break + + final_edge = _retrieve_edge(current_node, end_node) + if final_edge is not None: + path.append(final_edge) + + if len(path) > 0 and all(edge is not None for edge in path): + return pd.concat(path) # Combine edges into a single GeoDataFrame + return None + def _find_and_append_degree_4_paths( _edge_paths: list ) -> None: - def retrieve_edge(node1: int|float, node2: int|float) -> gpd.GeoDataFrame: - """Retrieve the edge from snkit_network.edges GeoDataFrame between two nodes.""" - edge = snkit_network.edges[ - (snkit_network.edges['from_id'] == node1) & - (snkit_network.edges['to_id'] == node2) - ] - return edge if not edge.empty else None - - def construct_path(start_node: int|float, end_node: int|float, intermediates: list) -> pd.DataFrame | None: - path = [] - current_node = start_node - _intermediates = intermediates.copy() - - while _intermediates: - for next_node in _intermediates: - edge = retrieve_edge(current_node, next_node) - if edge is not None: - path.append(edge) - _intermediates.remove(next_node) - current_node = next_node - break - - final_edge = retrieve_edge(current_node, end_node) - if final_edge is not None: - path.append(final_edge) - - if len(path) > 0 and all(edge is not None for edge in path): - return pd.concat(path) # Combine edges into a single GeoDataFrame - return None - boundary_nodes = list(node_path - filtered_degree_4_set) if len(boundary_nodes) == 2: from_node, to_node = boundary_nodes - intermediates = list(node_path & filtered_degree_4_set) + _intermediates = list(node_path & filtered_degree_4_set) # Construct and append the forward path - forward_gdf = construct_path(from_node, to_node, intermediates) + forward_gdf = _construct_path(from_node, to_node, _intermediates) if forward_gdf is not None: _edge_paths.append(forward_gdf) # Construct and append the backward path - backward_gdf = construct_path(to_node, from_node, intermediates) + backward_gdf = _construct_path(to_node, from_node, _intermediates) if backward_gdf is not None: _edge_paths.append(backward_gdf) - # Convert edges to an adjacency list using vectorized operations - edge_dict = defaultdict(set) - from_ids = snkit_network.edges["from_id"].values - to_ids = snkit_network.edges["to_id"].values - - for from_id, to_id in zip(from_ids, to_ids): - edge_dict[from_id].add(to_id) - edge_dict[to_id].add(from_id) - + edge_dict = _get_adjacency_list(edges_gdf=snkit_network.edges) _edge_paths = [] # find the edge paths for the nodes in node_set @@ -207,12 +214,11 @@ def construct_path(start_node: int|float, end_node: int|float, intermediates: li # node_path has nodes with degree 4 => get the forward and backward paths _find_and_append_degree_4_paths(_edge_paths) else: - # node_path has nodes with degree 4 => find the edges connected to the intermediates + # node_path has nodes with degree 2 => find the edges connected to the intermediates edge_paths_gdf = snkit_network.edges[ snkit_network.edges.from_id.isin(intermediates) | snkit_network.edges.to_id.isin(intermediates) ] - # Append the result to edge_paths _edge_paths.append(edge_paths_gdf) return _edge_paths From ce5d9f27074aaa038d71260c69fd18aef102d482 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 17:34:58 +0200 Subject: [PATCH 17/33] chore: _ removed from the inned functions --- .../snkit_network_merge_wrapper.py | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 0b362b01b..cbf86281e 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -61,7 +61,7 @@ def merge_edges( SnkitNetwork: _description_ """ - def _node_connectivity_degree(node, snkit_network: SnkitNetwork) -> int: + def node_connectivity_degree(node, snkit_network: SnkitNetwork) -> int: return len( snkit_network.edges[ (snkit_network.edges.from_id == node) @@ -69,13 +69,13 @@ def _node_connectivity_degree(node, snkit_network: SnkitNetwork) -> int: ] ) - def _get_edge_ids_to_update(edges_list: list) -> list: + def get_edge_ids_to_update(edges_list: list) -> list: ids_to_update = [] for edges in edges_list: ids_to_update.extend(edges.id.tolist()) return ids_to_update - def _get_merged_edges( + def get_merged_edges( paths_to_group: list, by: list, aggfunc: str | dict, @@ -98,7 +98,7 @@ def _get_merged_edges( updated_edges_gdf = updated_edges_gdf.drop(columns=["id"]) return updated_edges_gdf - def _filter_node(_node_set: set, _degrees: int) -> set: + def filter_node(_node_set: set, _degrees: int) -> set: _filtered = set() for _node_id in _node_set: # Get the predecessors (antecedents) and successors (precedents) to make sure for to filter correctly @@ -115,8 +115,8 @@ def _filter_node(_node_set: set, _degrees: int) -> set: _filtered.add(_node_id) return _filtered - def _get_edge_paths(node_set: set, _snkit_network: SnkitNetwork) -> list: - def _get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from_id", + def get_edge_paths(node_set: set, _snkit_network: SnkitNetwork) -> list: + def get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from_id", to_id_column: str = "to_id") -> defaultdict: # Convert the edges of a GeoDataFrame to an adjacency list using vectorized operations. _edge_dict = defaultdict(set) @@ -130,7 +130,7 @@ def _get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from return _edge_dict - def _retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: + def retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: """Retrieve the edge from snkit_network.edges GeoDataFrame between two nodes.""" edge = _snkit_network.edges[ (_snkit_network.edges['from_id'] == node1) & @@ -138,21 +138,21 @@ def _retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: ] return edge if not edge.empty else None - def _construct_path(start_node: int | float, end_node: int | float, __intermediates: list) -> pd.DataFrame | None: + def construct_path(start_node: int | float, end_node: int | float, __intermediates: list) -> pd.DataFrame | None: path = [] current_node = start_node _intermediates = __intermediates.copy() while _intermediates: for next_node in _intermediates: - edge = _retrieve_edge(current_node, next_node) + edge = retrieve_edge(current_node, next_node) if edge is not None: path.append(edge) _intermediates.remove(next_node) current_node = next_node break - final_edge = _retrieve_edge(current_node, end_node) + final_edge = retrieve_edge(current_node, end_node) if final_edge is not None: path.append(final_edge) @@ -160,27 +160,29 @@ def _construct_path(start_node: int | float, end_node: int | float, __intermedia return pd.concat(path) # Combine edges into a single GeoDataFrame return None - def _find_and_append_degree_4_paths( + def find_and_append_degree_4_paths( _edge_paths: list - ) -> None: + ) -> list: + _edge_paths_results = _edge_paths boundary_nodes = list(node_path - filtered_degree_4_set) if len(boundary_nodes) == 2: from_node, to_node = boundary_nodes _intermediates = list(node_path & filtered_degree_4_set) # Construct and append the forward path - forward_gdf = _construct_path(from_node, to_node, _intermediates) + forward_gdf = construct_path(from_node, to_node, _intermediates) if forward_gdf is not None: - _edge_paths.append(forward_gdf) + _edge_paths_results.append(forward_gdf) # Construct and append the backward path - backward_gdf = _construct_path(to_node, from_node, _intermediates) + backward_gdf = construct_path(to_node, from_node, _intermediates) if backward_gdf is not None: - _edge_paths.append(backward_gdf) + _edge_paths_results.append(backward_gdf) + return _edge_paths_results # Convert edges to an adjacency list using vectorized operations - edge_dict = _get_adjacency_list(edges_gdf=snkit_network.edges) - _edge_paths = [] + edge_dict = get_adjacency_list(edges_gdf=snkit_network.edges) + _edge_paths: list = [] # find the edge paths for the nodes in node_set while node_set: @@ -212,7 +214,7 @@ def _find_and_append_degree_4_paths( if len(node_path) >= 2: if any(node_path.intersection(filtered_degree_4_set)): # node_path has nodes with degree 4 => get the forward and backward paths - _find_and_append_degree_4_paths(_edge_paths) + _edge_paths = find_and_append_degree_4_paths(_edge_paths) else: # node_path has nodes with degree 2 => find the edges connected to the intermediates edge_paths_gdf = snkit_network.edges[ @@ -225,7 +227,7 @@ def _find_and_append_degree_4_paths( # Adds degree column which is needed to find the to-be-simplified nodes and edges. if "degree" not in snkit_network.nodes.columns: snkit_network.nodes["degree"] = snkit_network.nodes[id_col].apply( - lambda x: _node_connectivity_degree(x, snkit_network) + lambda x: node_connectivity_degree(x, snkit_network) ) # Filter on the nodes with degree 2 and 4 which suffice the following criteria: @@ -233,29 +235,22 @@ def _find_and_append_degree_4_paths( # E.g. filters on 2 in 1->2, 1->5, 2->3, 3->4, 3->5 # For _node_set with all degree 4: Check if the predecessors and successors are the same nodes. # E.g. filters on 2 in 1->2, 2->3, 2->1, 3->2. - degree_2 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2]) - degree_2_set = set(degree_2) - filtered_degree_2_set = _filter_node(degree_2_set, _degrees=2) + degree_2_set = set(list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 2])) + filtered_degree_2_set = filter_node(degree_2_set, _degrees=2) - degree_4 = list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4]) - degree_4_set = set(degree_4) - filtered_degree_4_set = _filter_node(degree_4_set, _degrees=4) + degree_4_set = set(list(snkit_network.nodes[id_col].loc[snkit_network.nodes.degree == 4])) + filtered_degree_4_set = filter_node(degree_4_set, _degrees=4) nodes_of_interest = filtered_degree_2_set | filtered_degree_4_set - edge_paths = _get_edge_paths(sorted(nodes_of_interest), snkit_network) + edge_paths = get_edge_paths(sorted(nodes_of_interest), snkit_network) - edge_ids_to_update = _get_edge_ids_to_update(edge_paths) + edge_ids_to_update = get_edge_ids_to_update(edge_paths) edges_to_keep = snkit_network.edges[ ~snkit_network.edges["id"].isin(edge_ids_to_update) ] - updated_edges = _get_merged_edges( - paths_to_group=edge_paths, - by=by, - aggfunc=aggregate_func, - net=snkit_network, - ) + updated_edges = get_merged_edges(paths_to_group=edge_paths, by=by, aggfunc=aggregate_func, net=snkit_network) edges_to_keep = edges_to_keep.drop(columns=["id"]) updated_edges = updated_edges.reset_index(drop=True) @@ -270,7 +265,7 @@ def _find_and_append_degree_4_paths( merged_snkit_network = SnkitNetwork(nodes=new_nodes_gdf, edges=new_edges_gdf) merged_snkit_network.nodes["degree"] = merged_snkit_network.nodes[id_col].apply( - lambda x: _node_connectivity_degree(x, merged_snkit_network) + lambda x: node_connectivity_degree(x, merged_snkit_network) ) return merged_snkit_network From 61acd5edc28365617a25476aa4c7bc34cd9fa6ca Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 17:35:13 +0200 Subject: [PATCH 18/33] chore: _ removed from the inned functions --- .../network_simplification/test_network_graph_simplificator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 0da811513..d627b1a41 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -25,7 +25,7 @@ def _detailed_edge_comparison( graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: - def _dicts_comparison( + def dicts_comparison( _graph1: nx.MultiDiGraph | nx.MultiGraph, _graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: for u, v, k, data1 in _graph1.edges(keys=True, data=True): From efb3477f2370427dba62ca9f40c95f97002c4932 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Wed, 4 Sep 2024 17:35:21 +0200 Subject: [PATCH 19/33] chore: _ removed from the inned functions --- .../test_network_graph_simplificator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index d627b1a41..b147edba6 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -49,8 +49,8 @@ def dicts_comparison( return False return True - check_1_2 = _dicts_comparison(graph1, graph2) - check_2_1 = _dicts_comparison(graph2, graph1) + check_1_2 = dicts_comparison(graph1, graph2) + check_2_1 = dicts_comparison(graph2, graph1) if check_1_2 and check_2_1: return True From 7a72adbc9ac8575d957f1808be2e964d1a8e2a5b Mon Sep 17 00:00:00 2001 From: asgarpou Date: Sun, 8 Sep 2024 00:38:51 +0200 Subject: [PATCH 20/33] fix: array to int to create a dict --- .../network_simplification/snkit_network_merge_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index cbf86281e..d1e471b13 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -125,8 +125,8 @@ def get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from_ to_ids = edges_gdf[to_id_column].values # Vectorized operation to populate the adjacency list for from_id, to_id in np.nditer([from_ids, to_ids]): - _edge_dict[from_id].add(to_id) - _edge_dict[to_id].add(from_id) + _edge_dict[int(from_id)].add(int(to_id)) + _edge_dict[int(to_id)].add(int(from_id)) return _edge_dict From ea8ef06bd11e869da12926e297f27761c1228452 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 08:51:18 +0200 Subject: [PATCH 21/33] fix: removed default values + private variables --- .../snkit_network_merge_wrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index d1e471b13..32a113623 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -116,8 +116,8 @@ def filter_node(_node_set: set, _degrees: int) -> set: return _filtered def get_edge_paths(node_set: set, _snkit_network: SnkitNetwork) -> list: - def get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str = "from_id", - to_id_column: str = "to_id") -> defaultdict: + def get_adjacency_list(edges_gdf: gpd.GeoDataFrame, from_id_column: str, + to_id_column: str) -> defaultdict: # Convert the edges of a GeoDataFrame to an adjacency list using vectorized operations. _edge_dict = defaultdict(set) # Extract the 'from_id' and 'to_id' columns as numpy arrays for efficient processing @@ -138,10 +138,10 @@ def retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: ] return edge if not edge.empty else None - def construct_path(start_node: int | float, end_node: int | float, __intermediates: list) -> pd.DataFrame | None: + def construct_path(start_node: int | float, end_node: int | float, intermediates: list) -> pd.DataFrame | None: path = [] current_node = start_node - _intermediates = __intermediates.copy() + _intermediates = intermediates.copy() while _intermediates: for next_node in _intermediates: @@ -181,7 +181,7 @@ def find_and_append_degree_4_paths( return _edge_paths_results # Convert edges to an adjacency list using vectorized operations - edge_dict = get_adjacency_list(edges_gdf=snkit_network.edges) + edge_dict = get_adjacency_list(edges_gdf=snkit_network.edges, from_id_column="from_id", to_id_column="to_id") _edge_paths: list = [] # find the edge paths for the nodes in node_set From 9260b0666690167e63873d8c4f0525d02b62796c Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 08:58:24 +0200 Subject: [PATCH 22/33] fix: while loop is replaced --- .../snkit_network_merge_wrapper.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py index 32a113623..eac76f852 100644 --- a/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_merge_wrapper.py @@ -138,19 +138,22 @@ def retrieve_edge(node1: int | float, node2: int | float) -> gpd.GeoDataFrame: ] return edge if not edge.empty else None - def construct_path(start_node: int | float, end_node: int | float, intermediates: list) -> pd.DataFrame | None: + def construct_path(start_node: int | float, end_node: int | float, intermediate_nodes: list) -> pd.DataFrame | None: path = [] current_node = start_node - _intermediates = intermediates.copy() - - while _intermediates: - for next_node in _intermediates: - edge = retrieve_edge(current_node, next_node) - if edge is not None: - path.append(edge) - _intermediates.remove(next_node) - current_node = next_node - break + _intermediates = intermediate_nodes.copy() + + _explored_nodes = [] + # Ensure we go through all the items. + for _ in range(len(intermediate_nodes)): + # Filter out nodes already used for edge retrieval + _to_explore = filter(lambda x: x not in _explored_nodes, intermediate_nodes) + for _next_node in _to_explore: + _edge = retrieve_edge(current_node, _next_node) + if _edge is not None: + path.append(_edge) + _explored_nodes.append(_next_node) + current_node = _next_node final_edge = retrieve_edge(current_node, end_node) if final_edge is not None: From f18be85e4155eddde15787438f341c81f067c5dc Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 09:18:16 +0200 Subject: [PATCH 23/33] fix: add_edge_with_attributes added to make the test compacter --- .../test_network_graph_simplificator.py | 188 ++++-------------- 1 file changed, 41 insertions(+), 147 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index b147edba6..52ef574f0 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -4,6 +4,8 @@ import networkx as nx import numpy as np import pytest +from altgraph.Graph import Graph +from networkx import DiGraph from shapely.geometry import LineString from ra2ce.network import add_missing_geoms_graph @@ -176,6 +178,23 @@ def create_nx_multidigraph(): def _get_expected_result_graph_fixture( self, nx_digraph_factory: nx.MultiDiGraph ) -> nx.MultiGraph: + def add_edge_with_attributes(graph_to_shape: Graph | DiGraph, edge_u: int | float, edge_v: int | float, a_value: str, edge_node_ids: list) -> Graph | DiGraph: + # Create a copy of the input graph + shaped_graph = graph_to_shape.copy() + + # Extract geometries programmatically using edge_node_ids + geometry_list = [graph_to_shape.nodes[n_id]["geometry"] for n_id in edge_node_ids] + + shaped_graph.add_edge( + edge_u, + edge_v, + a=a_value, + from_node=edge_u, + to_node=edge_v, + geometry=LineString(geometry_list), + ) + return shaped_graph + _nx_digraph = nx_digraph_factory() _result_digraph = nx.MultiDiGraph() node_ids_degrees = {2: 3, 4: 2, 7: 2, 8: 4, 11: 3, 12: 2, 16: 4, 17: 1, 18: 1} @@ -186,163 +205,38 @@ def _get_expected_result_graph_fixture( _result_digraph.add_node(node_id, **node_data) _result_digraph = add_missing_geoms_graph(_result_digraph, "geometry") - _result_digraph.add_edge( - 2, - 4.0, - a='None', - from_node=2, - to_node=4, - geometry=LineString( - [ - _nx_digraph.nodes[2]["geometry"], - _nx_digraph.nodes[3]["geometry"], - _nx_digraph.nodes[4]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 2, 4.0, 'None', [2, 3, 4] ) - - _result_digraph.add_edge( - 2, - 16.0, - a='None', - from_node=2, - to_node=16, - geometry=LineString( - [ - _nx_digraph.nodes[2]["geometry"], - _nx_digraph.nodes[1]["geometry"], - _nx_digraph.nodes[16]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 2, 16.0, 'None', [2, 1, 16] ) - - _result_digraph.add_edge( - 4, - 7.0, - a="yes", - from_node=4, - to_node=7, - geometry=LineString( - [ - _nx_digraph.nodes[4]["geometry"], - _nx_digraph.nodes[5]["geometry"], - _nx_digraph.nodes[6]["geometry"], - _nx_digraph.nodes[7]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 4, 7.0, 'yes', [4, 5, 6, 7] ) - _result_digraph.add_edge( - 7, - 8.0, - a='None', - from_node=7, - to_node=8, - geometry=LineString( - [ - _nx_digraph.nodes[7]["geometry"], - _nx_digraph.nodes[8]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 7, 8.0, 'None', [7, 8] ) - _result_digraph.add_edge( - 8, - 11, - a='None', - from_node=8, - to_node=11, - geometry=LineString( - [ - _nx_digraph.nodes[8]["geometry"], - _nx_digraph.nodes[9]["geometry"], - _nx_digraph.nodes[10]["geometry"], - _nx_digraph.nodes[11]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 8, 11, 'None', [8, 9, 10, 11] ) - _result_digraph.add_edge( - 8, - 11, - a="yes", - from_node=8, - to_node=11, - geometry=LineString( - [ - _nx_digraph.nodes[8]["geometry"], - _nx_digraph.nodes[13]["geometry"], - _nx_digraph.nodes[14]["geometry"], - _nx_digraph.nodes[15]["geometry"], - _nx_digraph.nodes[11]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 8, 11, 'yes', [8, 13, 14, 15, 11] ) - _result_digraph.add_edge( - 8, - 12, - a='None', - from_node=8, - to_node=12, - geometry=LineString( - [ - _nx_digraph.nodes[8]["geometry"], - _nx_digraph.nodes[12]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 8, 12, 'None', [8, 12] ) - _result_digraph.add_edge( - 11, - 12, - a="yes", - from_node=11, - to_node=12, - geometry=LineString( - [ - _nx_digraph.nodes[11]["geometry"], - _nx_digraph.nodes[12]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 11, 12, 'yes', [11, 12] ) - - _result_digraph.add_edge( - 16, - 2.0, - a='None', - from_node=16, - to_node=2, - geometry=LineString( - [ - _nx_digraph.nodes[16]["geometry"], - _nx_digraph.nodes[1]["geometry"], - _nx_digraph.nodes[2]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 16, 2.0, 'None', [16, 1, 2] ) - - _result_digraph.add_edge( - 16, - 17, - a='None', - from_node=16, - to_node=17, - geometry=LineString( - [ - _nx_digraph.nodes[16]["geometry"], - _nx_digraph.nodes[17]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 16, 17, 'None', [16, 17] ) - - _result_digraph.add_edge( - 16, - 18, - a='None', - from_node=16, - to_node=18, - geometry=LineString( - [ - _nx_digraph.nodes[16]["geometry"], - _nx_digraph.nodes[18]["geometry"], - ] - ), + _result_digraph = add_edge_with_attributes( + _result_digraph, 16, 18, 'None', [16, 18] ) _result_digraph.graph["crs"] = "EPSG:4326" From 6d328a19a324f50cd562a1f1ced1012a42115d04 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 09:25:13 +0200 Subject: [PATCH 24/33] fix: add_nodes_from replaced the for loop --- .../network_simplification/test_network_graph_simplificator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 52ef574f0..79e730bbd 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -141,8 +141,7 @@ def _get_network_simplification_with_attribute_exclusion( def _get_nx_digraph_factory(self) -> Iterator[Callable[[], nx.MultiDiGraph]]: def create_nx_multidigraph(): _nx_digraph = nx.MultiDiGraph() - for i in range(1, 19): - _nx_digraph.add_node(i, x=i, y=i * 10) + _nx_digraph.add_nodes_from([(i, {'x': i, 'y': i * 10}) for i in range(1, 19)]) _nx_digraph.add_edge(1, 2, a='None') _nx_digraph.add_edge(2, 1, a='None') From 01c6da9ab13f8b43a7cb093c2ad286ae6801b97e Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 09:27:52 +0200 Subject: [PATCH 25/33] chore: default values are removed --- .../network_simplification/snkit_network_wrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_wrapper.py b/ra2ce/network/network_simplification/snkit_network_wrapper.py index 14802d1ce..819bed6ff 100644 --- a/ra2ce/network/network_simplification/snkit_network_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_wrapper.py @@ -43,12 +43,12 @@ class SnkitNetworkWrapper: """ snkit_network: Network - node_id_column_name: str = "id" - edge_from_id_column_name: str = "from_id" - edge_to_id_column_name: str = "to_id" + node_id_column_name: str + edge_from_id_column_name: str + edge_to_id_column_name: str - def __init__(self, snkit_network: Network, node_id_column_name: str = "id", - edge_from_id_column_name: str = "from_id", edge_to_id_column_name: str = "to_id") -> None: + def __init__(self, snkit_network: Network, node_id_column_name: str, + edge_from_id_column_name: str, edge_to_id_column_name: str) -> None: self.snkit_network = snkit_network self.node_id_column_name = node_id_column_name self.edge_from_id_column_name = edge_from_id_column_name From 34fec8dde687926ce2565ff2946713f1f3d52822 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 09:30:27 +0200 Subject: [PATCH 26/33] chore: self.to_networkx() replaced --- ra2ce/network/network_simplification/snkit_network_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ra2ce/network/network_simplification/snkit_network_wrapper.py b/ra2ce/network/network_simplification/snkit_network_wrapper.py index 819bed6ff..0cfeb41ca 100644 --- a/ra2ce/network/network_simplification/snkit_network_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_wrapper.py @@ -95,7 +95,7 @@ def filter_excluded_attributes() -> list[str]: # Overwrite the existing network with the merged edges. self.snkit_network = merge_edges( snkit_network=self.snkit_network, - networkx_graph=self.networkx_graph, + networkx_graph=self.to_networkx(), aggregate_func=_aggregate_function, by=_attributes_to_exclude, id_col="id", From 29d461a6ca213857f7806106e31b970a19ff767a Mon Sep 17 00:00:00 2001 From: asgarpou Date: Tue, 10 Sep 2024 10:06:05 +0200 Subject: [PATCH 27/33] chore: test revised --- .../network_simplification/test_network_graph_simplificator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 79e730bbd..e5b7bb67b 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -182,7 +182,7 @@ def add_edge_with_attributes(graph_to_shape: Graph | DiGraph, edge_u: int | floa shaped_graph = graph_to_shape.copy() # Extract geometries programmatically using edge_node_ids - geometry_list = [graph_to_shape.nodes[n_id]["geometry"] for n_id in edge_node_ids] + geometry_list = [_nx_digraph.nodes[n_id]["geometry"] for n_id in edge_node_ids] shaped_graph.add_edge( edge_u, From e3802b1cb80b2f6740982ef0b980b88783b0fc3b Mon Sep 17 00:00:00 2001 From: asgarpou Date: Fri, 13 Sep 2024 11:23:59 +0200 Subject: [PATCH 28/33] chore: removed unnecessaty self.to_networkx() --- .../network_simplification/snkit_network_wrapper.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_wrapper.py b/ra2ce/network/network_simplification/snkit_network_wrapper.py index 0cfeb41ca..e5173638d 100644 --- a/ra2ce/network/network_simplification/snkit_network_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_wrapper.py @@ -53,13 +53,12 @@ def __init__(self, snkit_network: Network, node_id_column_name: str, self.node_id_column_name = node_id_column_name self.edge_from_id_column_name = edge_from_id_column_name self.edge_to_id_column_name = edge_to_id_column_name - self.networkx_graph: NxGraph = self.to_networkx() @classmethod def from_networkx( - cls, - networkx_graph: NxGraph, - column_names_dict: dict[str, str], + cls, + networkx_graph: NxGraph, + column_names_dict: dict[str, str], ) -> SnkitNetworkWrapper: """ Generates a `SnkitNetworkWrapper` based on the given `NxGraph`. @@ -108,7 +107,7 @@ def process_network(self) -> None: ) # length in m self.snkit_network.edges = self.snkit_network.edges[ self.snkit_network.edges["length"] != 0 - ] # Remove zero-length edges + ] # Remove zero-length edges def convert_to_line_string(geometry_to_convert) -> MultiLineString: if isinstance(geometry_to_convert, MultiLineString): From 8b5f7a7f8d0b43964f3d14101b67204d003fcd15 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Fri, 13 Sep 2024 16:47:45 +0200 Subject: [PATCH 29/33] chore: formatted --- .../snkit_network_wrapper.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ra2ce/network/network_simplification/snkit_network_wrapper.py b/ra2ce/network/network_simplification/snkit_network_wrapper.py index e5173638d..8ebab34fd 100644 --- a/ra2ce/network/network_simplification/snkit_network_wrapper.py +++ b/ra2ce/network/network_simplification/snkit_network_wrapper.py @@ -47,8 +47,13 @@ class SnkitNetworkWrapper: edge_from_id_column_name: str edge_to_id_column_name: str - def __init__(self, snkit_network: Network, node_id_column_name: str, - edge_from_id_column_name: str, edge_to_id_column_name: str) -> None: + def __init__( + self, + snkit_network: Network, + node_id_column_name: str, + edge_from_id_column_name: str, + edge_to_id_column_name: str, + ) -> None: self.snkit_network = snkit_network self.node_id_column_name = node_id_column_name self.edge_from_id_column_name = edge_from_id_column_name @@ -56,9 +61,9 @@ def __init__(self, snkit_network: Network, node_id_column_name: str, @classmethod def from_networkx( - cls, - networkx_graph: NxGraph, - column_names_dict: dict[str, str], + cls, + networkx_graph: NxGraph, + column_names_dict: dict[str, str], ) -> SnkitNetworkWrapper: """ Generates a `SnkitNetworkWrapper` based on the given `NxGraph`. @@ -87,9 +92,13 @@ def filter_excluded_attributes() -> list[str]: _attributes_to_exclude = filter_excluded_attributes() if "demand_edge" not in _attributes_to_exclude: - _aggregate_function = self._aggrfunc(cols, _attributes_to_exclude, with_demand=True) + _aggregate_function = self._aggrfunc( + cols, _attributes_to_exclude, with_demand=True + ) else: - _aggregate_function = self._aggrfunc(cols, _attributes_to_exclude, with_demand=False) + _aggregate_function = self._aggrfunc( + cols, _attributes_to_exclude, with_demand=False + ) # Overwrite the existing network with the merged edges. self.snkit_network = merge_edges( @@ -107,7 +116,7 @@ def process_network(self) -> None: ) # length in m self.snkit_network.edges = self.snkit_network.edges[ self.snkit_network.edges["length"] != 0 - ] # Remove zero-length edges + ] # Remove zero-length edges def convert_to_line_string(geometry_to_convert) -> MultiLineString: if isinstance(geometry_to_convert, MultiLineString): From 727b5f26d38b523b2a118f029dbc5704c5685cd2 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Mon, 16 Sep 2024 10:04:14 +0200 Subject: [PATCH 30/33] chore: graph1 renamed --- .../test_network_graph_simplificator.py | 107 ++++++++++-------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index e5b7bb67b..b36e24be9 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -25,18 +25,19 @@ def _detailed_edge_comparison( - graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph + graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: def dicts_comparison( - _graph1: nx.MultiDiGraph | nx.MultiGraph, _graph2: nx.MultiDiGraph | nx.MultiGraph + graph_a: nx.MultiDiGraph | nx.MultiGraph, + graph_b: nx.MultiDiGraph | nx.MultiGraph, ) -> bool: - for u, v, k, data1 in _graph1.edges(keys=True, data=True): - geom1 = data1['geometry'] + for u, v, k, data1 in graph_a.edges(keys=True, data=True): + geom1 = data1["geometry"] geom_found = 0 - data2_dict = _graph2.get_edge_data(u, v) + data2_dict = graph_b.get_edge_data(u, v) for _, data2 in data2_dict.items(): - if data2['geometry'] == geom1: + if data2["geometry"] == geom1: geom_found = 1 for key1, value1 in data1.items(): if key1 not in data2: @@ -63,7 +64,7 @@ def dicts_comparison( class TestNetworkGraphSimplificator: @pytest.fixture(name="network_graph_simplificator_factory") def _get_network_graph_simplificator( - self, + self, ) -> Iterator[Callable[[], NetworkGraphSimplificator]]: def get_network_graph_simplificator() -> NetworkGraphSimplificator: return NetworkGraphSimplificator( @@ -73,8 +74,8 @@ def get_network_graph_simplificator() -> NetworkGraphSimplificator: yield get_network_graph_simplificator def test_validate_fixture_init( - self, - network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], + self, + network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], ): # 1. Define test data. _network_graph_simplificator = network_graph_simplificator_factory() @@ -90,9 +91,9 @@ def _get_multigraph_fixture(self) -> Iterator[nx.MultiGraph]: yield _graph def test__graph_create_unique_ids_with_missing_id_data( - self, - network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], - multigraph_fixture: nx.MultiGraph, + self, + network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], + multigraph_fixture: nx.MultiGraph, ): # 1. Define test data _network_graph_simplificator = network_graph_simplificator_factory() @@ -110,9 +111,9 @@ def test__graph_create_unique_ids_with_missing_id_data( assert all(_new_id_name in _keys for _keys in _dicts_keys) def test__graph_create_unique_ids_with_existing_id( - self, - network_graph_simplificator_factory: NetworkGraphSimplificator, - multigraph_fixture: nx.MultiGraph, + self, + network_graph_simplificator_factory: NetworkGraphSimplificator, + multigraph_fixture: nx.MultiGraph, ): # 1. Define test data _network_graph_simplificator = network_graph_simplificator_factory() @@ -131,7 +132,7 @@ def test__graph_create_unique_ids_with_existing_id( class TestNetworkSimplificationWithAttributeExclusion: @pytest.fixture(name="network_simplification_with_attribute_exclusion") def _get_network_simplification_with_attribute_exclusion( - self, + self, ) -> Iterator[NetworkSimplificationWithAttributeExclusion]: yield NetworkSimplificationWithAttributeExclusion( nx_graph=None, attributes_to_exclude=[] @@ -141,29 +142,31 @@ def _get_network_simplification_with_attribute_exclusion( def _get_nx_digraph_factory(self) -> Iterator[Callable[[], nx.MultiDiGraph]]: def create_nx_multidigraph(): _nx_digraph = nx.MultiDiGraph() - _nx_digraph.add_nodes_from([(i, {'x': i, 'y': i * 10}) for i in range(1, 19)]) + _nx_digraph.add_nodes_from( + [(i, {"x": i, "y": i * 10}) for i in range(1, 19)] + ) - _nx_digraph.add_edge(1, 2, a='None') - _nx_digraph.add_edge(2, 1, a='None') - _nx_digraph.add_edge(2, 3, a='None') - _nx_digraph.add_edge(3, 4, a='None') + _nx_digraph.add_edge(1, 2, a="None") + _nx_digraph.add_edge(2, 1, a="None") + _nx_digraph.add_edge(2, 3, a="None") + _nx_digraph.add_edge(3, 4, a="None") _nx_digraph.add_edge(4, 5, a="yes") _nx_digraph.add_edge(5, 6, a="yes") _nx_digraph.add_edge(6, 7, a="yes") - _nx_digraph.add_edge(7, 8, a='None') - _nx_digraph.add_edge(8, 9, a='None') - _nx_digraph.add_edge(8, 12, a='None') + _nx_digraph.add_edge(7, 8, a="None") + _nx_digraph.add_edge(8, 9, a="None") + _nx_digraph.add_edge(8, 12, a="None") _nx_digraph.add_edge(8, 13, a="yes") - _nx_digraph.add_edge(9, 10, a='None') - _nx_digraph.add_edge(10, 11, a='None') + _nx_digraph.add_edge(9, 10, a="None") + _nx_digraph.add_edge(10, 11, a="None") _nx_digraph.add_edge(11, 12, a="yes") _nx_digraph.add_edge(13, 14, a="yes") _nx_digraph.add_edge(14, 15, a="yes") _nx_digraph.add_edge(15, 11, a="yes") - _nx_digraph.add_edge(1, 16, a='None') - _nx_digraph.add_edge(16, 1, a='None') - _nx_digraph.add_edge(16, 17, a='None') - _nx_digraph.add_edge(16, 18, a='None') + _nx_digraph.add_edge(1, 16, a="None") + _nx_digraph.add_edge(16, 1, a="None") + _nx_digraph.add_edge(16, 17, a="None") + _nx_digraph.add_edge(16, 18, a="None") _nx_digraph = add_missing_geoms_graph(_nx_digraph, "geometry") _nx_digraph.graph["crs"] = "EPSG:4326" @@ -175,14 +178,22 @@ def create_nx_multidigraph(): @pytest.fixture(name="expected_result_graph_fixture") def _get_expected_result_graph_fixture( - self, nx_digraph_factory: nx.MultiDiGraph + self, nx_digraph_factory: nx.MultiDiGraph ) -> nx.MultiGraph: - def add_edge_with_attributes(graph_to_shape: Graph | DiGraph, edge_u: int | float, edge_v: int | float, a_value: str, edge_node_ids: list) -> Graph | DiGraph: + def add_edge_with_attributes( + graph_to_shape: Graph | DiGraph, + edge_u: int | float, + edge_v: int | float, + a_value: str, + edge_node_ids: list, + ) -> Graph | DiGraph: # Create a copy of the input graph shaped_graph = graph_to_shape.copy() # Extract geometries programmatically using edge_node_ids - geometry_list = [_nx_digraph.nodes[n_id]["geometry"] for n_id in edge_node_ids] + geometry_list = [ + _nx_digraph.nodes[n_id]["geometry"] for n_id in edge_node_ids + ] shaped_graph.add_edge( edge_u, @@ -205,37 +216,37 @@ def add_edge_with_attributes(graph_to_shape: Graph | DiGraph, edge_u: int | floa _result_digraph = add_missing_geoms_graph(_result_digraph, "geometry") _result_digraph = add_edge_with_attributes( - _result_digraph, 2, 4.0, 'None', [2, 3, 4] + _result_digraph, 2, 4.0, "None", [2, 3, 4] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 2, 16.0, 'None', [2, 1, 16] + _result_digraph, 2, 16.0, "None", [2, 1, 16] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 4, 7.0, 'yes', [4, 5, 6, 7] + _result_digraph, 4, 7.0, "yes", [4, 5, 6, 7] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 7, 8.0, 'None', [7, 8] + _result_digraph, 7, 8.0, "None", [7, 8] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 8, 11, 'None', [8, 9, 10, 11] + _result_digraph, 8, 11, "None", [8, 9, 10, 11] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 8, 11, 'yes', [8, 13, 14, 15, 11] + _result_digraph, 8, 11, "yes", [8, 13, 14, 15, 11] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 8, 12, 'None', [8, 12] + _result_digraph, 8, 12, "None", [8, 12] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 11, 12, 'yes', [11, 12] + _result_digraph, 11, 12, "yes", [11, 12] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 16, 2.0, 'None', [16, 1, 2] + _result_digraph, 16, 2.0, "None", [16, 1, 2] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 16, 17, 'None', [16, 17] + _result_digraph, 16, 17, "None", [16, 17] ) _result_digraph = add_edge_with_attributes( - _result_digraph, 16, 18, 'None', [16, 18] + _result_digraph, 16, 18, "None", [16, 18] ) _result_digraph.graph["crs"] = "EPSG:4326" @@ -251,10 +262,10 @@ def add_edge_with_attributes(graph_to_shape: Graph | DiGraph, edge_u: int | floa return SnkitToNxNetworkConverter(snkit_network=snkit_network).convert() def test_simplify_graph( - self, - network_simplification_with_attribute_exclusion: NetworkSimplificationWithAttributeExclusion, - nx_digraph_factory: Callable[[], nx.MultiDiGraph], - expected_result_graph_fixture: nx.MultiDiGraph, + self, + network_simplification_with_attribute_exclusion: NetworkSimplificationWithAttributeExclusion, + nx_digraph_factory: Callable[[], nx.MultiDiGraph], + expected_result_graph_fixture: nx.MultiDiGraph, ): network_simplification_with_attribute_exclusion.nx_graph = nx_digraph_factory() network_simplification_with_attribute_exclusion.attributes_to_exclude = ["a"] From ca3bdacd8065453827cb936ac594a279dfe3c916 Mon Sep 17 00:00:00 2001 From: asgarpou Date: Mon, 16 Sep 2024 10:07:41 +0200 Subject: [PATCH 31/33] chore: reformatting --- .../test_network_graph_simplificator.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index b36e24be9..226536af6 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -25,11 +25,11 @@ def _detailed_edge_comparison( - graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph + graph1: nx.MultiDiGraph | nx.MultiGraph, graph2: nx.MultiDiGraph | nx.MultiGraph ) -> bool: def dicts_comparison( - graph_a: nx.MultiDiGraph | nx.MultiGraph, - graph_b: nx.MultiDiGraph | nx.MultiGraph, + graph_a: nx.MultiDiGraph | nx.MultiGraph, + graph_b: nx.MultiDiGraph | nx.MultiGraph, ) -> bool: for u, v, k, data1 in graph_a.edges(keys=True, data=True): geom1 = data1["geometry"] @@ -64,7 +64,7 @@ def dicts_comparison( class TestNetworkGraphSimplificator: @pytest.fixture(name="network_graph_simplificator_factory") def _get_network_graph_simplificator( - self, + self, ) -> Iterator[Callable[[], NetworkGraphSimplificator]]: def get_network_graph_simplificator() -> NetworkGraphSimplificator: return NetworkGraphSimplificator( @@ -74,8 +74,8 @@ def get_network_graph_simplificator() -> NetworkGraphSimplificator: yield get_network_graph_simplificator def test_validate_fixture_init( - self, - network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], + self, + network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], ): # 1. Define test data. _network_graph_simplificator = network_graph_simplificator_factory() @@ -91,9 +91,9 @@ def _get_multigraph_fixture(self) -> Iterator[nx.MultiGraph]: yield _graph def test__graph_create_unique_ids_with_missing_id_data( - self, - network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], - multigraph_fixture: nx.MultiGraph, + self, + network_graph_simplificator_factory: Callable[[], NetworkGraphSimplificator], + multigraph_fixture: nx.MultiGraph, ): # 1. Define test data _network_graph_simplificator = network_graph_simplificator_factory() @@ -111,9 +111,9 @@ def test__graph_create_unique_ids_with_missing_id_data( assert all(_new_id_name in _keys for _keys in _dicts_keys) def test__graph_create_unique_ids_with_existing_id( - self, - network_graph_simplificator_factory: NetworkGraphSimplificator, - multigraph_fixture: nx.MultiGraph, + self, + network_graph_simplificator_factory: NetworkGraphSimplificator, + multigraph_fixture: nx.MultiGraph, ): # 1. Define test data _network_graph_simplificator = network_graph_simplificator_factory() @@ -132,7 +132,7 @@ def test__graph_create_unique_ids_with_existing_id( class TestNetworkSimplificationWithAttributeExclusion: @pytest.fixture(name="network_simplification_with_attribute_exclusion") def _get_network_simplification_with_attribute_exclusion( - self, + self, ) -> Iterator[NetworkSimplificationWithAttributeExclusion]: yield NetworkSimplificationWithAttributeExclusion( nx_graph=None, attributes_to_exclude=[] @@ -178,14 +178,14 @@ def create_nx_multidigraph(): @pytest.fixture(name="expected_result_graph_fixture") def _get_expected_result_graph_fixture( - self, nx_digraph_factory: nx.MultiDiGraph + self, nx_digraph_factory: nx.MultiDiGraph ) -> nx.MultiGraph: def add_edge_with_attributes( - graph_to_shape: Graph | DiGraph, - edge_u: int | float, - edge_v: int | float, - a_value: str, - edge_node_ids: list, + graph_to_shape: Graph | DiGraph, + edge_u: int | float, + edge_v: int | float, + a_value: str, + edge_node_ids: list, ) -> Graph | DiGraph: # Create a copy of the input graph shaped_graph = graph_to_shape.copy() @@ -262,10 +262,10 @@ def add_edge_with_attributes( return SnkitToNxNetworkConverter(snkit_network=snkit_network).convert() def test_simplify_graph( - self, - network_simplification_with_attribute_exclusion: NetworkSimplificationWithAttributeExclusion, - nx_digraph_factory: Callable[[], nx.MultiDiGraph], - expected_result_graph_fixture: nx.MultiDiGraph, + self, + network_simplification_with_attribute_exclusion: NetworkSimplificationWithAttributeExclusion, + nx_digraph_factory: Callable[[], nx.MultiDiGraph], + expected_result_graph_fixture: nx.MultiDiGraph, ): network_simplification_with_attribute_exclusion.nx_graph = nx_digraph_factory() network_simplification_with_attribute_exclusion.attributes_to_exclude = ["a"] From 2308cd8c5b597c1dab16fb3ae278e3999d4ff25f Mon Sep 17 00:00:00 2001 From: asgarpou Date: Mon, 16 Sep 2024 10:11:52 +0200 Subject: [PATCH 32/33] chore: attribute to exclude renamed --- .../test_network_graph_simplificator.py | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/network/network_simplification/test_network_graph_simplificator.py b/tests/network/network_simplification/test_network_graph_simplificator.py index 226536af6..474352e56 100644 --- a/tests/network/network_simplification/test_network_graph_simplificator.py +++ b/tests/network/network_simplification/test_network_graph_simplificator.py @@ -146,27 +146,27 @@ def create_nx_multidigraph(): [(i, {"x": i, "y": i * 10}) for i in range(1, 19)] ) - _nx_digraph.add_edge(1, 2, a="None") - _nx_digraph.add_edge(2, 1, a="None") - _nx_digraph.add_edge(2, 3, a="None") - _nx_digraph.add_edge(3, 4, a="None") - _nx_digraph.add_edge(4, 5, a="yes") - _nx_digraph.add_edge(5, 6, a="yes") - _nx_digraph.add_edge(6, 7, a="yes") - _nx_digraph.add_edge(7, 8, a="None") - _nx_digraph.add_edge(8, 9, a="None") - _nx_digraph.add_edge(8, 12, a="None") - _nx_digraph.add_edge(8, 13, a="yes") - _nx_digraph.add_edge(9, 10, a="None") - _nx_digraph.add_edge(10, 11, a="None") - _nx_digraph.add_edge(11, 12, a="yes") - _nx_digraph.add_edge(13, 14, a="yes") - _nx_digraph.add_edge(14, 15, a="yes") - _nx_digraph.add_edge(15, 11, a="yes") - _nx_digraph.add_edge(1, 16, a="None") - _nx_digraph.add_edge(16, 1, a="None") - _nx_digraph.add_edge(16, 17, a="None") - _nx_digraph.add_edge(16, 18, a="None") + _nx_digraph.add_edge(1, 2, bridge="None") + _nx_digraph.add_edge(2, 1, bridge="None") + _nx_digraph.add_edge(2, 3, bridge="None") + _nx_digraph.add_edge(3, 4, bridge="None") + _nx_digraph.add_edge(4, 5, bridge="yes") + _nx_digraph.add_edge(5, 6, bridge="yes") + _nx_digraph.add_edge(6, 7, bridge="yes") + _nx_digraph.add_edge(7, 8, bridge="None") + _nx_digraph.add_edge(8, 9, bridge="None") + _nx_digraph.add_edge(8, 12, bridge="None") + _nx_digraph.add_edge(8, 13, bridge="yes") + _nx_digraph.add_edge(9, 10, bridge="None") + _nx_digraph.add_edge(10, 11, bridge="None") + _nx_digraph.add_edge(11, 12, bridge="yes") + _nx_digraph.add_edge(13, 14, bridge="yes") + _nx_digraph.add_edge(14, 15, bridge="yes") + _nx_digraph.add_edge(15, 11, bridge="yes") + _nx_digraph.add_edge(1, 16, bridge="None") + _nx_digraph.add_edge(16, 1, bridge="None") + _nx_digraph.add_edge(16, 17, bridge="None") + _nx_digraph.add_edge(16, 18, bridge="None") _nx_digraph = add_missing_geoms_graph(_nx_digraph, "geometry") _nx_digraph.graph["crs"] = "EPSG:4326" @@ -184,7 +184,7 @@ def add_edge_with_attributes( graph_to_shape: Graph | DiGraph, edge_u: int | float, edge_v: int | float, - a_value: str, + value_to_exclude: str, edge_node_ids: list, ) -> Graph | DiGraph: # Create a copy of the input graph @@ -198,7 +198,7 @@ def add_edge_with_attributes( shaped_graph.add_edge( edge_u, edge_v, - a=a_value, + bridge=value_to_exclude, from_node=edge_u, to_node=edge_v, geometry=LineString(geometry_list), @@ -268,7 +268,9 @@ def test_simplify_graph( expected_result_graph_fixture: nx.MultiDiGraph, ): network_simplification_with_attribute_exclusion.nx_graph = nx_digraph_factory() - network_simplification_with_attribute_exclusion.attributes_to_exclude = ["a"] + network_simplification_with_attribute_exclusion.attributes_to_exclude = [ + "bridge" + ] _graph_simple = network_simplification_with_attribute_exclusion.simplify_graph() From b63d5ad33f7917cb4238ad002d9cfadc4acdd9ea Mon Sep 17 00:00:00 2001 From: asgarpou Date: Mon, 16 Sep 2024 11:34:46 +0200 Subject: [PATCH 33/33] chore: network.ini updated for optimal route with hazard example --- examples/data/optimal_routes_analysis_with_hazard/network.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/data/optimal_routes_analysis_with_hazard/network.ini b/examples/data/optimal_routes_analysis_with_hazard/network.ini index 6e62313f4..a9446eeff 100644 --- a/examples/data/optimal_routes_analysis_with_hazard/network.ini +++ b/examples/data/optimal_routes_analysis_with_hazard/network.ini @@ -32,6 +32,6 @@ hazard_crs = EPSG:32736 [cleanup] snapping_threshold = None segmentation_length = None -merge_lines = True +merge_lines = False merge_on_id = False cut_at_intersections = False \ No newline at end of file