diff --git a/src/topupopt/data/gis/calculate.py b/src/topupopt/data/gis/calculate.py index 901ee9bed23b056c90353d6b96c3997284a9943a..2935702f8984ce949b9f7d48f560fdd5cf6ea787 100644 --- a/src/topupopt/data/gis/calculate.py +++ b/src/topupopt/data/gis/calculate.py @@ -19,43 +19,42 @@ from osmnx.projection import is_projected from ..gis import osm as osm from ..gis import identify as ident -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def arc_lengths(network: MultiDiGraph, - arc_keys: tuple = None) -> dict: +def edge_lengths(network: MultiDiGraph, edge_keys: tuple = None) -> dict: """ - Calculate arc lengths in a OSMnx-formatted MultiDiGraph network object. + Calculate edge lengths in a OSMnx-formatted MultiDiGraph network object. The calculation method changes depending on whether the coordinates are - projected and depending on whether the arcs are simplified. + projected and depending on whether the edges are simplified. Parameters ---------- network : MultiDiGraph The object describing the network. - arc_keys : tuple, optional - The keys for the arcs under consideration. The default is None, which - means all arcs in the network will be considered. + edge_keys : tuple, optional + The keys for the edges under consideration. The default is None, which + means all edges in the network will be considered. Returns ------- dict - A dictionary with the lengths for each arc. + A dictionary with the lengths for each edge. """ graph_is_projected = is_projected(network.graph['crs']) - # for each arc on the graph + # for each edge on the graph - if type(arc_keys) == type(None): + if type(edge_keys) == type(None): - arc_keys = network.edges(keys=True) # tuple(network.edges(keys=True)) + edge_keys = network.edges(keys=True) # tuple(network.edges(keys=True)) length_dict = {} - for arc_key in arc_keys: + for edge_key in edge_keys: # calculate it using the library @@ -63,12 +62,12 @@ def arc_lengths(network: MultiDiGraph, # calculate it using projected coordinates - if osm.KEY_OSMNX_GEOMETRY in network.edges[arc_key]: + if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # use geometry - length_dict[arc_key] = length( - network.edges[arc_key][osm.KEY_OSMNX_GEOMETRY] + length_dict[edge_key] = length( + network.edges[edge_key][osm.KEY_OSMNX_GEOMETRY] ) else: @@ -76,44 +75,44 @@ def arc_lengths(network: MultiDiGraph, # use (projected) coordinates start_point = Point( - (network.nodes[arc_key[0]][osm.KEY_OSMNX_X], - network.nodes[arc_key[0]][osm.KEY_OSMNX_Y]) + (network.nodes[edge_key[0]][osm.KEY_OSMNX_X], + network.nodes[edge_key[0]][osm.KEY_OSMNX_Y]) ) end_point = Point( - (network.nodes[arc_key[1]][osm.KEY_OSMNX_X], - network.nodes[arc_key[1]][osm.KEY_OSMNX_Y]) + (network.nodes[edge_key[1]][osm.KEY_OSMNX_X], + network.nodes[edge_key[1]][osm.KEY_OSMNX_Y]) ) - length_dict[arc_key] = start_point.distance(end_point) + length_dict[edge_key] = start_point.distance(end_point) else: # calculate it using unprojected coordinates (lat/long) - if osm.KEY_OSMNX_GEOMETRY in network.edges[arc_key]: + if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # use geometry - length_dict[arc_key] = great_circle_distance_along_path( - network.edges[arc_key][osm.KEY_OSMNX_GEOMETRY] + length_dict[edge_key] = great_circle_distance_along_path( + network.edges[edge_key][osm.KEY_OSMNX_GEOMETRY] ) else: # use (unprojected) coordinates - length_dict[arc_key] = great_circle( - lat1=network.nodes[arc_key[0]][osm.KEY_OSMNX_Y], - lon1=network.nodes[arc_key[0]][osm.KEY_OSMNX_X], - lat2=network.nodes[arc_key[1]][osm.KEY_OSMNX_Y], - lon2=network.nodes[arc_key[1]][osm.KEY_OSMNX_X] + length_dict[edge_key] = great_circle( + lat1=network.nodes[edge_key[0]][osm.KEY_OSMNX_Y], + lon1=network.nodes[edge_key[0]][osm.KEY_OSMNX_X], + lat2=network.nodes[edge_key[1]][osm.KEY_OSMNX_Y], + lon2=network.nodes[edge_key[1]][osm.KEY_OSMNX_X] ) return length_dict -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def great_circle_distance_along_path(path: LineString) -> float: """ @@ -151,8 +150,8 @@ def great_circle_distance_along_path(path: LineString) -> float: ) ) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def update_street_count(network: MultiDiGraph): """ @@ -170,16 +169,14 @@ def update_street_count(network: MultiDiGraph): """ # update street count - street_count_dict = count_streets_per_node(network) - network.add_nodes_from( ((key, {osm.KEY_OSMNX_STREET_COUNT:value}) for key, value in street_count_dict.items()) ) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def node_path_length(network: MultiDiGraph, path: list, @@ -187,7 +184,7 @@ def node_path_length(network: MultiDiGraph, """ Returns the length or lengths of a path defined using nodes. - If more than one arc connects adjacent nodes along the path, a length value + If more than one edge connects adjacent nodes along the path, a length value will be returned for each possible path combination. Parameters @@ -195,7 +192,7 @@ def node_path_length(network: MultiDiGraph, network : MultiDiGraph The object describing the network. path : list - A list of node keys or arc keys describing the path. + A list of node keys or edge keys describing the path. return_minimum_length_only : bool, optional If True, returns the minimum path length only. The default is True. @@ -207,113 +204,81 @@ def node_path_length(network: MultiDiGraph, """ # direction matters - path_length = len(path) - if path_length == 0: - return inf #************************************************************************** # if the path is given as a list of node keys, then it is subjective - # i.e., it may refer to many paths, namely if parallel arcs exist + # i.e., it may refer to many paths, namely if parallel edges exist # check if the path object qualifies as such - if not is_path(network, path): - # it does not, exit - if return_minimum_length_only: - return inf - else: - return [inf] - # prepare a list with all possible paths given as lists of arc keys - - list_of_arc_key_paths = [[]] # a list of arc key lists + # prepare a list with all possible paths given as lists of edge keys + list_of_edge_key_paths = [[]] # a list of edge key lists # for each pair of nodes in the path - for node_pair in range(path_length-1): - - # get the arcs between these two nodes - - arc_keys = ident.all_arcs_from_a_to_b(network, - path[node_pair], - path[node_pair+1]) - - number_arc_keys = len(arc_keys) - - if number_arc_keys == 1: - - # only one arc exists: append its key to all existing lists/paths - - for arc_key_path in list_of_arc_key_paths: - - arc_key_path.append(arc_keys[0]) - - else: # multiple arcs exist: each path identified so far has to be - # replicated a total of number_arc_keys times and then updated - - number_paths = len(list_of_arc_key_paths) - - # for each parallel arc - - for arc_key_index in range(number_arc_keys-1): - - # replicate all paths - - for path_index in range(number_paths): - - list_of_arc_key_paths.append( - list(list_of_arc_key_paths[path_index]) - ) - - # paths have been replicated, now add the arcs - - for arc_key_index in range(number_arc_keys): - - for path_index in range(number_paths): - - # add the new arc - - list_of_arc_key_paths[ - path_index+arc_key_index*number_paths + # get the edges between these two nodes + edge_keys = ident.get_edges_from_a_to_b( + network, + path[node_pair], + path[node_pair+1] + ) + number_edge_keys = len(edge_keys) + if number_edge_keys == 1: + # only one edge exists: append its key to all existing lists/paths + for edge_key_path in list_of_edge_key_paths: + edge_key_path.append(edge_keys[0]) + else: # multiple edges exist: each path identified so far has to be + # replicated a total of number_edge_keys times and then updated + number_paths = len(list_of_edge_key_paths) + # for each parallel edge + for edge_key_index in range(number_edge_keys-1): + # replicate all paths + for path_index in range(number_paths): + list_of_edge_key_paths.append( + list(list_of_edge_key_paths[path_index]) + ) + # paths have been replicated, now add the edges + for edge_key_index in range(number_edge_keys): + for path_index in range(number_paths): + # add the new edge + list_of_edge_key_paths[ + path_index+edge_key_index*number_paths ].append( - arc_keys[arc_key_index] + edge_keys[edge_key_index] ) - #************************************************************************** + # ************************************************************************* path_lenths = [ - sum(network.edges[arc_key][osm.KEY_OSMNX_LENGTH] - for arc_key in arc_key_path) - for arc_key_path in list_of_arc_key_paths - ] - - if return_minimum_length_only: - - return min(path_lenths) - - else: - + sum(network.edges[edge_key][osm.KEY_OSMNX_LENGTH] + for edge_key in edge_key_path) + for edge_key_path in list_of_edge_key_paths + ] + if return_minimum_length_only: + return min(path_lenths) + else: return path_lenths - #************************************************************************** + # ************************************************************************* -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def edge_path_length(network: MultiDiGraph, path: list, **kwargs) -> float: """ - Returns the total length of a path defined using arcs. + Returns the total length of a path defined using edges. If the path does not exist, or if no path is provided, the result will be infinity (math.inf). @@ -323,7 +288,7 @@ def edge_path_length(network: MultiDiGraph, network : MultiDiGraph The object describing the network. path : list - A list of arc keys describing the path. + A list of edge keys describing the path. Returns ------- @@ -343,7 +308,7 @@ def edge_path_length(network: MultiDiGraph, if ident.is_edge_path(network, path, **kwargs): return sum( - network.edges[arc_key][osm.KEY_OSMNX_LENGTH] for arc_key in path + network.edges[edge_key][osm.KEY_OSMNX_LENGTH] for edge_key in path ) else: @@ -352,8 +317,8 @@ def edge_path_length(network: MultiDiGraph, return inf -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def count_ocurrences(gdf: GeoDataFrame, column: str, @@ -439,5 +404,5 @@ def count_ocurrences(gdf: GeoDataFrame, return count_dict -#****************************************************************************** -#****************************************************************************** \ No newline at end of file +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file diff --git a/src/topupopt/data/gis/identify.py b/src/topupopt/data/gis/identify.py index c185296f6a92be135b69be25e089a5a2cb5a62e7..5f89cbfae1cc4ca6240bc5f5b571c75ac521175f 100644 --- a/src/topupopt/data/gis/identify.py +++ b/src/topupopt/data/gis/identify.py @@ -17,12 +17,33 @@ from .calculate import node_path_length from ..gis import osm -# TODO: change arcs to edges within the GIS modules - # ***************************************************************************** # ***************************************************************************** def is_edge_consistent_with_geometry(network: nx.MultiDiGraph, edge_key): + """ + Returns True if a given edge in an OSMnx-formatted graph is declared in the + same order as its geometry. That is, if the source node corresponds to the + first position in the geometry attribute and so forth. + + Parameters + ---------- + network : nx.MultiDiGraph + The object describing the graph. + edge_key : TYPE + The key for the edge under consideration. + + Raises + ------ + ValueError + Is raised if no matching edge can be found. + + Returns + ------- + bool + Returns True if the edge is declared consistently or False otherwise. + + """ # three cases: # no geometry: the edge is okay # geometry appears in the same order as the edge: the edge is okay @@ -47,9 +68,7 @@ def is_edge_consistent_with_geometry(network: nx.MultiDiGraph, edge_key): # ***************************************************************************** # ***************************************************************************** -def find_edges_in_reverse( - network: nx.MultiDiGraph, - ) -> dict: +def find_edges_in_reverse(network: nx.MultiDiGraph) -> dict: """ Finds edges in reverse within an OSMnx-formatted MultiDiGraph object. @@ -67,7 +86,7 @@ def find_edges_in_reverse( return { edge_key: [ other_edge_key - for other_edge_key in all_arcs_from_a_to_b( + for other_edge_key in get_edges_from_a_to_b( network, node_start=edge_key[1], node_end=edge_key[0] @@ -182,7 +201,7 @@ def edges_are_in_reverse( """ - # the arcs are the same but in reverse: + # the edges are the same but in reverse: # - all attributes have to be the same or lists with # the same content, as in a set, except for # the geometry and reversed attributes @@ -208,7 +227,7 @@ def edges_are_in_reverse( # incoherent inputs return False - # for each key, value pair in the forward arc's dict + # for each key, value pair in the forward edge's dict for attr_key, attr_value in fw_dict.items(): if (type(attr_value) == list and ((type(rv_dict[attr_key]) == list and @@ -231,7 +250,7 @@ def edges_are_in_reverse( type(rv_dict[attr_key]) != LineString)): # either the geometries are not reversed # or, there is no geometry attribute in the reverse dict - # or, the geometry in the reverse arc is not for a LineString + # or, the geometry in the reverse edge is not for a LineString # print('ping3:'+str(attr_key)) return False elif (attr_key == osm.KEY_OSMNX_GEOMETRY and @@ -279,8 +298,8 @@ def edges_are_in_reverse( # all other possibilities have been exhausted: return True return True -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def close_to_extremities( line: LineString, @@ -322,82 +341,50 @@ def close_to_extremities( """ # calculate the distances to the line - line_distances = line.distance(points) # identify the start and end points - start_point = Point(line.coords[0]) - end_point = Point(line.coords[-1]) # calculate the distances to the start and end points - start_distances = start_point.distance(points) - end_distances = end_point.distance(points) # for each point - _start = [] - _end = [] - for i, (line_distance, start_distance, end_distance) in enumerate( zip(line_distances, start_distances, end_distances)): - if start_distance < end_distance: - # the point is closer to the start point than to the end point - if abs(start_distance-line_distance) <= tolerance: - # the point is within range of the start point - _start.append(i) - elif start_distance > end_distance: - # the point is closer to the end point than to the start point - if abs(end_distance-line_distance) <= tolerance: - # the point is within range of the end point - _end.append(i) - else: - # the point is equidistant to the start and end points - if line_distance < end_distance: - # the point is closer to the line than to the start/end points - continue - - # if not - - if use_start_point_equidistant: - + # TODO: reach these statements + if use_start_point_equidistant: _start.append(i) - else: - _end.append(i) # return statement - if return_distances: - return _start, _end, line_distances, start_distances, end_distances - else: - return _start, _end -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def find_roundabouts(network: nx.MultiDiGraph, maximum_perimeter: float = None, @@ -451,153 +438,103 @@ def find_roundabouts(network: nx.MultiDiGraph, # 3) edges that do not have the necessary attributes # node number limits - there_are_upper_node_number_limits = ( True if maximum_number_nodes != None else False) - there_are_lower_node_number_limits = ( True if minimum_number_nodes != None else False) - there_are_node_number_limits = ( there_are_upper_node_number_limits or there_are_lower_node_number_limits) # perimeter limits - there_are_upper_perimeter_limits = ( True if maximum_perimeter != None else False) - there_are_lower_perimeter_limits = ( True if minimum_perimeter != None else False) - there_are_perimeter_limits = ( there_are_upper_perimeter_limits or there_are_lower_perimeter_limits) # find edges that are not one way - list_removable_edges = [] - for edge_key in new_network.edges(keys=True): - - arc_data_dict = new_network.get_edge_data(*edge_key) - - if osm.KEY_OSMNX_ONEWAY not in arc_data_dict: - - # no information about being oneway or not: not a oneway edge - + edge_data_dict = new_network.get_edge_data(*edge_key) + # if osm.KEY_OSMNX_ONEWAY not in edge_data_dict: + # # no information about being oneway or not: not a oneway edge + # # add edge to list + # list_removable_edges.append(edge_key) + # continue + # else: + # # there is information about the edge being oneway or not + # if not edge_data_dict[osm.KEY_OSMNX_ONEWAY]: + # # oneway attr is False: not a oneway edge + # # add edge to list + # list_removable_edges.append(edge_key) + # continue + # there is information about the edge being oneway or not + if not edge_data_dict[osm.KEY_OSMNX_ONEWAY]: + # oneway attr is False: not a oneway edge # add edge to list - list_removable_edges.append(edge_key) - continue - - else: - - # there is information about the arc being oneway or not - - if not arc_data_dict[osm.KEY_OSMNX_ONEWAY]: - - # oneway attr is False: not a oneway edge - - # add edge to list - - list_removable_edges.append(edge_key) - - continue - # it is a one way node... - # remove the edges - new_network.remove_edges_from(list_removable_edges) - # remove isolated nodes - remove_isolated_nodes(new_network) - # exclude self loops - list_selflooping_nodes = find_self_loops(new_network) - for node_key in list_selflooping_nodes: - while new_network.has_edge(u=node_key, v=node_key): - new_network.remove_edge(u=node_key, v=node_key) - #************************************************************************** + # ************************************************************************* + # ************************************************************************* # find loops - node_paths = nx.simple_cycles(new_network) - #************************************************************************** + # ************************************************************************* + # ************************************************************************* # exclude paths based on the perimeter and the number of nodes, if set - if not there_are_node_number_limits and not there_are_perimeter_limits: - return list(node_paths) - else: # each potential candidate needs to be checked - final_node_paths = [] - # for each node group - for node_path in node_paths: - if there_are_perimeter_limits: - # compute the total length for each node - total_length = node_path_length( network, node_path, return_minimum_length_only=True ) - if there_are_lower_perimeter_limits: - if total_length < minimum_perimeter: - continue - if there_are_upper_perimeter_limits: - if total_length > maximum_perimeter: - continue - if there_are_node_number_limits: - number_nodes_in_path = len(node_path) - if there_are_lower_node_number_limits: - if number_nodes_in_path < minimum_number_nodes: - continue - if there_are_upper_node_number_limits: - if number_nodes_in_path > maximum_number_nodes: - continue - # if none of the conditions was violated, add it the final list - final_node_paths.append(node_path) - # return the final list - return final_node_paths - #************************************************************************** + # ********************************************************************* + # ********************************************************************* -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def is_roundabout(network: nx.MultiDiGraph, path: list, @@ -651,45 +588,45 @@ def is_roundabout(network: nx.MultiDiGraph, # check if the last node connects to the first - arc_keys = all_arcs_from_a_to_b(network, + edge_keys = get_edges_from_a_to_b(network, path[-1], path[0]) - if len(arc_keys) == 0: + if len(edge_keys) == 0: return False else: - # among the arcs between them, find at least one compatible + # among the edges between them, find at least one compatible - compatible_arc_exists = False + compatible_edge_exists = False - for arc_key in arc_keys: + for edge_key in edge_keys: # get its data - arc_data_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) + edge_data_dict = network.get_edge_data(u=edge_key[0], + v=edge_key[1], + key=edge_key[2]) - # ensure that this arc has the oneway attribute + # ensure that this edge has the oneway attribute - if osm.KEY_OSMNX_ONEWAY in arc_data_dict: + if osm.KEY_OSMNX_ONEWAY in edge_data_dict: # ensure that it is true - if arc_data_dict[osm.KEY_OSMNX_ONEWAY]: + if edge_data_dict[osm.KEY_OSMNX_ONEWAY]: - compatible_arc_exists = True + compatible_edge_exists = True break - # check for compatible arcs + # check for compatible edges - if not compatible_arc_exists: + if not compatible_edge_exists: - # no compatible arcs exist between these two nodes + # no compatible edges exist between these two nodes return False @@ -697,37 +634,37 @@ def is_roundabout(network: nx.MultiDiGraph, for node_pair in range(len(path)-1): - # for each arc between them, find at least one compatible arc + # for each edge between them, find at least one compatible edge - compatible_arc_exists = False + compatible_edge_exists = False - for arc_key in all_arcs_from_a_to_b(network, + for edge_key in get_edges_from_a_to_b(network, path[node_pair], path[node_pair+1]): # get its data - arc_data_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) + edge_data_dict = network.get_edge_data(u=edge_key[0], + v=edge_key[1], + key=edge_key[2]) - # ensure that this arc has the oneway attribute + # ensure that this edge has the oneway attribute - if osm.KEY_OSMNX_ONEWAY in arc_data_dict: + if osm.KEY_OSMNX_ONEWAY in edge_data_dict: # ensure that it is true - if arc_data_dict[osm.KEY_OSMNX_ONEWAY]: + if edge_data_dict[osm.KEY_OSMNX_ONEWAY]: - compatible_arc_exists = True + compatible_edge_exists = True break - # check for compatible arcs + # check for compatible edges - if not compatible_arc_exists: + if not compatible_edge_exists: - # no compatible arcs exist between these two nodes + # no compatible edges exist between these two nodes return False @@ -735,16 +672,14 @@ def is_roundabout(network: nx.MultiDiGraph, return True -#****************************************************************************** -#****************************************************************************** - -# TODO: change name to get_multi_edge_tuples +# ***************************************************************************** +# ***************************************************************************** -def all_arcs_from_a_to_b(network: nx.MultiDiGraph, - node_start, - node_end) -> list: +def get_edges_from_a_to_b(network: nx.MultiDiGraph, + node_start, + node_end) -> list: """ - Retrieve the keys for arcs from one node to another. + Retrieve the keys for edges from one node to another. Parameters ---------- @@ -758,7 +693,7 @@ def all_arcs_from_a_to_b(network: nx.MultiDiGraph, Returns ------- list - A list of arc keys from the start to the end node. + A list of edge keys from the start to the end node. """ if network.has_edge(u=node_start, v=node_end): @@ -767,15 +702,15 @@ def all_arcs_from_a_to_b(network: nx.MultiDiGraph, else: return [] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def all_arcs_between_two_nodes(network: nx.MultiDiGraph, u, v) -> list: +def get_edges_between_two_nodes(network: nx.MultiDiGraph, u, v) -> list: """ - Retrieve the keys for all arcs involving two specific nodes. + Retrieve the keys for all edges involving two specific nodes. - The keys concern arcs in both directions. For a single direction, consider - using the method all_arcs_from_a_to_b instead. + The keys concern edges in both directions. For a single direction, consider + using the method get_edges_from_a_to_b instead. Parameters ---------- @@ -789,41 +724,41 @@ def all_arcs_between_two_nodes(network: nx.MultiDiGraph, u, v) -> list: Returns ------- list - A list of arc keys involving both nodes, in both directions. + A list of edge keys involving both nodes, in both directions. """ if network.has_edge(u, v): - # arcs exist from u to v + # edges exist from u to v _out = [(u,v,k) for k in network._adj[u][v]] try: - # try finding out if arcs exist from v to u + # try finding out if edges exist from v to u _out.extend([(v,u,k) for k in network._adj[v][u]]) except KeyError: - # arcs do not exist from v to u + # edges do not exist from v to u pass # return what was obtained return _out elif network.has_edge(v, u): - # arcs do not exist from u to v but exist from v to u + # edges do not exist from u to v but exist from v to u return [(v,u,k) for k in network._adj[v][u]] else: - # no arcs found + # no edges found return [] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def all_arcs_involving_node(network: nx.MultiDiGraph, +def get_edges_involving_node(network: nx.MultiDiGraph, node_key, - include_outgoing_arcs: bool = True, - include_incoming_arcs: bool = True, + include_outgoing_edges: bool = True, + include_incoming_edges: bool = True, include_self_loops: bool = True) -> list: """ - Retrieve the keys for all arcs involving a specific node. + Retrieve the keys for all edges involving a specific node. - The keys concern incoming and outgoing arcs. Optionally, the keys retrieved - can concern only incoming or outgoing arcs, self-loops included or not. + The keys concern incoming and outgoing edges. Optionally, the keys retrieved + can concern only incoming or outgoing edges, self-loops included or not. Parameters ---------- @@ -831,17 +766,17 @@ def all_arcs_involving_node(network: nx.MultiDiGraph, The object describing the network. node_key : hashable-type The key to the node under consideration. - include_outgoing_arcs : bool, optional - If True, outgoing arcs are considered. The default is True. - include_incoming_arcs : bool, optional - If True, incoming arcs are considered. The default is True. + include_outgoing_edges : bool, optional + If True, outgoing edges are considered. The default is True. + include_incoming_edges : bool, optional + If True, incoming edges are considered. The default is True. include_self_loops : bool, optional If True, self-loops are considered. The default is True. Returns ------- list - A list of arc keys involving the specified node. + A list of edge keys involving the specified node. """ @@ -849,19 +784,19 @@ def all_arcs_involving_node(network: nx.MultiDiGraph, edge_key for edge_key in network.edges(keys=True) if node_key in edge_key[0:2] - # outgoing arcs - if ((node_key != edge_key[0] and not include_outgoing_arcs) or - include_outgoing_arcs) - # incoming arcs - if ((node_key != edge_key[1] and not include_incoming_arcs) or - include_incoming_arcs) + # outgoing edges + if ((node_key != edge_key[0] and not include_outgoing_edges) or + include_outgoing_edges) + # incoming edges + if ((node_key != edge_key[1] and not include_incoming_edges) or + include_incoming_edges) # self-loops if ((edge_key[0] != edge_key[1] and not include_self_loops) or include_self_loops) ] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def neighbours(network: nx.MultiDiGraph or nx.MultiGraph, node_key, @@ -900,12 +835,19 @@ def neighbours(network: nx.MultiDiGraph or nx.MultiGraph, return nx.all_neighbors(network, node_key) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def is_node_path(network: nx.MultiDiGraph, path: list) -> bool: +def is_node_path( + network: nx.MultiDiGraph, + path: list, + consider_reversed_edges: bool = False) -> bool: """ Indicates if a given path qualifies as a node path in a directed network. + + A node path consists of a sequence of nodes connected by directed edges. + + The sequence must include at least two nodes. Parameters ---------- @@ -913,6 +855,9 @@ def is_node_path(network: nx.MultiDiGraph, path: list) -> bool: The network object. path : list The tentative node path. + consider_reversed_edges : bool, optional + If True, reversed edges can be used to identify a path. If False, only + edges in the travel direction will be considered. The default is False. Returns ------- @@ -920,13 +865,25 @@ def is_node_path(network: nx.MultiDiGraph, path: list) -> bool: Returns True if path is a node path and False otherwise. """ - if len(path) == 0: + if len(path) <= 1: return False + elif consider_reversed_edges: + # all nodes must exist in the network + for node in path: + if not network.has_node(node): + return False + # each pair of nodes must be connected, in one way or another + for n1, n2 in nx.utils.pairwise(path): + if network.has_edge(n1, n2) or network.has_edge(n2, n1): + continue + else: + return False + return True else: - return nx.is_path(network, path) + return nx.is_path(network, path) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def is_edge_path(network: nx.MultiDiGraph, path: list, @@ -1044,783 +1001,105 @@ def is_edge_path(network: nx.MultiDiGraph, return True -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def convert_edge_path(network: nx.MultiDiGraph, - path: list, - allow_reversed_edges: bool = False) -> list: +def is_path_straight(network: nx.MultiDiGraph, + path: list, + consider_reversed_edges: bool = False, + ignore_self_loops: bool = False) -> bool: """ - Converts a path of edge keys into a path of node keys. + Returns True if the path is straight and False otherwise. + + A path is defined to be straight if it presents no options along it. Parameters ---------- network : nx.MultiDiGraph The objet describing the network. path : list - A list of sequential edge keys that form a path. - allow_reversed_edges : bool, optional - If True, arcs in the opposite direction also count to form paths, as - long as the same nodes are involved. The default is False. + The list of nodes along the path. + consider_reversed_edges : bool, optional + If True, reversed edges can also be used to form paths. If False, only + edges in the stated direction will be considered. The default is False. + ignore_self_loops : bool, optional + If True, paths with self-loops can still be straight. If False, paths + containing self-loops cannot be straight. The default is False. Returns ------- - list - A list of node keys forming a path. + bool + A boolean indicating whether the path is straight or not. """ - if is_edge_path(network, - path, - ignore_edge_direction=allow_reversed_edges): - - # path is a sequence of edge keys: convert to node path - - if allow_reversed_edges: - - # reverse arcs are allowed - - # drop self-loops, if any - - edge_path = [ - edge_key - for edge_key in path - if edge_key[0] != edge_key[1] # exclude self loops - ] - - # if there is only one arc, the node path is straightforward - - if len(edge_path) == 1: - - return [edge_path[0][0], edge_path[0][1]] - - node_path = [] - - for edge_index, edge_key in enumerate(edge_path): - - # if there are no nodes yet on the path - - if len(node_path) == 0: - - # find out which node comes first - - if edge_key[0] in edge_path[1]: - - # the start node is in the second edge too: reversed - - node_path.append(edge_key[1]) - - node_path.append(edge_key[0]) - - else: # the arc is not reversed - - node_path.append(edge_key[0]) - - node_path.append(edge_key[1]) - - else: - - # find out which node comes after the previous node - - if node_path[-1] == edge_key[0]: - - # the start node is the same as the previous node - - node_path.append(edge_key[1]) - - else: - - # the end node is the same as the previous node - - node_path.append(edge_key[0]) - - else: - - # no reversed arcs - - node_path = [ - edge_key[0] - for edge_key in path - if edge_key[0] != edge_key[1] # exclude self loops - ] - - # add the last edge's end node - - node_path.append(path[-1][1]) + # confirm that it is a path + if not is_node_path(network, path, consider_reversed_edges): + return False - # return statement - - return node_path - - else: - - # not an edge path - - return [] - -#****************************************************************************** -#****************************************************************************** + # a straight path requires at least two nodes + path_length = len(path) + if path_length == 2: + return True # path with two nodes is always straight + + # check if the intermediate nodes have the right number of neighbours + for intermediate_node in path[1:-1]: + if len(set(neighbours( + network, + intermediate_node, + ignore_self_loops=ignore_self_loops)) + ) != 2: + # the path is not straight if the intermediate nodes do not have + # two distinct neighbours + return False + + # if all intermediate nodes have two neighbours, return True + return True -# TODO: split method into is_edge_path_simplifiable and is_node_path_simplifiable +# ***************************************************************************** +# ***************************************************************************** -def is_path_simplifiable(network: nx.MultiDiGraph, - path: list, - path_as_node_keys: bool = True, - ignore_self_loops: bool = True, - ignore_arc_directions: bool = True) -> bool: +def find_simplifiable_paths(network: nx.MultiDiGraph, + excluded_nodes: list, + ignore_self_loops: bool = False, + consider_reversed_edges: bool = False, + include_both_directions: bool = False) -> list: """ - Returns True if the path provided can be simplified and False otherwise. + Enumerates the simplifiable paths found in a given graph. + + A path is defined to be simplifiable if it presents no options along it + and involves at least three different nodes: two-node paths are straight + but are not simplifiable, with or without reversed edges. Parameters ---------- network : nx.MultiDiGraph - The objet describing the network. - path : list - The list of nodes along the path. If path_as_node_keys is set to False, - the path is instead described using a list of edge keys. - path_as_node_keys : bool, optional - If True, the path is provided as a list of node keys. If False, the - path is provided as a list of edge keys. The default is True. + The object describing the graph. + excluded_nodes : list + A list of keys for nodes that cannot be in any straight path. ignore_self_loops : bool, optional - If True, self-loops are ignored. If False, paths containing self-loops - cannot be simplified. The default is True. - ignore_arc_directions : bool, optional - If True, paths with arcs in different directions can still be - simplified. If False, the paths with arcs in different directions - cannot be simplified. The default is True. + If True, paths including self-loops can still be straight. If False, + paths including self-loops cannot be straight. The default is False. + consider_reversed_edges : bool, optional + If True, a straight path can include nodes connected in reverse. If + False, only edges in the stated direction will be considered. The + default is False. + include_both_directions : bool, optional + If True, and if reverse edges are allowed, simplifiable paths will + appear once per direction, otherwise just once. The default is False. Returns ------- - bool - A boolean indicating whether the path can be simplified or not. + list + A list of the straight paths in the graph. """ - # path format - - if path_as_node_keys: - - # the path is provided as a list of node keys - - # a simplifiable path must contain at least three nodes - - path_length = len(path) - - if path_length < 3: - - return False - - # verify that the elements are node keys in the network - - for node_key in path: - - if not network.has_node(node_key): - - return False - - # verify that there are arcs between each consecutive node pair - - for node_pair in range(path_length-1): - - if ignore_arc_directions: - - # check for arcs from A to B or vice-versa - - arc_keys = all_arcs_between_two_nodes(network, - path[node_pair], - path[node_pair+1]) - - # if there are no arcs between the two nodes, no path exists - - if len(arc_keys) == 0: - - return False - - else: - - # check for arcs from A to B - - if not network.has_edge(u=path[node_pair], - v=path[node_pair+1]): - - return False - - # verify that each node in the list, except the first and last, only - # has two neighbours and that these are before and after it in the path - - for node_index, node_key in enumerate(path): - - if node_index == 0 or node_index == path_length-1: - - continue - - # get the respective neighbours - - _neighbours = list( - neighbours( - network, node_key, ignore_self_loops=ignore_self_loops - ) - ) - - # check the number of neighbours - - if len(_neighbours) != 2: - - # if not two, the path is not straight - - return False - - # for each neighbour - - for neighbour in _neighbours: - - # # verify that it is adjacent to node_key - - # if (neighbour != path[node_index-1] and - # neighbour != path[node_index+1]): - - # return False - - assert (neighbour == path[node_index-1] or - neighbour == path[node_index+1]) - - #********************************************************************** - #********************************************************************** - - else: - - # the path is provided as a list of arc keys - - # there must be at least two arc keys: there must be a middle node - - path_length = len(path) - - if path_length < 2: - - return False # TODO: reach this statement - - # each arc key must have the (node_A, node_B, extra_arc_key) format - - node_path = [] - - for arc_key in path: - - # check format - - if len(arc_key) != 3: - - return False # TODO: reach this statement - - # check that it exists in the network - - if ignore_arc_directions: - - # verify that it exists as provided - - if not network.has_edge(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]): - - # if not, verify that it exists in the opposite direction - - if not network.has_edge(u=arc_key[1], - v=arc_key[0]): - # TODO: reach this statement - # if not either, then it does not exist - - return False - - else: - - # verify that it exists as provided - - if not network.has_edge(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]): - - # if not, then there is no link between the nodes - - return False # TODO: reach this statement - - #********************************************************************** - #********************************************************************** - - # otherwise, return True - - return True - -#****************************************************************************** -#****************************************************************************** - -def find_straight_paths(network: nx.MultiDiGraph, - excluded_nodes: list, - paths_must_be_oneway: bool = True, - paths_must_be_unique: bool = True): - # a straight path is a path where the intermediate nodes (all excluding the # first and the last) have to exist and have exactly 2 distinct neighbours - #************************************************************************** - - def find_path_case_a( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list - ) -> list: - - #paths_must_be_oneway=True - #paths_must_be_unique=True - # cycles have to involve 3 nodes, e.g. [0, 1, 2, 0] - # [0, 1, 0] is ruled out, since it is not oneway (2 anti-parallel arcs) - - def find_path_forward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[-1], ignore_self_loops=True) - ) - # check each neighbour - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(path[-1], a_neighbour) - n_reverse = network.number_of_edges(a_neighbour, path[-1]) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward == 1 and n_reverse == 0: - # the node is connected to the path via a forward e - path.append(a_neighbour) - return find_path_forward( - network, - list_valid_nodes, - path - ) - elif (a_neighbour == path[0] and - n_forward == 1 and n_reverse == 0): - # the neighbour matches the first node - # and there is a forward edge - # a cycle was found: finalise and return it - path.append(a_neighbour) - return path - elif n_forward == 1 and n_reverse == 0: - # one neighbour only: end node - path.append(a_neighbour) - return path - # all neighbours have been visited: return the current path - return path - - def find_path_backward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[0], ignore_self_loops=True) - ) - # check all neighbours (should be two at most) - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(a_neighbour, path[0]) - n_reverse = network.number_of_edges(path[0], a_neighbour) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward == 1 and n_reverse == 0: - # the node is connected to the path via a forward e - path.insert(0, a_neighbour) - return find_path_backward( - network, - list_valid_nodes, - path - ) - elif n_forward == 1 and n_reverse == 0: - # one neighbour only: start node - path.insert(0, a_neighbour) - return path - # all neighbours have been visited: return the current path - return path - - # explore paths in the forward sense - path = find_path_forward( - network, - list_valid_nodes, - path - ) - # check for cycles - if len(path) >= 3 and path[0] == path[-1]: - # it is a cycle: no need to search backwards - return path - # explore paths in the backward sense and return the path - return find_path_backward( - network, - list_valid_nodes, - path - ) - - #************************************************************************** - - def find_path_case_b( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list - ) -> list: - - #paths_must_be_oneway=True - #paths_must_be_unique=False - # cycles have to involve 3 nodes, e.g. [0, 1, 2, 0] - # [0, 1, 0] is ruled out, since it is not oneway (2 anti-parallel arcs) - - def find_path_forward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[-1], ignore_self_loops=True) - ) - # check each neighbour - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(path[-1], a_neighbour) - n_reverse = network.number_of_edges(a_neighbour, path[-1]) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward >= 1 and n_reverse == 0: - # the node is connected to the path via a forward edge - path.append(a_neighbour) - return find_path_forward( - network, - list_valid_nodes, - path - ) - elif (a_neighbour == path[0] and - n_forward >= 1 and n_reverse == 0): - # the neighbour matches the first node - # and there is a forward edge - # a cycle was found: finalise and return it - path.append(a_neighbour) - return path - elif n_forward >= 1 and n_reverse == 0: - # one neighbour only: end node - path.append(a_neighbour) - return path - # all neighbours have been visited: return the current path - return path - - def find_path_backward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[0], ignore_self_loops=True) - ) - # check all neighbours (should be two at most) - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(a_neighbour, path[0]) - n_reverse = network.number_of_edges(path[0], a_neighbour) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward >= 1 and n_reverse == 0: - # the node is connected to the path via a forward edge - path.insert(0, a_neighbour) - return find_path_backward( - network, - list_valid_nodes, - path - ) - elif n_forward >= 1 and n_reverse == 0: - # one neighbour only: start node - path.insert(0, a_neighbour) - return path - # all neighbours have been visited: return the current path - return path - - # explore paths in the forward sense - path = find_path_forward( - network, - list_valid_nodes, - path - ) - # check for cycles - if len(path) >= 3 and path[0] == path[-1]: - # it is a cycle: no need to search backwards - return path - # explore paths in the backward sense and return the path - return find_path_backward( - network, - list_valid_nodes, - path - ) - - #************************************************************************** - - def find_path_case_c( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list - ) -> list: - - #paths_must_be_oneway=False - #paths_must_be_unique=True - # [0,1,2,0] - # [0,1,0] is allowed since arcs anti-parallel arcs are allowed - # [1,0,1] is allowed since arcs anti-parallel arcs are allowed - # [0,1,0] and [1,0,1] should not be returned together - - #********************************************************************** - - def find_path_forward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[-1], ignore_self_loops=True) - ) - # check each neighbour - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(path[-1], a_neighbour) - n_reverse = network.number_of_edges(a_neighbour, path[-1]) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward + n_reverse == 1: - # either kind of edge will do - path.append(a_neighbour) - return find_path_forward( - network, - list_valid_nodes, - path - ) - elif (a_neighbour == path[0] and len(path) >= 3 and - n_forward + n_reverse == 1): - # the neighb. is in the path and matches the first node - # and there is only one edge - # a cycle was found: finalise and return it - path.append(a_neighbour) - return path - # if the neighbour is on the path and not a cycle, continue - elif n_forward + n_reverse == 1 and a_neighbour not in path: - # the neighbour is not on the list of valid nodes, id est, - # it has one neighbour only: start/end node - path.append(a_neighbour) - return path - # else: - # print('here4') - # path.insert(0, a_neighbour) - # return path - # all neighbours have been visited: return the current path - return path - - #********************************************************************** - - def find_path_backward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[0], ignore_self_loops=True) - ) - # check all neighbours (should be two at most) - for a_neighbour in current_neighbours: - # determine which kinds of edges it has - n_forward = network.number_of_edges(a_neighbour, path[0]) - n_reverse = network.number_of_edges(path[0], a_neighbour) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward + n_reverse == 1: - # the node is connected to the path via a forward e - path.insert(0, a_neighbour) - return find_path_backward( - network, - list_valid_nodes, - path - ) - elif n_forward + n_reverse == 1 and a_neighbour not in path: - # one neighbour only: start node - path.insert(0, a_neighbour) - return path - # all neighbours have been visited: return the current path - return path - - #********************************************************************** - - # explore paths in the forward sense - path = find_path_forward( - network, - list_valid_nodes, - path - ) - # check for cycles - if len(path) >= 3 and path[0] == path[-1]: - # it is a cycle: no need to search backwards - return path - # explore paths in the backward sense and return the path - return find_path_backward( - network, - list_valid_nodes, - path - ) - - #************************************************************************** - - def find_path_case_d( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list - ) -> list: - - # paths_must_be_oneway=False - # paths_must_be_unique=False - # cycles have to involve 3 nodes, e.g. [0, 1, 2, 0] - # [0, 1, 0] is ruled out, since it is not oneway (2 anti-parallel arcs) - - #********************************************************************** - - def find_path_forward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[-1], ignore_self_loops=True) - ) - # print('current neighbours: '+str(current_neighbours)) - # check each neighbour - for a_neighbour in current_neighbours: - # print('neighbour: '+str(a_neighbour)) - # print('current path: '+str(path)) - # determine which kinds of edges it has - n_forward = network.number_of_edges(path[-1], a_neighbour) - n_reverse = network.number_of_edges(a_neighbour, path[-1]) - # print('n_forward: '+str(n_forward)) - # print('n_reverse: '+str(n_reverse)) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - - if a_neighbour not in path: - # the node is not on the path - if n_forward + n_reverse >= 1: - # either kind of edge will do - # print('here') - path.append(a_neighbour) - return find_path_forward( - network, - list_valid_nodes, - path - ) - elif (a_neighbour == path[0] and len(path) >= 3 and - n_forward + n_reverse >= 1): - # the neighb. is in the path and matches the first node - # and there is only one edge - # a cycle was found: finalise and return it - # print('here2') - path.append(a_neighbour) - return path - # print('here23') - # if the neighbour is on the path and not a cycle, continue - elif n_forward + n_reverse >= 1 and a_neighbour not in path: - # the neighbour is not on the list of valid nodes, id est, - # it has one neighbour only: start/end node - # print('here3') - path.append(a_neighbour) - return path - # print('here34') - # else: - # print('here4') - # path.insert(0, a_neighbour) - # return path - # all neighbours have been visited: return the current path - # print('here4') - return path - - #********************************************************************** - - def find_path_backward(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list): - # identify the last node's neighbours - current_neighbours = set( - neighbours(network, path[0], ignore_self_loops=True) - ) - # print('current neighbours: '+str(current_neighbours)) - # check all neighbours (should be two at most) - for a_neighbour in current_neighbours: - # print('neighbour: '+str(a_neighbour)) - # print('current path: '+str(path)) - # determine which kinds of edges it has - n_forward = network.number_of_edges(a_neighbour, path[0]) - n_reverse = network.number_of_edges(path[0], a_neighbour) - # print('n_forward: '+str(n_forward)) - # print('n_reverse: '+str(n_reverse)) - if a_neighbour in list_valid_nodes: - # two neighbours, one of which must be the current node - # this means there is at least another node to be added - # which requires recursion, unless there is a loop - if a_neighbour not in path: - # the node is not on the path - if n_forward + n_reverse >= 1: - # print('here5') - # the node is connected to the path via a forward e - path.insert(0, a_neighbour) - return find_path_backward( - network, - list_valid_nodes, - path - ) - elif n_forward + n_reverse >= 1 and a_neighbour not in path: - # print('here6') - # one neighbour only: start node - path.insert(0, a_neighbour) - return path - # print('here67') - # all neighbours have been visited: return the current path - # print('here7') - return path - - #********************************************************************** - # print('path:'+str(path)) - # explore paths in the forward sense - path = find_path_forward( - network, - list_valid_nodes, - path - ) - # print('post-forward search path:'+str(path)) - # check for cycles - if len(path) >= 3 and path[0] == path[-1]: - # it is a cycle: no need to search backwards - return path - # explore paths in the backward sense and return the path - return find_path_backward( - network, - list_valid_nodes, - path - ) - - #************************************************************************** + # ************************************************************************* + # ************************************************************************* # locate all the non-excluded nodes that can form straight paths @@ -1829,7 +1108,7 @@ def find_straight_paths(network: nx.MultiDiGraph, for node_key in network.nodes() # the node cannot be among those excluded if node_key not in excluded_nodes - # the node can only be linked to two other nodes other than itself + # the node has to be linked to two other nodes other than itself if len(set( neighbours( network, @@ -1837,12 +1116,15 @@ def find_straight_paths(network: nx.MultiDiGraph, ignore_self_loops=True ) )) == 2 + # exclude nodes with self-loops if desired: + # 1) self-loops are tolerated (no need to check) + # 2) self-loops are not tolerated and they do not exist + if (ignore_self_loops or + (not ignore_self_loops and + not network.has_edge(node_key, node_key))) ] - # print('******************************************************************') - # print('excluded_nodes:'+str(excluded_nodes)) - # print('intermediate_candidate_nodes:'+str(intermediate_candidate_nodes)) - - #************************************************************************** + + # ************************************************************************* # find out paths around the nodes identified @@ -1851,56 +1133,30 @@ def find_straight_paths(network: nx.MultiDiGraph, list_nodes_joined = [] - for i, candidate_node in enumerate(intermediate_candidate_nodes): - # print('-------------------------------------------------------------') - # print('candidate_node: '+str(candidate_node)) - # if node is already in previous paths, skip + # try to form paths around the candidate nodes + for candidate_node in intermediate_candidate_nodes: + + # skip if the node is already in a path if candidate_node in list_nodes_joined: continue - # a neighbour can extend the path under the following conditions: - # i) if it is among the valid nodes (has 2 neighbours only); - # ii) if it is not on the path already or it is its first node for a - # path of at least 3 nodes (with the current neighbour included) - # iii) - # a) with paths_must_be_oneway=True and paths_must_be_unique=True, if - # only one forward edge links the last node and the neighbour - # b) with paths_must_be_oneway=True and paths_must_be_unique=False, if - # no reverse edges link the last node and the neighbour - # c) with paths_must_be_oneway=False and paths_must_be_unique=True, if - # there is one forward or one reverse edge linking the nodes - # d) with paths_must_be_oneway=False and paths_must_be_unique=False, if - # there are any edges linking the last node and the neighbour - - if paths_must_be_oneway and paths_must_be_unique: - # case a - new_sequence = find_path_case_a( + # select method + if consider_reversed_edges: + # reversed edges are accepted + new_sequence = _find_path_direction_insensitive( network, - intermediate_candidate_nodes, - [candidate_node] + list_valid_nodes=intermediate_candidate_nodes, + start_node=candidate_node ) - elif paths_must_be_oneway and not paths_must_be_unique: - # case b - new_sequence = find_path_case_b( - network, - intermediate_candidate_nodes, - [candidate_node] - ) - elif not paths_must_be_oneway and paths_must_be_unique: - # case c - new_sequence = find_path_case_c( - network, - intermediate_candidate_nodes, - [candidate_node] - ) else: - # case d - new_sequence = find_path_case_d( + # reversed edges are not accepted + new_sequence = _find_path_direction_sensitive( network, - intermediate_candidate_nodes, - [candidate_node] + list_valid_nodes=intermediate_candidate_nodes, + start_node=candidate_node ) - # print('new_sequence: '+str(new_sequence)) + + # make sure the sequence is not redundant if (len(new_sequence) <= 2 or new_sequence in list_paths or set(new_sequence) in list_paths_nodes): @@ -1910,262 +1166,286 @@ def find_straight_paths(network: nx.MultiDiGraph, # add the path list_paths.append(new_sequence) list_paths_nodes.append(set(new_sequence)) - + if consider_reversed_edges and include_both_directions: + # directions do not matter: + list_paths.append(new_sequence[::-1]) + # update the list of intermediate nodes already on paths list_nodes_joined.extend( (element for element in new_sequence[1:-1] if element != candidate_node) # used to keep it shorter? ) - #************************************************************************** + # ************************************************************************* + # ************************************************************************* return list_paths - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def find_all_straight_paths(network: nx.MultiDiGraph, - excluded_nodes: list, - return_paths_as_arc_keys: bool = False, - pick_shortest_parallel_edge: bool = True): + # ************************************************************************* + # ************************************************************************* - # TODO: pick shortest parallel edge - - #************************************************************************** - - # locate all the non-excluded nodes that form straight paths, that is, - # nodes that only have arcs with two other nodes - - removable_nodes = [] - - # for each node in the network - - for node_key in network.nodes(): - - # check if node_key is in the list of nodes not to be considered - - if node_key in excluded_nodes: - - continue - - # identify the node's neighbours (while ignoring self-loops) - - _node_neighbours = list( - neighbours(network, node_key, ignore_self_loops=True) +# ***************************************************************************** +# ***************************************************************************** + +def _find_path_direction_sensitive( + network: nx.MultiDiGraph, + list_valid_nodes: list, + start_node + ) -> list: + + def find_path_forward(network: nx.MultiDiGraph, + current_node, + path: list): + # identify the last node's neighbours + current_neighbours = set( + neighbours(network, current_node, ignore_self_loops=True) ) - - # if the node only has two neighbours - - if len(_node_neighbours) == 2: - - # store the node's key - - removable_nodes.append(node_key) - - #************************************************************************** - - # TODO: make the method work with ignore_arc_directions=False - - def get_path_valid_nodes(network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list, - ignore_arc_directions: bool = True) -> list: - - # forward direction - - path = get_path_valid_nodes_forward(network, - list_valid_nodes, - path, - ignore_arc_directions) - - # backward direction - - path = get_path_valid_nodes_backward(network, - list_valid_nodes, - path, - ignore_arc_directions) - - if len(path) == 1: - - _neighbours = list(neighbours(network, path[0])) - - path.insert(0, _neighbours[0]) - path.append(_neighbours[1]) - - return path - - # add the ends - - for neighbour in neighbours(network, path[0]): - - if neighbour not in path: - - path.insert(0, neighbour) - - break - - for neighbour in neighbours(network, path[-1]): - - if neighbour not in path or neighbour == path[0]: - - path.append(neighbour) - - break - + # check each neighbour + for a_neighbour in current_neighbours: + # check the direction of edge towards the neighbour + if network.has_edge(current_node, a_neighbour): + # forward edge + # 1) node is not on the path + # 1.1) is a valid node >> path continues + # 1.2) is not a valid node >> end of path + # 2) node is on the path already + # 2.1) matches the start node = cycle + # 2.2) does not match the start node (reversed edge) = ignore + if a_neighbour not in path: + # neighbour is not on the path + if a_neighbour in list_valid_nodes: + # is a valid node: path continues + # add the neighbour to the end of the path + path.append(a_neighbour) + # recursive call with extended path + return find_path_forward( + network, + path[-1], + path + ) + else: # is not a valid node: path ends + # add the neighbour to the end of the path: + path.append(a_neighbour) + # return the path + return path + elif a_neighbour == path[0]: + # neighbour is already on the path and matches the start + # add the neighbour to the end of the path: + path.append(a_neighbour) + # return the path + return path + # all neighbours have been visited: return the current path return path - def get_path_valid_nodes_forward( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list, - ignore_arc_directions: bool = True) -> list: - - _node_neighbours = list( - neighbours(network, path[-1], ignore_self_loops=True) + def find_path_backward(network: nx.MultiDiGraph, + current_node, + path: list): + # identify the last node's neighbours + current_neighbours = set( + neighbours(network, current_node, ignore_self_loops=True) ) - - for node_neighbour in _node_neighbours: - - # if it is a valid node - - if node_neighbour in list_valid_nodes: - - # if it not in the path - - if node_neighbour not in path: - - # neighbour is not on the path yet, and is valid - - # append it to the list - - path.append(node_neighbour) - - # call method again - - return get_path_valid_nodes_forward(network, - list_valid_nodes, - path) - + # check each neighbour + # 1) if the neighbour is ahead and is a valid node: + # >> recursion w/ neighbour and then return the final path + # 2) if the neighbour is ahead and is not a valid node: + # >> add it to the path and then return it + # 3) if the neighbour is not ahead and is on the path: + # >> continue, namely to check the other neighbour + # 4) if the neighbour is not ahead and is not on the path: + # >> add it to the beginning of the path and continue + + # check each neighbour + for a_neighbour in current_neighbours: + # check the direction of edge towards the neighbour + if network.has_edge(a_neighbour, current_node): + # backward edge + # 1) node is not on the path + # 1.1) is a valid node >> path continues + # 1.2) is not a valid node >> end of path + # 2) node is on the path already + # 2.1) matches the start node = cycle + # 2.2) does not match the start node (reversed edge) = ignore + if a_neighbour not in path: + # neighbour is not on the path + if a_neighbour in list_valid_nodes: + # is a valid node: path continues + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # recursive call with extended path + return find_path_backward( + network, + path[0], + path + ) + else: # is not a valid node: path ends + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # return the path + return path + elif a_neighbour == path[-1] and len(path) >= 3: + # neighbour is already on the path, matches the end, and + # the path length is greater than or equal to 3 + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # return the path + return path + # all neighbours have been visited: return the current path return path - - def get_path_valid_nodes_backward( - network: nx.MultiDiGraph, - list_valid_nodes: list, - path: list, - ignore_arc_directions: bool = True) -> list: - - _node_neighbours = list( - neighbours(network, path[0], ignore_self_loops=True) - ) - - for node_neighbour in _node_neighbours: - - if node_neighbour in list_valid_nodes: - - # node is already known - - if node_neighbour not in path: - - # neighbour is not on the path yet, and is valid - - # append it to the list - - path.insert(0,node_neighbour) - - # call method again - - return get_path_valid_nodes_backward(network, - list_valid_nodes, - path) - + + # ************************************************************************* + + # find the path forward, check for cycles and then find the path backwards + # find the forward path segment + path = find_path_forward( + network, + start_node, + [start_node] + ) + # cycles have to be detected on the first try + if len(path) >= 3 and path[0] == path[-1]: + # it is a cycle: no need to seedgeh backwards return path - - #************************************************************************** - - # find out paths around the nodes identified + # find the backward path segment + return find_path_backward( + network, + path[0], + path + ) - list_paths = [] + # ************************************************************************* + +# ***************************************************************************** +# ***************************************************************************** - list_nodes_joined = [] +def _find_path_direction_insensitive( + network: nx.MultiDiGraph, + list_valid_nodes: list, + start_node + ) -> list: - for i, node in enumerate(removable_nodes): - - # if node is already in previous paths, skip - - if node in list_nodes_joined: - - continue - - #********************************************************************** - - # get sequence of valid nodes - - new_sequence = get_path_valid_nodes(network, - removable_nodes, - [node]) - - # add the ends - - list_paths.append(new_sequence) + def find_path_forward(network: nx.MultiDiGraph, + current_node, + path: list): - list_nodes_joined.extend( - (element - for element in new_sequence[1:-1] - if element != node) + # identify the last node's neighbours + current_neighbours = set( + neighbours(network, current_node, ignore_self_loops=True) ) - - # print('new run:') - # print('iteration: '+str(i)) - # print('node: '+str(node)) - # print('paths: '+str(list_paths)) - # print('nodes joined: '+str(list_nodes_joined)) - - #************************************************************************** + # check each neighbour + for a_neighbour in current_neighbours: + # check the direction of edge towards the neighbour: + # 1) node is not on the path + # 1.1) is a valid node >> path continues + # 1.2) is not a valid node >> end of path + # 2) node is on the path already + # 2.1) matches the start node = cycle + # 2.2) does not match the start node (reversed edge) = ignore + if a_neighbour not in path: + # neighbour is not on the path + if a_neighbour in list_valid_nodes: + # is a valid node: path continues + # add the neighbour to the end of the path + path.append(a_neighbour) + # recursive call with extended path + return find_path_forward( + network, + path[-1], + path + ) + else: # is not a valid node: path ends + # add the neighbour to the end of the path: + path.append(a_neighbour) + # return the path + return path + elif a_neighbour == path[0] and len(path) >= 3: + # neighbour is already on the path and matches the start, and + # the path length is greater than or equal to 3 + # add the neighbour to the end of the path: + path.append(a_neighbour) + # return the path + return path + # all neighbours have been visited: return the current path + return path - if return_paths_as_arc_keys: + def find_path_backward(network: nx.MultiDiGraph, + current_node, + path: list): - # transform list of node lists into list of arc key lists + # identify the last node's neighbours + current_neighbours = set( + neighbours(network, current_node, ignore_self_loops=True) + ) + # check each neighbour + # 1) if the neighbour is ahead and is a valid node: + # >> recursion w/ neighbour and then return the final path + # 2) if the neighbour is ahead and is not a valid node: + # >> add it to the path and then return it + # 3) if the neighbour is not ahead and is on the path: + # >> continue, namely to check the other neighbour + # 4) if the neighbour is not ahead and is not on the path: + # >> add it to the beginning of the path and continue + + # check each neighbour + for a_neighbour in current_neighbours: + # check the direction of edge towards the neighbour + # 1) node is not on the path + # 1.1) is a valid node >> path continues + # 1.2) is not a valid node >> end of path + # 2) node is on the path already + # 2.1) matches the start node = cycle + # 2.2) does not match the start node (reversed edge) = ignore + if a_neighbour not in path: + # neighbour is not on the path + if a_neighbour in list_valid_nodes: + # is a valid node: path continues + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # recursive call with extended path + return find_path_backward( + network, + path[0], + path + ) + else: # is not a valid node: path ends + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # return the path + return path + elif a_neighbour == path[-1] and len(path) >= 3: + # neighbour is already on the path, matches the end, and + # the path length is greater than or equal to 3 + # add the neighbour to the start of the path + path.insert(0, a_neighbour) + # return the path + return path + # all neighbours have been visited: return the current path + return path - list_path_arcs = [] - - for path in list_paths: - - temp_path_arcs = [] - - for node_pair_index in range(len(path)-1): - - arcs = all_arcs_from_a_to_b(network, - path[node_pair_index], - path[node_pair_index+1]) - - if len(arcs) == 0: - - arcs = all_arcs_from_a_to_b(network, - path[node_pair_index+1], - path[node_pair_index]) - - # if len(arcs) == 0: - # print(list_paths) - # print(path) - # print((path[node_pair_index],path[node_pair_index+1])) - # print((path[node_pair_index+1],path[node_pair_index])) - - temp_path_arcs.append( - arcs[0] - ) - - list_path_arcs.append(temp_path_arcs) - - return list_path_arcs - - else: + # ************************************************************************* - return list_paths + # find the path forward, check for cycles and then find the path backwards + # explore paths in the forward sense + path = find_path_forward( + network, + start_node, + [start_node] + ) + # check for cycles + if len(path) >= 3 and path[0] == path[-1]: + # it is a cycle: no need to search backwards + return path + # explore paths in the backward sense and return the path + return find_path_backward( + network, + path[0], + path + ) -#****************************************************************************** -#****************************************************************************** + # ************************************************************************* + # ************************************************************************* + +# ***************************************************************************** +# ***************************************************************************** def find_self_loops(network: nx.MultiDiGraph) -> list: """ @@ -2189,8 +1469,8 @@ def find_self_loops(network: nx.MultiDiGraph) -> list: if network.has_edge(u=node_key, v=node_key) ) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def find_unconnected_nodes(network: nx.MultiDiGraph) -> list: """ @@ -2216,8 +1496,8 @@ def find_unconnected_nodes(network: nx.MultiDiGraph) -> list: if len(tuple(network.neighbors(node_key))) == 0 ] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def nearest_nodes_other_than_themselves(network: nx.MultiDiGraph, node_keys: list, @@ -2266,8 +1546,8 @@ def nearest_nodes_other_than_themselves(network: nx.MultiDiGraph, return nearest_node_keys -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def is_start_or_end_point_or_close(line: LineString, point: Point, @@ -2323,8 +1603,8 @@ def is_start_or_end_point_or_close(line: LineString, return False -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def is_start_or_end_point(line: LineString, point: Point) -> bool: @@ -2363,12 +1643,12 @@ def is_start_or_end_point(line: LineString, # ***************************************************************************** # ***************************************************************************** -def identify_arc_closest_to_node( +def identify_edge_closest_to_node( network: nx.MultiDiGraph, node_keys: list, crs: str = None) -> Tuple[list, nx.MultiDiGraph]: """ - Identify the arcs that are closest to a given set of nodes. + Identify the edges that are closest to a given set of nodes. The network object should formatted according to OSMnx standards. @@ -2387,8 +1667,8 @@ def identify_arc_closest_to_node( Returns ------- - nearest_arc_keys : list - The list of keys for the arcs that are closest to the nodes (1:1). + nearest_edge_keys : list + The list of keys for the edges that are closest to the nodes (1:1). network : nx.MultiDiGraph The object for the projected network. @@ -2397,7 +1677,7 @@ def identify_arc_closest_to_node( #************************************************************************** # 1) ensure that the network crs is correct and convert if not - # 2) identify the arcs that are nearest to the nodes + # 2) identify the edges that are nearest to the nodes # 3) revert network crs back to the original, if necessary #************************************************************************** @@ -2416,9 +1696,9 @@ def identify_arc_closest_to_node( #************************************************************************** - # 2) identify the arcs that are nearest to the nodes + # 2) identify the edges that are nearest to the nodes - nearest_arc_keys = nearest_edges( + nearest_edge_keys = nearest_edges( projected_network, X=[projected_network.nodes[node_key][osm.KEY_OSMNX_X] for node_key in node_keys], @@ -2428,7 +1708,7 @@ def identify_arc_closest_to_node( # return statement - return nearest_arc_keys, projected_network + return nearest_edge_keys, projected_network # ***************************************************************************** # ***************************************************************************** \ No newline at end of file diff --git a/src/topupopt/data/gis/modify.py b/src/topupopt/data/gis/modify.py index d041e92f0c649ba15757f644421fc4ad58d12567..643e7cc08c2972ec011afac0b798736d8516da2b 100644 --- a/src/topupopt/data/gis/modify.py +++ b/src/topupopt/data/gis/modify.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - # standard library imports -import math - # from typing import Tuple # local, external @@ -25,10 +21,10 @@ from ..gis import osm from ..gis import identify as gis_iden # from ..gis import identify as gis_calc from .identify import close_to_extremities -from .calculate import update_street_count, arc_lengths +from .calculate import update_street_count, edge_lengths -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def remove_self_loops(network: nx.MultiDiGraph): """ @@ -52,8 +48,8 @@ def remove_self_loops(network: nx.MultiDiGraph): network.remove_edge(u=node, v=node) return selflooping_nodes -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def transform_roundabouts_into_crossroads( network: nx.MultiDiGraph, @@ -169,8 +165,8 @@ def transform_roundabouts_into_crossroads( for other_node_key in gis_iden.neighbours(network, node_key) # if it is not in the roundabout itself if other_node_key not in roundabout - # for each arc between the two nodes - for edge_key in gis_iden.all_arcs_between_two_nodes( + # for each edge between the two nodes + for edge_key in gis_iden.get_edges_between_two_nodes( network, node_key, other_node_key) @@ -208,9 +204,9 @@ def transform_roundabouts_into_crossroads( old_length = edge_dict[osm.KEY_OSMNX_LENGTH] else: # length does not exist - old_length = arc_lengths( + old_length = edge_lengths( network, - arc_keys=[edge_key])[edge_key] + edge_keys=[edge_key])[edge_key] # # calculate it # old_length = great_circle( @@ -326,8 +322,8 @@ def transform_roundabouts_into_crossroads( #************************************************************************** #************************************************************************** -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # TODO: develop algorithm to traverse the graph in search of dead ends @@ -358,19 +354,13 @@ def remove_dead_ends(network: nx.MultiDiGraph, """ if type(keepers) == type(None): - keepers = [] # while true - nodes_removed = [] - iteration_counter = 0 - while iteration_counter < max_iterations: - # find nodes that can be excluded and that connect to only 1 other node - # it is necessary to rule out that more one 1 node is involved target_nodes = [ node_key @@ -383,152 +373,31 @@ def remove_dead_ends(network: nx.MultiDiGraph, ignore_self_loops=True) )) <= 1 ] - # if there no nodes meeting those conditions, break out of loop if len(target_nodes) == 0: break - # otherwise, find straight paths from these nodes - # remove all the nodes in these paths network.remove_nodes_from(target_nodes) - # increment iteration counter iteration_counter += 1 - # store the nodes removed nodes_removed.extend(target_nodes) - # return the list of nodes removed return nodes_removed - -#****************************************************************************** -#****************************************************************************** - -def convert_edge_path(network: nx.MultiDiGraph, - path: list, - allow_reversed_edges: bool = False) -> list: - """ - Converts a path of edge keys into a path of node keys. - - Parameters - ---------- - network : nx.MultiDiGraph - The objet describing the network. - path : list - A list of sequential edge keys that form a path. - allow_reversed_edges : bool, optional - If True, arcs in the opposite direction also count to form paths, as - long as the same nodes are involved. The default is False. - - Returns - ------- - list - A list of node keys forming a path. - - """ - - if gis_iden.is_edge_path( - network, - path, - allow_reversed_edges=allow_reversed_edges): - - # path is a sequence of edge keys: convert to node path - - if allow_reversed_edges: - - # reverse arcs are allowed - - # drop self-loops, if any - - edge_path = [ - edge_key - for edge_key in path - if edge_key[0] != edge_key[1] # exclude self loops - ] - - # if there is only one arc, the node path is straightforward - - if len(edge_path) == 1: - - return [edge_path[0][0], edge_path[0][1]] - - node_path = [] - - for edge_index, edge_key in enumerate(edge_path): - - # if there are no nodes yet on the path - - if len(node_path) == 0: - - # find out which node comes first - - if edge_key[0] in edge_path[1]: - - # the start node is in the second edge too: reversed - - node_path.append(edge_key[1]) - - node_path.append(edge_key[0]) - - else: # the arc is not reversed - - node_path.append(edge_key[0]) - - node_path.append(edge_key[1]) - - else: - - # find out which node comes after the previous node - - if node_path[-1] == edge_key[0]: - - # the start node is the same as the previous node - - node_path.append(edge_key[1]) - - else: - - # the end node is the same as the previous node - - node_path.append(edge_key[0]) - - else: - - # no reversed arcs - - node_path = [ - edge_key[0] - for edge_key in path - if edge_key[0] != edge_key[1] # exclude self loops - ] - - # add the last edge's end node - - node_path.append(path[-1][1]) - - # return statement - - return node_path - - else: - - # not an edge path - - return [] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def replace_path( network: nx.MultiDiGraph, - path: list, - ignore_directions: bool = False + path: list ) -> tuple: """ - Replaces a path of three or more nodes with one edge linking both ends. + Replaces a simplifiable path with one equivalent edge linking both ends. - The path must be given as an ordered list of node keys. + If there are parallel or anti-parallel edges along the path, only one will + be used to create the new edge. There should only be one edge between each of the nodes on the path, since only one will be used to create the new edge. By default, the edges between @@ -543,9 +412,6 @@ def replace_path( The object describing the network graph. path : list A list of keys for the nodes on the path. - ignore_directions : bool, optional - If True, the edges along the path can be in any direction. If False, - they have to be in the forward sense. The default is False. Raises ------ @@ -561,9 +427,14 @@ def replace_path( # ************************************************************************* - # make sure path is a path - if not ignore_directions and not nx.is_path(network, path): - raise ValueError('No valid path was provided.') + # make sure path it is a simplifiable path + if not gis_iden.is_path_straight( + network, + path, + consider_reversed_edges=True, + ignore_self_loops=True + ): + raise ValueError('The path cannot be simplified.') # ************************************************************************* @@ -579,19 +450,16 @@ def replace_path( for node_pair_index in range(len(path)-1): - if ignore_directions: - # edge directions can be ignored - edge_key = list(gis_iden.all_arcs_between_two_nodes( - network, - path[node_pair_index], - path[node_pair_index+1] - ))[0] - else: # edge directions cannot be ignored - edge_key = list(gis_iden.all_arcs_from_a_to_b( - network, - path[node_pair_index], - path[node_pair_index+1] - ))[0] + # get one edge for this node pair + edge_key = list(gis_iden.get_edges_between_two_nodes( + network, + path[node_pair_index], + path[node_pair_index+1] + )) + edge_key = sorted( + (network.edges[_key][osm.KEY_OSMNX_LENGTH], _key) + for _key in edge_key + )[0][1] if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # geometry exists: possibly a composite geometry @@ -646,35 +514,27 @@ def replace_path( ) # update the edge length - edge_length += network.edges[edge_key][osm.KEY_OSMNX_LENGTH] # merge the geometries new_geo = linemerge(list_geometries) # verify that it led to the creation of a linestring object - - try: - assert type(new_geo) == LineString - except AssertionError: + if type(new_geo) != LineString: + # TODO: make sure this is still relevant and add tests # if not a linestring, it is a multistring - # assert type(new_geo) == MultiLineString - list_geometries = [] - # snap each consecutive geometry pair in the MultiLineString object # since linemerge separates linestrings that are not contiguous - for geo_pair_index in range(len(new_geo.geoms)-1): - list_geometries.append( - snap(new_geo.geoms[geo_pair_index], - new_geo.geoms[geo_pair_index+1], - tolerance=1e-3 - ) + snap( + new_geo.geoms[geo_pair_index], + new_geo.geoms[geo_pair_index+1], + tolerance=1e-3 + ) ) - new_geo = linemerge(list_geometries) # prepare edge data dict @@ -715,112 +575,18 @@ def replace_path( # return the edge key return (start_node, end_node, for_k) -#****************************************************************************** -#****************************************************************************** - -def remove_redundant_arcs(network: nx.MultiDiGraph, - both_directions: bool = False) -> list: - """ - Removes parallel arcs from the network. - - Parameters - ---------- - network : nx.MultiDiGraph - The object describing the network. - both_directions : bool, optional - If True, arcs in opposite directions are also considered to be parallel - to one another. If False, they are not. The default is False. - - Returns - ------- - list - A list of the arcs removed. - - """ - - # redundancy: having more than one arc between two nodes - # solution: remove the longest one, leave the shortest one - - # for each node pair - - for node_one in network.nodes(): - - for node_two in network.nodes(): - - # skip self-loops - - if node_one == node_two: - - continue - - # get the arcs between the two nodes - - # list_arcs = gis_iden.all_arcs_between_two_nodes(network, - # node_one, - # node_two) - - # # if none exist, skip - - # if len(list_arcs) == 0: - - # continue - - # otherwise, find out which is the shortest one - - # sorted_arcs = sorted( - # (network.edges[arc_key][osm.KEY_OSMNX_LENGTH], arc_key) - # for arc_key in list_arcs - # ) - - # network.remove_edges_from( - # arc_tuple[1] for arc_tuple in sorted_arcs[1:] - # ) - - - # get the arcs between the two nodes - - list_arcs = gis_iden.all_arcs_from_a_to_b(network, - node_one, - node_two) - - # if none exist, skip - - if len(list_arcs) == 0: - - continue - - min_arc_length = math.inf - - shortest_arc = (node_one, node_two, 0) - - for arc in list_arcs: - - if network.edges[arc][osm.KEY_OSMNX_LENGTH] < min_arc_length: - - min_arc_length = network.edges[arc][osm.KEY_OSMNX_LENGTH] - - shortest_arc = arc - - # remove all but that one - - list_arcs.pop(list_arcs.index(shortest_arc)) - - for arc in list_arcs: - - network.remove_edge(u=node_one, v=node_two, key=arc[2]) - -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def remove_longer_parallel_arcs(network: nx.MultiDiGraph, - ignore_arc_directions: bool = False) -> list: +def remove_longer_parallel_edges(network: nx.MultiDiGraph, + ignore_edge_directions: bool = False) -> list: """ - Removes longer parallel arcs from the network. + Removes longer parallel edges from the network. - Parallel arcs are those connecting the same nodes in the same direction. If - there are parallel arcs between any given pair of nodes, the longer ones - will be removed. If desired, arc directions can be ignored. In that case, - only the shortest arc between the same pair of nodes will be retained. + Parallel edges are those connecting the same nodes in the same direction. If + there are parallel edges between any given pair of nodes, the longer ones + will be removed. If desired, edge directions can be ignored. In that case, + only the shortest edge between the same pair of nodes will be retained. Parameters ---------- @@ -830,11 +596,11 @@ def remove_longer_parallel_arcs(network: nx.MultiDiGraph, Returns ------- list - A list of the arcs removed. + A list of the edges removed. """ - # redundancy: having more than one arc between two nodes + # redundancy: having more than one edge between two nodes # solution: remove the longest one, leave the shortest one # for each node pair @@ -845,40 +611,40 @@ def remove_longer_parallel_arcs(network: nx.MultiDiGraph, # skip self-loops if node_one == node_two: continue - # get the arcs between the two nodes - if ignore_arc_directions: # both directions - list_arcs = gis_iden.all_arcs_between_two_nodes( + # get the edges between the two nodes + if ignore_edge_directions: # both directions + list_edges = gis_iden.get_edges_between_two_nodes( network, node_one, node_two ) else: # one direction - list_arcs = gis_iden.all_arcs_from_a_to_b( + list_edges = gis_iden.get_edges_from_a_to_b( network, node_start=node_one, node_end=node_two ) # if none exist, skip - if len(list_arcs) == 0: + if len(list_edges) == 0: continue # otherwise, find out which is the shortest one - sorted_arcs = sorted( - (network.edges[arc_key][osm.KEY_OSMNX_LENGTH], arc_key) - for arc_key in list_arcs + sorted_edges = sorted( + (network.edges[edge_key][osm.KEY_OSMNX_LENGTH], edge_key) + for edge_key in list_edges ) network.remove_edges_from( - arc_tuple[1] for arc_tuple in sorted_arcs[1:] + edge_tuple[1] for edge_tuple in sorted_edges[1:] ) - edges_removed.extend(arc_tuple[1] for arc_tuple in sorted_arcs[1:]) + edges_removed.extend(edge_tuple[1] for edge_tuple in sorted_edges[1:]) return edges_removed -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def merge_points_into_linestring( line: LineString, @@ -1000,8 +766,11 @@ def merge_points_into_linestring( # if the closest points are end/start points of a segment, then # place the point after the second point of the first segment - if abs(Point(line_segments[sorted_distances[0][1]].coords[-1]).distance(points[i])- - line_distances[i]) <= tolerance: + if abs(Point( + line_segments[ + sorted_distances[0][1] + ].coords[-1] + ).distance(points[i])-line_distances[i]) <= tolerance: line_coords.insert( # sorted_distances[0][1]+1, @@ -1041,8 +810,8 @@ def merge_points_into_linestring( return line, close_to_start, close_to_end -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def split_linestring(line: LineString, points: list, @@ -1076,7 +845,6 @@ def split_linestring(line: LineString, """ # add the points to the linestring - new_line, close_to_start, close_to_end = merge_points_into_linestring( line=line, points=points, @@ -1085,41 +853,26 @@ def split_linestring(line: LineString, ) if len(close_to_end)+len(close_to_start) == len(points): - # no changes to the line (assuming no swaps) - return [], close_to_start, close_to_end # split the linestring object (new_line) - line_segments = [] - previous_split_index = 0 - # for each point on the new line (they should be ordered) - for coords_index, coords in enumerate(new_line.coords): - # if it matches one of the input points - if Point(coords) in points: - # this is a cutting point - if new_line.coords[0] == coords or new_line.coords[-1] == coords: - # it is a start or end point: skip the iteration - # line_segments.append(None) - continue # if it is not a start nor an end point, and it is on the original # line, then - # if not a start nor an end point, build the segment between the # previous split point and the current input point - line_segments.append( LineString( new_line.coords[previous_split_index:coords_index+1] @@ -1127,7 +880,6 @@ def split_linestring(line: LineString, ) # store new end/start point - previous_split_index = coords_index # else: @@ -1139,7 +891,6 @@ def split_linestring(line: LineString, # next iteration # add the last segment - line_segments.append( LineString( new_line.coords[previous_split_index:] @@ -1147,74 +898,73 @@ def split_linestring(line: LineString, ) # return the geometries for each segment and the relevant points by order - return line_segments, close_to_start, close_to_end #************************************************************************** -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def recreate_arcs(network: nx.MultiDiGraph, +def recreate_edges(network: nx.MultiDiGraph, points: dict, tolerance: float = 7./3-4./3-1) -> tuple: """ - Recreates OSMnx-type arcs by splitting them into multiple smaller arcs, - which are defined by points along the original arc. + Recreates OSMnx-type edges by splitting them into multiple smaller edges, + which are defined by points along the original edge. - If the points are closest to the extremities than other parts of an arc, + If the points are closest to the extremities than other parts of an edge, no changes are introduced and the points become synonymous with the closest extremity (i.e., the start or end points). - If the points are closest to other parts of an arc, the arc is split there - with new nodes and arcs being created to replace the original arc. + If the points are closest to other parts of an edge, the edge is split there + with new nodes and edges being created to replace the original edge. Parameters ---------- network : nx.MultiDiGraph - The object describing the network with the arcs and nodes. + The object describing the network with the edges and nodes. points : dict - A dictionary keyed by arc and whose values are the Point geometries - with which to split and then recreate the arc. + A dictionary keyed by edge and whose values are the Point geometries + with which to split and then recreate the edge. Returns ------- dict - A dictionary keyed by arc and holding the node keys for the points that - were provided initially to split the arc. These node keys are for nodes + A dictionary keyed by edge and holding the node keys for the points that + were provided initially to split the edge. These node keys are for nodes that already existed (start or end nodes) or newly created ones. dict - A dictionary keyed by arc and holding the keys for the arcs that were - created to replace the original arc. If a given arc was not recreated, + A dictionary keyed by edge and holding the keys for the edges that were + created to replace the original edge. If a given edge was not recreated, its key does not appear in the dictionary. """ # declare outputs - connection_node_keys_per_arc = {} + connection_node_keys_per_edge = {} - recreated_arcs = {} + recreated_edges = {} - # for each arc that is to be split + # for each edge that is to be split - for arc_key, points_in_arc in points.items(): + for edge_key, points_in_edge in points.items(): # check if there is a geometry already - if osm.KEY_OSMNX_GEOMETRY in network.edges[arc_key]: + if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # get the geometry - line = network.edges[arc_key][osm.KEY_OSMNX_GEOMETRY] - # check if the geometry is consistent with the arc declaration - if not gis_iden.is_edge_consistent_with_geometry(network, arc_key): + line = network.edges[edge_key][osm.KEY_OSMNX_GEOMETRY] + # check if the geometry is consistent with the edge declaration + if not gis_iden.is_edge_consistent_with_geometry(network, edge_key): # the geometry must be reversed line = LineString(line.reverse()) else: # there is not geometry, create it line = LineString( - [(network.nodes[arc_key[0]][osm.KEY_OSMNX_X], - network.nodes[arc_key[0]][osm.KEY_OSMNX_Y]), - (network.nodes[arc_key[1]][osm.KEY_OSMNX_X], - network.nodes[arc_key[1]][osm.KEY_OSMNX_Y])] + [(network.nodes[edge_key[0]][osm.KEY_OSMNX_X], + network.nodes[edge_key[0]][osm.KEY_OSMNX_Y]), + (network.nodes[edge_key[1]][osm.KEY_OSMNX_X], + network.nodes[edge_key[1]][osm.KEY_OSMNX_Y])] ) # split the line into segments using the intermediate points @@ -1223,63 +973,63 @@ def recreate_arcs(network: nx.MultiDiGraph, line_segments, close_to_start, close_to_end = split_linestring( line=line, - points=points_in_arc + points=points_in_edge ) # link each point to a node key: - # those closer to the start node: arc_key[0] - # those closer to the end node: arc_key[1] + # those closer to the start node: edge_key[0] + # those closer to the end node: edge_key[1] # intermediate points: new node key via unused_node_key # _node_keys_by_point = { - # points_in_arc[i]: ( - # arc_key[0] if i in close_to_start else arc_key[1] + # points_in_edge[i]: ( + # edge_key[0] if i in close_to_start else edge_key[1] # ) if ( # i in close_to_start or i in close_to_end # ) else unused_node_key(network) - # for i in range(len(points_in_arc)) + # for i in range(len(points_in_edge)) # } _node_keys_by_point = {} - for i in range(len(points_in_arc)): + for i in range(len(points_in_edge)): if i in close_to_start or i in close_to_end: # point i is close to the extremities: use start/end node key - _node_keys_by_point[points_in_arc[i]] = ( - arc_key[0] if i in close_to_start else arc_key[1] + _node_keys_by_point[points_in_edge[i]] = ( + edge_key[0] if i in close_to_start else edge_key[1] ) else: # point i is not close to the extremities: new node key _node_keys_by_point[ - points_in_arc[i] + points_in_edge[i] ] = unused_node_key(network) - network.add_node(_node_keys_by_point[points_in_arc[i]]) + network.add_node(_node_keys_by_point[points_in_edge[i]]) # _node_keys = [ - # (arc_key[0] if i in close_to_start else arc_key[1]) if ( + # (edge_key[0] if i in close_to_start else edge_key[1]) if ( # i in close_to_start or i in close_to_end # ) else unused_node_key(network) - # for i in range(len(points_in_arc)) + # for i in range(len(points_in_edge)) # ] # should be the same order as in the inputs _node_keys = [ _node_keys_by_point[point] - for point in points_in_arc + for point in points_in_edge ] # ********************************************************************* - # create new arcs between the points to rebuild the arc + # create new edges between the points to rebuild the edge segment_keys = [] - arc_dict = dict(network.get_edge_data(*arc_key)) + edge_dict = dict(network.get_edge_data(*edge_key)) for line_index, line_segment in enumerate(line_segments): - arc_dict[osm.KEY_OSMNX_GEOMETRY] = line_segment + edge_dict[osm.KEY_OSMNX_GEOMETRY] = line_segment if line_index == 0: @@ -1290,9 +1040,9 @@ def recreate_arcs(network: nx.MultiDiGraph, ] k_key = network.add_edge( - u_for_edge=arc_key[0], + u_for_edge=edge_key[0], v_for_edge=v_key, - **arc_dict + **edge_dict ) network.add_node( @@ -1301,7 +1051,7 @@ def recreate_arcs(network: nx.MultiDiGraph, osm.KEY_OSMNX_Y: line_segment.coords[-1][1]} ) - segment_keys.append((arc_key[0],v_key,k_key)) + segment_keys.append((edge_key[0],v_key,k_key)) elif line_index == len(line_segments)-1: @@ -1313,8 +1063,8 @@ def recreate_arcs(network: nx.MultiDiGraph, k_key = network.add_edge( u_for_edge=u_key, - v_for_edge=arc_key[1], - **arc_dict + v_for_edge=edge_key[1], + **edge_dict ) network.add_node( @@ -1323,7 +1073,7 @@ def recreate_arcs(network: nx.MultiDiGraph, osm.KEY_OSMNX_Y: line_segment.coords[0][1]} ) - segment_keys.append((u_key,arc_key[1],k_key)) + segment_keys.append((u_key,edge_key[1],k_key)) else: # intermediate segment @@ -1338,7 +1088,7 @@ def recreate_arcs(network: nx.MultiDiGraph, k_key = network.add_edge( u_for_edge=u_key, v_for_edge=v_key, - **arc_dict + **edge_dict ) network.add_node( @@ -1357,16 +1107,16 @@ def recreate_arcs(network: nx.MultiDiGraph, # TODO: use network.add_edges_from() for performance? - # TODO: try to create all the arcs (with lengths included) in one go + # TODO: try to create all the edges (with lengths included) in one go # calculate the lengths - arc_lengths_by_dict = arc_lengths(network, arc_keys=segment_keys) + edge_lengths_by_dict = edge_lengths(network, edge_keys=segment_keys) network.add_edges_from( tuple( (*segment_key, - {osm.KEY_OSMNX_LENGTH: arc_lengths_by_dict[segment_key]}) + {osm.KEY_OSMNX_LENGTH: edge_lengths_by_dict[segment_key]}) for segment_key in segment_keys ) ) @@ -1375,25 +1125,25 @@ def recreate_arcs(network: nx.MultiDiGraph, if len(line_segments) > 0: - recreated_arcs[arc_key] = segment_keys + recreated_edges[edge_key] = segment_keys # print('here segment keyse') # print(segment_keys) - connection_node_keys_per_arc[arc_key] = _node_keys + connection_node_keys_per_edge[edge_key] = _node_keys - return connection_node_keys_per_arc, recreated_arcs + return connection_node_keys_per_edge, recreated_edges -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def connect_nodes_to_arcs( +def connect_nodes_to_edges( network: nx.MultiDiGraph, node_keys: list, - arc_keys: list, + edge_keys: list, store_unsimplified_geometries: bool = False, - use_one_arc_per_direction: bool = False + use_one_edge_per_direction: bool = False ) -> tuple: """ - Connects nodes to arcs using additional arcs in an OSMnx-formatted graph. + Connects nodes to edges using additional edges in an OSMnx-formatted graph. Parameters ---------- @@ -1401,85 +1151,85 @@ def connect_nodes_to_arcs( The object describing the network. node_keys : list The keys of the nodes that are to be connected. - arc_keys : list - The keys of the arcs that are to be connected to the nodes. It must be + edge_keys : list + The keys of the edges that are to be connected to the nodes. It must be ordered based on node_keys and must have the same size. store_unsimplified_geometries : bool, optional If True, straight line geometries that are created are also preserved. If False, they are not preserved. The default is False. - use_one_arc_per_direction : bool, optional - If True, two arcs are used for each new arc created to connect a node. - If False, only one (forward) arc will be created. The default is False. + use_one_edge_per_direction : bool, optional + If True, two edges are used for each new edge created to connect a node. + If False, only one (forward) edge will be created. The default is False. Returns ------- network : nx.MultiDiGraph - A network graph object where the node and arc pairs are connected. - new_arc_keys : list - An ordered list containing the keys for the new arcs created to connect + A network graph object where the node and edge pairs are connected. + new_edge_keys : list + An ordered list containing the keys for the new edges created to connect each node. - connection_node_keys_per_arc : dict - A dictionary keyed by arc and holding the node keys for the points that - were provided initially to split the arc. These node keys are for nodes + connection_node_keys_per_edge : dict + A dictionary keyed by edge and holding the node keys for the points that + were provided initially to split the edge. These node keys are for nodes that already existed (start or end nodes) or newly created ones. - recreated_arcs : dict - A dictionary keyed by arc and holding the keys for the arcs that were - created to replace the original arc. If a given arc was not recreated, + recreated_edges : dict + A dictionary keyed by edge and holding the keys for the edges that were + created to replace the original edge. If a given edge was not recreated, its key does not appear in the dictionary. """ #************************************************************************** - # 1) group nodes by the arc they are closest to - # 2) for each arc, and node that is to be connected to it, find its closest - # point on the arc - # 3) recreate each arc after dividing it at the specified points - # 4) connect the nodes to the arcs - # 5) delete the original arcs, if they have been split + # 1) group nodes by the edge they are closest to + # 2) for each edge, and node that is to be connected to it, find its closest + # point on the edge + # 3) recreate each edge after dividing it at the specified points + # 4) connect the nodes to the edges + # 5) delete the original edges, if they have been split # 6) calculate or update the edge attributes #************************************************************************** #************************************************************************** - # 1) group nodes by the arc they are closest to + # 1) group nodes by the edge they are closest to - nodes_to_connect_to_arc = { - arc_key: tuple( + nodes_to_connect_to_edge = { + edge_key: tuple( node_key - for other_arc_key, node_key in zip(arc_keys, node_keys) - if other_arc_key == arc_key + for other_edge_key, node_key in zip(edge_keys, node_keys) + if other_edge_key == edge_key ) - for arc_key in set(arc_keys) + for edge_key in set(edge_keys) } #************************************************************************** #************************************************************************** - # 2) for each arc, and node that is to be connected to it, find its closest - # point on the arc + # 2) for each edge, and node that is to be connected to it, find its closest + # point on the edge - points_per_arc = {} + points_per_edge = {} - for arc_key, _node_keys in nodes_to_connect_to_arc.items(): + for edge_key, _node_keys in nodes_to_connect_to_edge.items(): # check if the geometry exists - if osm.KEY_OSMNX_GEOMETRY in network.edges[arc_key]: + if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # the geometry object exists, get it - arc_geo = network.edges[arc_key][osm.KEY_OSMNX_GEOMETRY] + edge_geo = network.edges[edge_key][osm.KEY_OSMNX_GEOMETRY] else: # the geometry object does not exist, make it - arc_geo = LineString( - [(network.nodes[arc_key[0]][osm.KEY_OSMNX_X], - network.nodes[arc_key[0]][osm.KEY_OSMNX_Y]), - (network.nodes[arc_key[1]][osm.KEY_OSMNX_X], - network.nodes[arc_key[1]][osm.KEY_OSMNX_Y])] + edge_geo = LineString( + [(network.nodes[edge_key[0]][osm.KEY_OSMNX_X], + network.nodes[edge_key[0]][osm.KEY_OSMNX_Y]), + (network.nodes[edge_key[1]][osm.KEY_OSMNX_X], + network.nodes[edge_key[1]][osm.KEY_OSMNX_Y])] ) # store the geometry @@ -1488,17 +1238,17 @@ def connect_nodes_to_arcs( # update the edge - network.add_edge(*arc_key, - **{osm.KEY_OSMNX_GEOMETRY: arc_geo}) + network.add_edge(*edge_key, + **{osm.KEY_OSMNX_GEOMETRY: edge_geo}) - # use nearest_points to locate the closest points on the arc + # use nearest_points to locate the closest points on the edge - points_per_arc[arc_key] = [ + points_per_edge[edge_key] = [ nearest_points( - arc_geo, + edge_geo, Point(network.nodes[node_key][osm.KEY_OSMNX_X], network.nodes[node_key][osm.KEY_OSMNX_Y]) - )[0] # [0] to get the point on the arc + )[0] # [0] to get the point on the edge for node_key in _node_keys ] @@ -1509,72 +1259,72 @@ def connect_nodes_to_arcs( #************************************************************************** #************************************************************************** - # 3) recreate each arc after dividing it at the specified points + # 3) recreate each edge after dividing it at the specified points - connection_node_keys_per_arc, recreated_arcs = recreate_arcs( + connection_node_keys_per_edge, recreated_edges = recreate_edges( network, - points=points_per_arc + points=points_per_edge ) # put the keys for the connection nodes connection_node_keys = [ - connection_node_keys_per_arc[arc_key][ - nodes_to_connect_to_arc[arc_key].index(node_key) + connection_node_keys_per_edge[edge_key][ + nodes_to_connect_to_edge[edge_key].index(node_key) ] - for node_key, arc_key in zip(node_keys, arc_keys) + for node_key, edge_key in zip(node_keys, edge_keys) ] - # delete the original arcs, if they have been split + # delete the original edges, if they have been split - network.remove_edges_from(recreated_arcs) + network.remove_edges_from(recreated_edges) #************************************************************************** #************************************************************************** - # 4) connect the nodes to the arcs + # 4) connect the nodes to the edges - connection_arc_containers = [] + connection_edge_containers = [] for node_key, connection_node_key in zip(node_keys, connection_node_keys): # skip self-loops if node_key == connection_node_key: continue - # proceed with other types of arcs - if use_one_arc_per_direction: - # add one directed arc per direction - connection_arc_containers.append( + # proceed with other types of edges + if use_one_edge_per_direction: + # add one directed edge per direction + connection_edge_containers.append( (node_key, connection_node_key) ) - connection_arc_containers.append( + connection_edge_containers.append( (connection_node_key, node_key) ) else: - # add one directed arc starting from the arc and ending in the node - connection_arc_containers.append( + # add one directed edge starting from the edge and ending in the node + connection_edge_containers.append( (connection_node_key, node_key) ) - edge_keys = network.add_edges_from(connection_arc_containers) + edge_keys = network.add_edges_from(connection_edge_containers) # ************************************************************************* # ************************************************************************* # 5) calculate or update the edge attributes - # calculate arc lengths and street counts for the new edges + # calculate edge lengths and street counts for the new edges if len(edge_keys) != 0: # there are new edges: calculate the lengths and add them - new_arc_keys = [ + new_edge_keys = [ (*edge_tuple[0:2], edge_key) # apply it only to specific edges for edge_tuple, edge_key in zip( - connection_arc_containers, edge_keys) + connection_edge_containers, edge_keys) ] if is_projected(network.graph['crs']): # projected crs: use own method - lengths_dict = arc_lengths( + lengths_dict = edge_lengths( network, - arc_keys=new_arc_keys + edge_keys=new_edge_keys ) network.add_edges_from( @@ -1584,24 +1334,24 @@ def connect_nodes_to_arcs( osm.KEY_OSMNX_ONEWAY: False, osm.KEY_OSMNX_REVERSED: False, osm.KEY_OSMNX_OSMID: None}) - for edge_key in new_arc_keys + for edge_key in new_edge_keys ) ) else: # unprojected crs: use the osmnx method - network = add_edge_lengths(network, edges=new_arc_keys) + network = add_edge_lengths(network, edges=new_edge_keys) # update the street count update_street_count(network) else: - new_arc_keys = [] + new_edge_keys = [] # ************************************************************************* # ************************************************************************* - return network, new_arc_keys, connection_node_keys_per_arc, recreated_arcs + return network, new_edge_keys, connection_node_keys_per_edge, recreated_edges # ***************************************************************************** # ***************************************************************************** @@ -1611,7 +1361,7 @@ def remove_reversed_edges( reversed_attr: bool = True ) -> list: """ - Removes reversed arcs from an OSMnx-formatted multi directed edge graph. + Removes reversed edges from an OSMnx-formatted multi directed edge graph. Parameters ---------- diff --git a/src/topupopt/data/gis/utils.py b/src/topupopt/data/gis/utils.py index 3ea3216831bfce713379e76e5e6ea4fbe74103a8..44b9ab2733206a849c4e5a2f2f98d237cc91fdc8 100644 --- a/src/topupopt/data/gis/utils.py +++ b/src/topupopt/data/gis/utils.py @@ -18,13 +18,12 @@ import contextily as cx # local, internal from ..gis import osm -# from .modify import remove_dead_ends, remove_redundant_arcs, replace_path from ..gis import identify as gis_iden from ..gis import modify as gis_mod from ..gis import calculate as gis_calc -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # constants @@ -33,8 +32,8 @@ KEY_GPD_GEOMETRY = 'geometry' RKW_GPKG = 'packed' -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # TODO: complete method @@ -113,8 +112,8 @@ def find_gpkg_packable_columns(gdf: GeoDataFrame) -> set: return set_columns -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def write_gdf_file(gdf: GeoDataFrame, filename: str, @@ -225,8 +224,8 @@ def write_gdf_file(gdf: GeoDataFrame, new_gdf.to_file(filename, **kwargs) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def pack_columns(gdf: GeoDataFrame, columns: list, @@ -300,8 +299,8 @@ def pack_columns(gdf: GeoDataFrame, gdf.drop(labels=columns, axis=1, inplace=True) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def unpack_columns(gdf: GeoDataFrame, packed_column_name: str = RKW_GPKG): """ @@ -359,8 +358,8 @@ def unpack_columns(gdf: GeoDataFrame, packed_column_name: str = RKW_GPKG): gdf.drop(labels=packed_column_name, axis=1, inplace=True) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def read_gdf_file(filename: str, packed_columns: tuple = None, @@ -444,8 +443,8 @@ def read_gdf_file(filename: str, return gdf -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # create osmnx-like geodataframes for nodes @@ -494,8 +493,8 @@ def create_node_geodataframe(longitudes: tuple or list, crs=crs ) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def prepare_node_data_from_geodataframe( gdf: GeoDataFrame, @@ -577,8 +576,8 @@ def prepare_node_data_from_geodataframe( return node_keys, node_data_container, node_key_to_gdf_index_dict -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # TODO: simplify the passing of options to the methods relied upon @@ -638,8 +637,8 @@ def plot_discrete_attributes(gdf_buildings: GeoDataFrame, zoom=zoom_level, source=cx.providers.OpenStreetMap.Mapnik) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def count_ocurrences(gdf: GeoDataFrame, column: str, @@ -725,8 +724,8 @@ def count_ocurrences(gdf: GeoDataFrame, return count_dict -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def get_directed(network: MultiGraph, drop_unsimplified_geometries: bool = True) -> MultiDiGraph: @@ -773,13 +772,13 @@ def get_directed(network: MultiGraph, return directed_network -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def simplify_network(network: MultiDiGraph, protected_nodes: list, dead_end_probing_depth: int = 5, - remove_opposite_parallel_arcs: bool = False, + remove_opposite_parallel_edges: bool = False, update_street_count_per_node: bool = True, **roundabout_conditions): """ @@ -793,8 +792,8 @@ def simplify_network(network: MultiDiGraph, A list with the keys for the nodes that must be preserved. dead_end_probing_depth: int The maximum number of nodes a dead end can have to be detectable. - remove_opposite_parallel_arcs : bool, optional - If True, longer parallel arcs in opposite directions are also removed. + remove_opposite_parallel_edges : bool, optional + If True, longer parallel edges in opposite directions are also removed. The default is False. update_street_count_per_node : bool, optional If True, updates the street count on each node. The default is True. @@ -815,9 +814,9 @@ def simplify_network(network: MultiDiGraph, ) # 2) remove longer parallel edges (tends to create straight paths) - gis_mod.remove_longer_parallel_arcs( + gis_mod.remove_longer_parallel_edges( network, - ignore_arc_directions=remove_opposite_parallel_arcs + ignore_edge_directions=remove_opposite_parallel_edges ) # 3) remove self loops (tends to create straight paths and dead ends) @@ -847,10 +846,10 @@ def simplify_network(network: MultiDiGraph, if update_street_count_per_node: gis_calc.update_street_count(network) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** -def identify_building_entrance_arcs( +def identify_building_entrance_edges( gdf: GeoDataFrame, gdf_street_column: str, network: gis_iden.nx.MultiDiGraph, @@ -903,18 +902,18 @@ def identify_building_entrance_arcs( # Notes: # - Each building is expected to have a street name associated with it; # - If a building does not have a street name associated with it, then the - # arc corresponding to the street must be determined using distances. + # edge corresponding to the street must be determined using distances. - # 1) for each node (building entrance), identify the closest arc - # 2) identify which arcs identified before cannot be linked back to their - # respective nodes via street names or via (only) one intermediate arc - # 3) for the nodes whose closest arcs that cannot be linked back to the no- - # des, find the arcs that can, if any, (via their street names) and select + # 1) for each node (building entrance), identify the closest edge + # 2) identify which edges identified before cannot be linked back to their + # respective nodes via street names or via (only) one intermediate edge + # 3) for the nodes whose closest edges that cannot be linked back to the no- + # des, find the edges that can, if any, (via their street names) and select # the closest one among them as a substitute for the closest one in general - # 4) for all other cases, use the closest arc among all + # 4) for all other cases, use the closest edge among all - # output: a list of arc keys (one per building entrance) - # exceptions: if a building cannot be linked to an arc key, link it to None + # output: a list of edge keys (one per building entrance) + # exceptions: if a building cannot be linked to an edge key, link it to None #************************************************************************** @@ -924,40 +923,40 @@ def identify_building_entrance_arcs( #************************************************************************** - # 1) for each building (entrance), identify the closest arc + # 1) for each building (entrance), identify the closest edge node_keys = list(node_key_to_gdf_index_dict.keys()) - closest_arc_keys, network = gis_iden.identify_arc_closest_to_node( + closest_edge_keys, network = gis_iden.identify_edge_closest_to_node( network=network, node_keys=node_keys, crs=crs) # do not revert back to the original yet - # create a dict for the closest arc keys: {node keys: closest arc keys} + # create a dict for the closest edge keys: {node keys: closest edge keys} - building_entrance_arcs = dict(zip(node_keys, closest_arc_keys)) + building_entrance_edges = dict(zip(node_keys, closest_edge_keys)) - _closest_arc_keys_dict = dict(building_entrance_arcs) + _closest_edge_keys_dict = dict(building_entrance_edges) #************************************************************************** # 2) identify the nodes that require additional precautions (i.e., those - # that should not be connected to their closest arcs) + # that should not be connected to their closest edges) # the nodes not requiring additional precautions are the following: # i) those that do not concern buildings (no address); - # ii) those whose closest arc has the same street name as the node; - # iii) those whose closest arc is a nameless intermediate arc that connects - # with another arc which has the same street name as the node (driveway). + # ii) those whose closest edge has the same street name as the node; + # iii) those whose closest edge is a nameless intermediate edge that connects + # with another edge which has the same street name as the node (driveway). # the nodes that require special precautions are: - # iv) those whose closest arcs have names that do not match the node's; - # v) those whose closest arcs do not have street names and do not lead to - # an arc whose street name matches that of the building address. + # iv) those whose closest edges have names that do not match the node's; + # v) those whose closest edges do not have street names and do not lead to + # an edge whose street name matches that of the building address. - # in both cases, the solution is to find arcs whose street names match + # in both cases, the solution is to find edges whose street names match # those of the node and connect the one that is closest among them. If not - # possible (no arcs), then the solution is to connect to the closest arc. + # possible (no edges), then the solution is to connect to the closest edge. # 2.1) generate a dict with the correspondence between streets and nodes @@ -969,7 +968,7 @@ def identify_building_entrance_arcs( trouble_nodes = [] - for node_key, closest_arc_key in zip(node_keys, closest_arc_keys): + for node_key, closest_edge_key in zip(node_keys, closest_edge_keys): # check if the street name is a string @@ -979,17 +978,17 @@ def identify_building_entrance_arcs( continue - # check if the arc has a name attribute + # check if the edge has a name attribute - if osm.KEY_OSMNX_NAME in network.edges[closest_arc_key]: + if osm.KEY_OSMNX_NAME in network.edges[closest_edge_key]: - # arc object has name attribute, check if the street names match + # edge object has name attribute, check if the street names match - if type(network.edges[closest_arc_key][osm.KEY_OSMNX_NAME]) == str: + if type(network.edges[closest_edge_key][osm.KEY_OSMNX_NAME]) == str: # the address is a string - if (network.edges[closest_arc_key][osm.KEY_OSMNX_NAME] in + if (network.edges[closest_edge_key][osm.KEY_OSMNX_NAME] in node_street_names[node_key]): # the street names match, this is not a problematic node (ii) @@ -1010,7 +1009,7 @@ def identify_building_entrance_arcs( matching_street_name_found_list = tuple( _name in node_street_names[node_key] - for _name in network.edges[closest_arc_key][ + for _name in network.edges[closest_edge_key][ osm.KEY_OSMNX_NAME] ) @@ -1028,20 +1027,20 @@ def identify_building_entrance_arcs( continue - # otherwise, the arc is nameless but may not lead to the right street + # otherwise, the edge is nameless but may not lead to the right street - # get adjacent/neighbouring arcs + # get adjacent/neighbouring edges - other_arcs = gis_iden.all_arcs_involving_node( + other_edges = gis_iden.get_edges_involving_node( network=network, - node_key=closest_arc_key[0], + node_key=closest_edge_key[0], include_self_loops=False ) - other_arcs.extend( - gis_iden.all_arcs_involving_node( + other_edges.extend( + gis_iden.get_edges_involving_node( network=network, - node_key=closest_arc_key[1], + node_key=closest_edge_key[1], include_self_loops=False ) ) @@ -1050,31 +1049,31 @@ def identify_building_entrance_arcs( # for each neighbour - for other_arc_key in other_arcs: + for other_edge_key in other_edges: - # check if the current arc is the closest one + # check if the current edge is the closest one - if closest_arc_key == other_arc_key: + if closest_edge_key == other_edge_key: # it is: skip, since it has already been considered continue - # check if the current arc has the address/name attribute + # check if the current edge has the address/name attribute - if osm.KEY_OSMNX_NAME in network.edges[other_arc_key]: + if osm.KEY_OSMNX_NAME in network.edges[other_edge_key]: # it does, now check if it is a string - if type(network.edges[other_arc_key][ + if type(network.edges[other_edge_key][ osm.KEY_OSMNX_NAME]) == str: # it is, now check if the street names match - if (network.edges[other_arc_key][osm.KEY_OSMNX_NAME] in + if (network.edges[other_edge_key][osm.KEY_OSMNX_NAME] in node_street_names[node_key]): - # an arc with a matching street name was found (iii) + # an edge with a matching street name was found (iii) matching_street_name_found = True @@ -1086,7 +1085,7 @@ def identify_building_entrance_arcs( matching_street_name_found_list = tuple( _name in node_street_names[node_key] - for _name in network.edges[other_arc_key][ + for _name in network.edges[other_edge_key][ osm.KEY_OSMNX_NAME] ) @@ -1112,51 +1111,51 @@ def identify_building_entrance_arcs( #************************************************************************** - # 3) for the nodes whose closest arcs that cannot be linked back to the no- - # des, find the arcs that can, if any, (via their street names) and select + # 3) for the nodes whose closest edges that cannot be linked back to the no- + # des, find the edges that can, if any, (via their street names) and select # the closest one among them as a substitute for the closest one in general - # 3.1) generate the list of arc keys per street + # 3.1) generate the list of edge keys per street unique_street_names = set( node_street_names[node_key] for node_key in trouble_nodes ) - # arc keys with a given street name + # edge keys with a given street name - arcs_per_street_name = { + edges_per_street_name = { street_name: [ - arc_key for arc_key in network.edges(keys=True) - if osm.KEY_OSMNX_NAME in network.edges[arc_key] - if street_name in network.edges[arc_key][osm.KEY_OSMNX_NAME] + edge_key for edge_key in network.edges(keys=True) + if osm.KEY_OSMNX_NAME in network.edges[edge_key] + if street_name in network.edges[edge_key][osm.KEY_OSMNX_NAME] ] for street_name in unique_street_names } - # 3.2) for each troublesome node, identify the arcs that mention the same + # 3.2) for each troublesome node, identify the edges that mention the same # street and pick the closest on for node_key in trouble_nodes: - # check the arcs keys relevant for this node + # check the edges keys relevant for this node - other_arc_keys = arcs_per_street_name[node_street_names[node_key]] + other_edge_keys = edges_per_street_name[node_street_names[node_key]] - # check if there are no arcs mentioning the street + # check if there are no edges mentioning the street - if len(other_arc_keys) == 0: + if len(other_edge_keys) == 0: - # no arcs mentioning that street, skip + # no edges mentioning that street, skip continue # create a view - new_network = network.edge_subgraph(edges=other_arc_keys) + new_network = network.edge_subgraph(edges=other_edge_keys) # pick the one that is closest - other_closest_arc = gis_iden.nearest_edges( + other_closest_edge = gis_iden.nearest_edges( new_network, X=network.nodes[node_key][osm.KEY_OSMNX_X], Y=network.nodes[node_key][osm.KEY_OSMNX_Y], @@ -1164,11 +1163,11 @@ def identify_building_entrance_arcs( # replace previous entry - building_entrance_arcs[node_key] = other_closest_arc + building_entrance_edges[node_key] = other_closest_edge #************************************************************************** - # 4) for all other cases, use the closest arc among all + # 4) for all other cases, use the closest edge among all #************************************************************************** @@ -1178,41 +1177,94 @@ def identify_building_entrance_arcs( network = gis_iden.project_graph(network, to_crs=original_crs) - # return arc keys + # return edge keys - return building_entrance_arcs, _closest_arc_keys_dict, network + return building_entrance_edges, _closest_edge_keys_dict, network - #************************************************************************** - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** + # ************************************************************************* + # ************************************************************************* + +# ***************************************************************************** +# ***************************************************************************** -def convert_edge_path(edge_path: list) -> list: +def convert_edge_path(network: MultiDiGraph, + path: list, + allow_reversed_edges: bool = False) -> list: """ - Converts a path formed by edge keys into a path formed by node keys. + Converts a path of edge keys into a path of node keys. Parameters ---------- - edge_path : list - The path described using edge keys. + network : nx.MultiDiGraph + The objet describing the network. + path : list + A list of sequential edge keys that form a path. + allow_reversed_edges : bool, optional + If True, edges in the opposite direction also count to form paths, as + long as the same nodes are involved. The default is False. Returns ------- list - The sequence of node keys forming the path. + A list of node keys forming a path. """ - if len(edge_path) == 0: - return [] + # check if the path corresponds to an edge path + if not gis_iden.is_edge_path( + network, + path, + ignore_edge_direction=allow_reversed_edges + ): + raise ValueError('No edge path was provided.') + + # path is a sequence of edge keys: convert to node path + if allow_reversed_edges: + + # reverse edges are allowed + + # drop self-loops, if any + edge_path = [ + edge_key + for edge_key in path + if edge_key[0] != edge_key[1] # exclude self loops + ] + + # if there is only one edge, the node path is straightforward + if len(edge_path) == 1: + return [edge_path[0][0], edge_path[0][1]] + + node_path = [] + for edge_index, edge_key in enumerate(edge_path): + # if there are no nodes yet on the path + if len(node_path) == 0: + # find out which node comes first + if edge_key[0] in edge_path[1]: + # the start node is in the second edge too: reversed + node_path.append(edge_key[1]) + node_path.append(edge_key[0]) + else: # the edge is not reversed + node_path.append(edge_key[0]) + node_path.append(edge_key[1]) + else: + # find out which node comes after the previous node + if node_path[-1] == edge_key[0]: + # the start node is the same as the previous node + node_path.append(edge_key[1]) + else: + # the end node is the same as the previous node + node_path.append(edge_key[0]) else: - out = list( + # no reversed edges + node_path = [ edge_key[0] - for edge_key in edge_path - ) - out.append(edge_path[-1][1]) # add end node of last edge key - return out + for edge_key in path + if edge_key[0] != edge_key[1] # exclude self loops + ] + # add the last edge's end node + node_path.append(path[-1][1]) + # return statement + return node_path -#****************************************************************************** -#****************************************************************************** \ No newline at end of file +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file diff --git a/tests/examples_gis.py b/tests/examples_gis.py index c90190e2f5fb5891a9642a39e1a0b334b76b8241..9a8d30a3adc6d71e6ee59c1544f5930294d07bce 100644 --- a/tests/examples_gis.py +++ b/tests/examples_gis.py @@ -652,7 +652,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, # if it is not in the roundabout itself if other_node_key not in original_roundabout_nodes # for each arc between the two nodes - for edge_key in gis_ident.all_arcs_between_two_nodes( + for edge_key in gis_ident.get_edges_between_two_nodes( network, node_key, other_node_key) @@ -769,7 +769,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, for ra_i in roundabout_candidates: - for arc_key in gis_ident.all_arcs_from_a_to_b( + for arc_key in gis_ident.get_edges_from_a_to_b( network, edge_key[0], new_roundabout_nodes[ra_i]): @@ -804,7 +804,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, for ra_i in roundabout_candidates: - for arc_key in gis_ident.all_arcs_from_a_to_b( + for arc_key in gis_ident.get_edges_from_a_to_b( network, new_roundabout_nodes[ra_i], edge_key[1]): @@ -856,7 +856,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, for ra_i in roundabout_candidates: - for arc_key in gis_ident.all_arcs_from_a_to_b( + for arc_key in gis_ident.get_edges_from_a_to_b( network, edge_key[0], new_roundabout_nodes[ra_i]): @@ -893,7 +893,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, for ra_i in roundabout_candidates: - for arc_key in gis_ident.all_arcs_from_a_to_b( + for arc_key in gis_ident.get_edges_from_a_to_b( network, new_roundabout_nodes[ra_i], edge_key[1]): @@ -945,7 +945,7 @@ def example_roundabouts_protocol(network: nx.MultiDiGraph, for ra_si_i in roundabout_candidates_sink: - for arc_key in gis_ident.all_arcs_from_a_to_b( + for arc_key in gis_ident.get_edges_from_a_to_b( network, new_roundabout_nodes[ra_so_i], new_roundabout_nodes[ra_si_i]): @@ -1011,7 +1011,7 @@ def example_intermediate_nodes_along_path(network: nx.MultiDiGraph, # replace the path - gis_mod.old_replace_path(network, + gis_mod.replace_path(network, path, path_as_arc_keys=return_paths_as_arc_keys) @@ -1105,7 +1105,7 @@ def example_removal_redundant_arcs(seed_number: int = None): for arc in network.edges(keys=True): assert len( - gis_ident.all_arcs_from_a_to_b(network,arc[0],arc[1]) + gis_ident.get_edges_from_a_to_b(network,arc[0],arc[1]) ) == 1 #************************************************************************** diff --git a/tests/examples_gis_utils.py b/tests/examples_gis_utils.py index c628dcd9da81f17dd23bab77387a3617efdfa6d9..e3f57734a69ad9907b231b2c11d13c8f198e4ced 100644 --- a/tests/examples_gis_utils.py +++ b/tests/examples_gis_utils.py @@ -933,7 +933,7 @@ def example_identify_entrances_simple_no_driveway_closest(): # find out which is the closest arc - nearest_arc_keys, network = gis_iden.identify_arc_closest_to_node( + nearest_arc_keys, network = gis_iden.identify_edge_closest_to_node( network, node_keys=['P']) @@ -2025,7 +2025,7 @@ def example_get_directed(network: nx.MultiDiGraph): edge_dict = directed_network.edges[edge_key] - for other_edge_key in gis_iden.all_arcs_from_a_to_b( + for other_edge_key in gis_iden.get_edges_from_a_to_b( network, edge_key[0], edge_key[1]): # check all attributes diff --git a/tests/test_gis_calculate.py b/tests/test_gis_calculate.py index 7ad2d3994fabe835b594bc54ffbdbb686e445d28..c44af7fe36eedc8525c5a5b97f512cea9ebd442b 100644 --- a/tests/test_gis_calculate.py +++ b/tests/test_gis_calculate.py @@ -15,7 +15,6 @@ from numpy import nan import src.topupopt.data.gis.calculate as gis_calc import src.topupopt.data.gis.osm as osm -from src.topupopt.data.gis.calculate import arc_lengths # ***************************************************************************** # ***************************************************************************** @@ -25,24 +24,24 @@ class TestGisCalculate: # ************************************************************************* # ************************************************************************* - def validate_arc_distances(self, G: nx.MultiDiGraph, abs_tol: float = 5): + def validate_edge_distances(self, G: nx.MultiDiGraph, abs_tol: float = 5): - # get the true arc lengths + # get the true edge lengths true_lengths = { - arc_key: (G.edges[arc_key][osm.KEY_OSMNX_LENGTH] - if osm.KEY_OSMNX_LENGTH in G.edges[arc_key] else None) - for arc_key in G.edges(keys=True) + edge_key: (G.edges[edge_key][osm.KEY_OSMNX_LENGTH] + if osm.KEY_OSMNX_LENGTH in G.edges[edge_key] else None) + for edge_key in G.edges(keys=True) } - # get the arc lengths calculated independently - calculated_lengths = arc_lengths(G) + # get the edge lengths calculated independently + calculated_lengths = gis_calc.edge_lengths(G) - # for each arc on the graph - for arc_key in true_lengths.keys(): + # for each edge on the graph + for edge_key in true_lengths.keys(): # validate assert isclose( - calculated_lengths[arc_key], - true_lengths[arc_key], + calculated_lengths[edge_key], + true_lengths[edge_key], abs_tol=abs_tol ) @@ -67,7 +66,7 @@ class TestGisCalculate: # validate without projected coordinates - self.validate_arc_distances(G=G) + self.validate_edge_distances(G=G) # project the graph @@ -75,7 +74,7 @@ class TestGisCalculate: # validate with projected coordinates - self.validate_arc_distances(G=projected_G) + self.validate_edge_distances(G=projected_G) # calculate node path lenths @@ -337,7 +336,7 @@ class TestGisCalculate: (13, 57)] # radius = 6363.478 km at sea level - # arc = radius*(pi/180)*angle in degrees + # edge = radius*(pi/180)*angle in degrees true_length_2_points_a = 62.178959*1e3 # 62.178959 km with r=6371.009 km diff --git a/tests/test_gis_identify.py b/tests/test_gis_identify.py index 7377f038a036cdcce9996bf8da9e509377faee67..ed3728efd51c5cb01accdfd3138806703b1c623a 100644 --- a/tests/test_gis_identify.py +++ b/tests/test_gis_identify.py @@ -29,8 +29,7 @@ class TestGisIdentify: network: nx.MultiDiGraph, path: list, excluded_nodes: list, - paths_must_be_oneway: bool = True, - paths_must_be_unique: bool = True): + consider_reversed_edges: bool): # find out the unique nodes set_nodes = set(path) # at least three nodes @@ -40,7 +39,7 @@ class TestGisIdentify: # count the nodes node_count = path.count(node) if node_count >= 3: - # the same node cannot appear three times + # the same node cannot appear three or more times assert False elif node_count == 2: # cycle: the first and last nodes must match @@ -49,542 +48,1789 @@ class TestGisIdentify: # no excluded nodes in the intermediate positions for node in excluded_nodes: assert node not in path[1:-1] - # make sure it qualifies as a pth - if paths_must_be_oneway and paths_must_be_oneway: - assert gis_iden.is_node_path(network, path) - + # make sure it qualifies as a path + assert gis_iden.is_node_path( + network, + path, + consider_reversed_edges=consider_reversed_edges + ) + # ************************************************************************* # ************************************************************************* - def test_find_straight_paths_case_a(self): + def test_find_straight_path_empty_graph(self): + + # test variations: + # 1) excluded nodes + # 2) self loops + # 3) reversed edges - # case a: - paths_must_be_oneway = True # reverse-parallel arcs prevent paths - paths_must_be_unique = True # anti-/parallel arcs prevent paths + # ********************************************************************* + # ********************************************************************* - # network without straight paths + # network with a two edge path network = nx.MultiDiGraph() + + # no reversed edges, no self loops, no excluded nodes + consider_reversed_edges = False + ignore_self_loops = False excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + # test path validator with non-path + error_raised = False + try: + assert not self.path_validator( + network, + [1, 1, 1], + excluded_nodes, + False + ) + except AssertionError: + error_raised = True + assert error_raised + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) assert len(straight_paths) == 0 - + # ********************************************************************* - - # network with the simplest straight path - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - # simple cycle - (3, 4, 0), - (4, 5, 0), - (5, 3, 0), - # two node loop >> not a straight path because it is not one-way - (6, 7, 0), - (7, 6, 0) - ]) + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + consider_reversed_edges = False + ignore_self_loops = True excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [3, 4, 5, 3]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - # try a path in which a node appears three times + assert len(straight_paths) == 0 - error_raised = False - try: - self.path_validator(network, [0, 1, 2, 1, 3, 1], excluded_nodes) - except AssertionError: - error_raised = True - assert error_raised - # ********************************************************************* - - # network with longer path + # ********************************************************************* + + # network with a two edge path network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - # - (4, 5, 0), - (5, 6, 0), - (6, 7, 0), - (7, 8, 0) - ]) + + # no reversed edges, no self loops, no excluded nodes + consider_reversed_edges = True + ignore_self_loops = False excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2, 3], [4, 5, 6, 7, 8]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) + assert len(straight_paths) == 0 - #************************************************************************** - - # network with excluded nodes - + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - (3, 4, 0), - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + # no reversed edges, no self loops, no excluded nodes + consider_reversed_edges = True + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [2, 3, 4]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** + assert len(straight_paths) == 0 + + # ********************************************************************* + # ********************************************************************* + + # enhance test coverage + + # add single node + network.add_node(0) + + path = gis_iden._find_path_direction_insensitive(network, [], 0) + assert type(path) == list + assert len(path) == 1 + assert repr(path) == repr([0]) + + # ************************************************************************* + # ************************************************************************* - # network with self loops and excluded nodes + def test_find_one_edge_path(self): + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path network = nx.MultiDiGraph() network.add_edges_from([ - (0, 1, 0), - (2, 2, 0), - (1, 2, 0), - (2, 3, 0), - (3, 3, 0), - (3, 4, 0), + (0, 1, 0) ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [2, 3, 4]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** - - # network with parallel arcs + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (2, 3, 0), - (3, 4, 0), - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[2,3,4]] # [0,1,2] involves parallel arcs + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** - - # network with parallel and anti-parallel arcs - - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (1, 2, 1), - (2, 3, 0), - (3, 4, 0), - (3, 2, 0), - (4, 3, 0), - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [] # [0,1,2] has parallel arcs, [2,3,4] anti-parallel + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - assert len(straight_paths) == 0 - - #************************************************************************** - - # network with cycles - - network = nx.MultiDiGraph() - network.add_edges_from([ - # simplest cycle (with reverse-parallel arcs) - (0, 1, 0), - (1, 0, 0), - # 3-node cycle (with/without reverse-/-parallel arcs) - (2, 3, 0), - (3, 4, 0), - (4, 2, 0), - # 2-node cycle (with parallel and reverse-parallel arcs) - (5, 6, 0), - (6, 5, 0), - (5, 6, 1), - (6, 5, 1), - # 3-node cycle (only without parallel arcs) - (7, 8, 0), - (8, 9, 0), - (9, 7, 0), - (9, 7, 1) - ]) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - # [0,1,0] or [1,0,1] are ruled out since they involve reverse-parallel arcs - # [5,6,5] or [6,5,6] are ruled out since they involve reverse/parallel arcs - # [7,8,9,7], etc are ruled out since they involve one parallel arc - true_straight_paths = [[2,3,4,2],[7,8,9]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - # ************************************************************************* - # ************************************************************************* - - def test_find_straight_paths_case_b(self): - # case a: - paths_must_be_oneway = True # anti-parallel arcs prevent paths - paths_must_be_unique = False # parallel/anti-parallel arcs do not prevent paths + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True - # network without straight paths - network = nx.MultiDiGraph() + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - assert len(straight_paths) == 0 - - #************************************************************************** - - # network with the simplest straight path - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - # simple cycle - (3, 4, 0), - (4, 5, 0), - (5, 3, 0), - # invalid cycle - (6, 7, 0), - (7, 6, 0) - ]) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [3, 4, 5, 3]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** - - # network with longer path - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - # - (4, 5, 0), - (5, 6, 0), - (6, 7, 0), - (7, 8, 0) - ]) - excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2, 3], [4, 5, 6, 7, 8]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - #************************************************************************** - - # network with excluded nodes - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - (3, 4, 0), - # path involving reverse arcs - (6, 5, 0), - (6, 7, 0), - (8, 7, 0), - # another - (8, 9, 0), - (10, 9, 0), - (11, 10, 0), - # cycle - (12, 13, 0), - (14, 13, 0), - (15, 12, 0) - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [2, 3, 4],[15, 12, 13],[11, 10, 9]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** - - # network with self loops and excluded nodes - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (2, 2, 0), - (1, 2, 0), - (2, 3, 0), - (3, 3, 0), - (3, 4, 0), - ]) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0, 1, 2], [2, 3, 4]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* - #************************************************************************** + def test_find_one_edge_path_w_reversed_edge(self): - # network with parallel arcs + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path network = nx.MultiDiGraph() network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (2, 3, 0), - (3, 4, 0), - # more parallel arcs - (5, 6, 0), - (6, 7, 0), - (5, 6, 1), - (6, 7, 1), - (7, 5, 0) + (0, 1, 0), (1, 0, 0) ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( - network, - excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique - ) - true_straight_paths = [[2,3,4],[0,1,2],[5,6,7,5]] - assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + def test_find_simple_straight_path(self): + + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path + network = nx.MultiDiGraph() + network.add_edges_from([ + (0, 1, 0), (1, 2, 0) + ]) + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + def test_find_simple_straight_path_with_antiparallel_edge(self): + + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path + network = nx.MultiDiGraph() + network.add_edges_from([ + (0, 1, 0), (1, 2, 0), (2, 1, 0) + ]) + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + def test_find_straight_path_cycle(self): + + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path + network = nx.MultiDiGraph() + network.add_edges_from([ + (0, 1, 0), (1, 2, 0), (2, 0, 0), (0, 2, 0), (1, 1, 0) + ]) + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0, 1]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2, 0], [1, 2, 0, 1], [2, 1, 0, 2]] + assert len(straight_paths) == len(true_straight_paths)-2 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # no self loops, excluded the "middle" node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0, 1]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[2, 0, 1]] + assert len(straight_paths) == len(true_straight_paths) + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2, 0], [1, 2, 0, 1], [2, 1, 0, 2]] + assert len(straight_paths) == len(true_straight_paths)-2 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0, 1], [1, 0, 2, 1]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0, 1], [1, 0, 2, 1]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 2, 0], [0, 2, 1]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[1, 0, 2], [2, 0, 1]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + def test_find_simple_straight_path_w_reversed_edge(self): + + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path + network = nx.MultiDiGraph() + network.add_edges_from([ + (0, 1, 0), (2, 1, 0) + ]) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) - #************************************************************************** - - # network with parallel and anti-parallel arcs - - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (1, 2, 1), - (2, 3, 0), - (3, 4, 0), - (3, 2, 0), - (4, 3, 0), - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0,1,2]] # [2,3,4] has anti-parallel arcs + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - #************************************************************************** + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + def test_find_simple_straight_path_w_reversed_edge2(self): - # network with cycles + # ********************************************************************* + # ********************************************************************* + + # network with a two edge path network = nx.MultiDiGraph() network.add_edges_from([ - # simplest cycle (with reverse-parallel arcs) - (0, 1, 0), - (1, 0, 0), - # 3-node cycle (with/without reverse-/-parallel arcs) - (2, 3, 0), - (3, 4, 0), - (4, 2, 0), - # 2-node cycle (with parallel and reverse-parallel arcs) - (5, 6, 0), - (6, 5, 0), - (5, 6, 1), - (6, 5, 1), - # 3-node cycle (only without parallel arcs) - (7, 8, 0), - (8, 9, 0), - (9, 7, 0), - (9, 7, 1), - (9, 7, 2) + (1, 0, 0), (1, 2, 0) ]) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [] + assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # do not allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - # [0,1,0] or [1,0,1] are ruled out since they involve reverse-parallel arcs - # [5,6,5] or [6,5,6] are ruled out since they involve reverse/parallel arcs - true_straight_paths = [[2,3,4,2],[7,8,9,7]] + true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, allow self loops, no excluded nodes + ignore_self_loops = True + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator(network, straight_path, excluded_nodes) - - # ************************************************************************* - # ************************************************************************* - - def test_find_straight_paths_case_c(self): + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) - # case a: - paths_must_be_oneway=False # arcs in the opposite direction do not prevent paths - paths_must_be_unique=True # anti-/parallel arcs prevent paths + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the middle node + ignore_self_loops = False + excluded_nodes = [1] - # network without straight paths - network = nx.MultiDiGraph() - excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) true_straight_paths = [] assert len(straight_paths) == len(true_straight_paths) - assert len(straight_paths) == 0 + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the start node + ignore_self_loops = False + excluded_nodes = [0] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # allow reversed edges, no self loops, excluded the end node + ignore_self_loops = False + excluded_nodes = [2] + + straight_paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2], [2, 1, 0]] + assert len(straight_paths) == len(true_straight_paths)-1 + for straight_path in straight_paths: + assert straight_path in true_straight_paths + self.path_validator( + network, + straight_path, + excluded_nodes, + consider_reversed_edges=consider_reversed_edges + ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* - # ************************************************************************** + def test_find_simplifiable_path_four_nodes(self): - # network with the simplest straight path + # ********************************************************************* + # ********************************************************************* + # network with a two edge path network = nx.MultiDiGraph() network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - # with reverse arcs - (3, 4, 0), - (5, 4, 0) + (1, 0, 0), (1, 2, 0), (2, 3, 0) ]) + + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[2, 1, 0], [0, 1, 2], [3, 4, 5], [5, 4, 3]] - assert len(straight_paths) == len(true_straight_paths)-2 + true_straight_paths = [[1, 2, 3]] + assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths self.path_validator( network, straight_path, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges ) - - # ************************************************************************** - - # network with longer straight path + + # ********************************************************************* + # ********************************************************************* - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - ]) + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False excluded_nodes = [] - straight_paths = gis_iden.find_straight_paths( + + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[0,1,2,3],[3,2,1,0]] + true_straight_paths = [[0, 1, 2, 3], [3, 2, 1, 0]] assert len(straight_paths) == len(true_straight_paths)-1 for straight_path in straight_paths: assert straight_path in true_straight_paths @@ -592,75 +1838,24 @@ class TestGisIdentify: network, straight_path, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges ) + + # ********************************************************************* + # ********************************************************************* - # ************************************************************************** - - # network with excluded nodes + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (2, 3, 0), - (3, 4, 0), - # path involving reverse arcs - (6, 5, 0), - (6, 7, 0), - (8, 7, 0), - # another - (8, 9, 0), - (10, 9, 0), - (11, 10, 0), - # cycle - (12, 13, 0), - (14, 13, 0), - (14, 15, 0), - (15, 12, 0) - ]) - excluded_nodes = [2, 8] - straight_paths = gis_iden.find_straight_paths( + straight_paths = gis_iden.find_simplifiable_paths( network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops, + include_both_directions=True ) - true_straight_paths = [ - [0,1,2],[2,1,0], - [2,3,4],[4,3,2], - [5,6,7,8],[8,7,6,5], - [8,9,10,11],[11,10,9,8], - [12,13,14,15,12],[14,13,12,15,14],[13,14,15,12,13],[15,12,13,14,15] - ] - assert len(straight_paths) == len(true_straight_paths)-4-3 - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator( - network, - straight_path, - excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique - ) - - # ************************************************************************** - - # network with self loops and excluded nodes - - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (2, 2, 0), - (1, 2, 0), - (2, 3, 0), - (3, 3, 0), - (3, 4, 0), - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths(network, excluded_nodes) - true_straight_paths = [[0, 1, 2], [2, 3, 4]] + true_straight_paths = [[0, 1, 2, 3], [3, 2, 1, 0]] assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths @@ -668,412 +1863,91 @@ class TestGisIdentify: network, straight_path, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges ) + + # ********************************************************************* + # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* - # ************************************************************************** + def test_find_simplifiable_path_four_node_cycle(self): - # network with parallel arcs + # ********************************************************************* + # ********************************************************************* + # network with a two edge path network = nx.MultiDiGraph() network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (2, 3, 0), - (3, 4, 0), - # 3-node cycle (only without parallel arcs) - (7, 8, 0), - (7, 8, 1), - (8, 9, 0), - (9, 7, 0) + (0, 1, 0), (1, 2, 0), (2, 3, 0), (0, 3, 0) ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( - network, - excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique - ) - true_straight_paths = [ - [2,3,4],[4,3,2], - [8,9,7],[7,9,8] - ] - assert len(straight_paths) == len(true_straight_paths)-1-1 - for straight_path in straight_paths: - assert straight_path in true_straight_paths - self.path_validator( - network, - straight_path, - excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique - ) - - # ************************************************************************** - # network with parallel and anti-parallel arcs - - network = nx.MultiDiGraph() - network.add_edges_from([ - (0, 1, 0), - (1, 2, 0), - (0, 1, 1), - (1, 2, 1), - (2, 3, 0), - (3, 4, 0), - (4, 5, 0), - (5, 2, 0), - (5, 2, 1), - # cycle - (6, 7, 0), - (7, 8, 0), - (8, 9, 0), - (9, 6, 0) - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( - network, + # ********************************************************************* + # ********************************************************************* + + # do not consider reversed edges + consider_reversed_edges = False + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique - ) - true_straight_paths = [ - [2,3,4,5],[5,4,3,2], - [6,7,8,9,6],[7,8,9,6,7],[8,9,6,7,8],[9,8,7,6,9], - [6,9,8,7,6],[7,6,9,8,7],[8,7,6,9,8],[9,6,7,8,9] - ] - assert len(straight_paths) == len(true_straight_paths)-1-3-4 + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + true_straight_paths = [[0, 1, 2, 3]] + assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths self.path_validator( network, straight_path, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges ) - - #************************************************************************** - - # network with cycles - - network = nx.MultiDiGraph() - network.add_edges_from([ - # simplest cycle (with reverse-parallel arcs) - (0, 1, 0), - (1, 0, 0), - # 3-node cycle (with/without reverse-/-parallel arcs) - (2, 3, 0), - (3, 4, 0), - (4, 2, 0), - # 2-node cycle (with parallel and reverse-parallel arcs) - (5, 6, 0), - (6, 5, 0), - (5, 6, 1), - (6, 5, 1), - # 3-node cycle (only without parallel arcs) - (7, 8, 0), - (8, 9, 0), - (9, 7, 0), - (9, 7, 1) - ]) - excluded_nodes = [2] - straight_paths = gis_iden.find_straight_paths( - network, + + # ********************************************************************* + # ********************************************************************* + + # consider reversed edges + consider_reversed_edges = True + + # ********************************************************************* + # ********************************************************************* + + # no reversed edges, no self loops, no excluded nodes + ignore_self_loops = False + excluded_nodes = [] + + straight_paths = gis_iden.find_simplifiable_paths( + network, excluded_nodes, - paths_must_be_oneway=True, # reverse-parallel arcs prevent paths - paths_must_be_unique=True # parallel arcs prevent paths + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) - # [0,1,0] or [1,0,1] are ruled out since they involve reverse-parallel arcs - # [5,6,5] or [6,5,6] are ruled out since they involve reverse/parallel arcs - # [7,8,9,7], etc are ruled out since they involve one parallel arc true_straight_paths = [ - [2,3,4,2],[3,4,2,3],[4,3,2,4],[2,4,3,2],[3,2,4,3],[4,2,3,4], - [7,8,9],[9,8,7] + [0, 1, 2, 3, 0], [1, 2, 3, 0, 1], [2, 3, 0, 1, 2], [3, 0, 1, 2, 3] ] - assert len(straight_paths) == len(true_straight_paths)-5-1 + assert len(straight_paths) == len(true_straight_paths)-3 for straight_path in straight_paths: assert straight_path in true_straight_paths self.path_validator( network, straight_path, excluded_nodes, - paths_must_be_oneway=paths_must_be_oneway, - paths_must_be_unique=paths_must_be_unique + consider_reversed_edges=consider_reversed_edges ) - - # ************************************************************************* - # ************************************************************************* - - # def test_find_straight_paths_case_d(self): - - # # case a: - # paths_must_be_oneway=False # reverse-parallel arcs prevent paths - # paths_must_be_unique=False # parallel arcs prevent paths - - # # network without straight paths - # network = nx.MultiDiGraph() - # excluded_nodes = [] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [] - # assert len(straight_paths) == len(true_straight_paths) - # assert len(straight_paths) == 0 - - # # ************************************************************************** - - # # network with the simplest straight path - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (1, 2, 0), - # ]) - # excluded_nodes = [] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [[0,1,2],[2,1,0]] - - # print('hello here') - # print(network.edges(keys=True, data=True)) - # print('true :' + str(true_straight_paths)) - # print('actual :' + str(straight_paths)) - # assert len(straight_paths) == len(true_straight_paths)-1 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # # ************************************************************************** - - # # network with longer straight path - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (1, 2, 0), - # (2, 3, 0), - # ]) - # excluded_nodes = [] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [[0,1,2,3],[3,2,1,0]] - # assert len(straight_paths) == len(true_straight_paths)-1 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # # ************************************************************************** - - # # network with excluded nodes - - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (1, 2, 0), - # (2, 3, 0), - # (3, 4, 0), - # ]) - - # excluded_nodes = [2] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [[0,1,2],[2,3,4],[2,1,0],[4,3,2]] - # assert len(straight_paths) == len(true_straight_paths)-2 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # # ************************************************************************** - - # # network with self loops and excluded nodes - - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (2, 2, 0), - # (1, 2, 0), - # (2, 3, 0), - # (3, 3, 0), - # (3, 4, 0), - # ]) - # excluded_nodes = [2] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [[0,1,2],[2,3,4],[4,3,2],[2,1,0]] - # assert len(straight_paths) == len(true_straight_paths)-2 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # # ************************************************************************** - - # # network with parallel arcs - - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (1, 2, 0), - # (0, 1, 1), - # (2, 3, 0), - # (3, 4, 0), - # ]) - # excluded_nodes = [2,1] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [[2,3,4],[4,3,2]] # [0,1,2] involves parallel arcs - # assert len(straight_paths) == len(true_straight_paths)-1 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # # ************************************************************************** - - # # network with parallel and anti-parallel arcs - - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # (0, 1, 0), - # (1, 2, 0), - # (0, 1, 1), - # (1, 2, 1), - # (2, 3, 0), - # (3, 4, 0), - # (3, 2, 0), - # (3, 3, 0), - # (4, 3, 0), - # ]) - # excluded_nodes = [2] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [ - # [0, 1, 2], [2, 1, 0], - # [2, 3, 4], [4, 3, 2] - # ] # all are good - # print('hello here') - # print(network.edges(keys=True, data=True)) - # print('true :' + str(true_straight_paths)) - # print('actual :' + str(straight_paths)) - # assert len(straight_paths) == len(true_straight_paths)-2 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - - # #************************************************************************** - - # # network with cycles - - # network = nx.MultiDiGraph() - # network.add_edges_from([ - # # simplest cycle (with reverse-parallel arcs) - # (0, 1, 0), - # (1, 0, 0), - # # 3-node cycle (with/without reverse-/-parallel arcs) - # (2, 3, 0), - # (3, 4, 0), - # (4, 2, 0), - # # 2-node cycle (with parallel and reverse-parallel arcs) - # (5, 6, 0), - # (6, 5, 0), - # (5, 6, 1), - # (6, 5, 1), - # # 3-node cycle (only without parallel arcs) - # (7, 8, 0), - # (8, 9, 0), - # (9, 7, 0), - # (9, 7, 1) - # ]) - # excluded_nodes = [2] - # straight_paths = gis_iden.find_straight_paths( - # network, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - # true_straight_paths = [ - # [2,3,4,2],[3,4,2,3],[4,3,2,4],[2,4,3,2],[3,2,4,3],[4,2,3,4], - # [7,8,9,7],[8,9,7,8],[9,7,8,9],[7,9,8,7],[8,7,9,8],[9,7,8,9], - # # [0,1,0],[1,0,1], - # # [5,6,5],[6,5,6] - # ] - # print('hello here') - # print(network.edges(keys=True, data=True)) - # print('true :' + str(true_straight_paths)) - # print('actual :' + str(straight_paths)) - # assert len(straight_paths) == len(true_straight_paths)-5-5 - # for straight_path in straight_paths: - # assert straight_path in true_straight_paths - # self.path_validator( - # network, - # straight_path, - # excluded_nodes, - # paths_must_be_oneway=paths_must_be_oneway, - # paths_must_be_unique=paths_must_be_unique - # ) - + + # ********************************************************************* + # ********************************************************************* + # ************************************************************************* # ************************************************************************* @@ -1213,9 +2087,9 @@ class TestGisIdentify: # ************************************************************************* # ************************************************************************* - def test_identify_all_from_a_to_b(self): + def test_identify_get_from_a_to_b(self): - # arc_keys = gis_iden.all_arcs_from_a_to_b(G, 0, 1) + # edge_keys = gis_iden.get_edges_from_a_to_b(G, 0, 1) G = nx.MultiDiGraph() @@ -1230,80 +2104,80 @@ class TestGisIdentify: # from node 0 to node 1 - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 0, 1) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 0, 1) - true_arc_keys = [(0, 1, 0)] + true_edge_keys = [(0, 1, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # from node 1 to node 0 - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 1, 0) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 1, 0) - true_arc_keys = [(1, 0, 0)] + true_edge_keys = [(1, 0, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # from node 1 to node 2 - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 1, 2) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 1, 2) - true_arc_keys = [(1, 2, 0)] + true_edge_keys = [(1, 2, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # from node 2 to node 1 - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 2, 1) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 2, 1) - true_arc_keys = [] + true_edge_keys = [] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) # from node 2 to node 3 - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 2, 3) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 2, 3) - true_arc_keys = [(2, 3, 0), (2, 3, 1)] + true_edge_keys = [(2, 3, 0), (2, 3, 1)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # from node 3 to node 2 - true_arc_keys = [(3, 2, 0)] + true_edge_keys = [(3, 2, 0)] - arc_keys = gis_iden.all_arcs_from_a_to_b(G, 3, 2) + edge_keys = gis_iden.get_edges_from_a_to_b(G, 3, 2) - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # ************************************************************************* # ************************************************************************* - def test_identify_all_arcs_involving_nodes(self): + def test_identify_get_edges_involving_nodes(self): G = nx.MultiDiGraph() - G.add_node('noarcs') + G.add_node('noedges') G.add_edges_from( [(0, 1, 0), @@ -1311,31 +2185,31 @@ class TestGisIdentify: (1, 2, 1)] ) - # no arcs - node_key = 'noarcs' - arcs = gis_iden.all_arcs_involving_node(G, node_key) - assert len(arcs) == 0 + # no edges + node_key = 'noedges' + edges = gis_iden.get_edges_involving_node(G, node_key) + assert len(edges) == 0 - # one arc + # one edge node_key = 0 - true_arcs = [(0,1,0)] - arcs = gis_iden.all_arcs_involving_node(G, node_key) - assert len(arcs) == len(true_arcs) - for arc in arcs: - assert arc in true_arcs + true_edges = [(0,1,0)] + edges = gis_iden.get_edges_involving_node(G, node_key) + assert len(edges) == len(true_edges) + for edge in edges: + assert edge in true_edges - # multiple arcs + # multiple edges node_key = 1 - true_arcs = [(0,1,0), (1,2,1), (1,2,0)] - arcs = gis_iden.all_arcs_involving_node(G, node_key) - assert len(arcs) == len(true_arcs) - for arc in arcs: - assert arc in true_arcs + true_edges = [(0,1,0), (1,2,1), (1,2,0)] + edges = gis_iden.get_edges_involving_node(G, node_key) + assert len(edges) == len(true_edges) + for edge in edges: + assert edge in true_edges # ************************************************************************* # ************************************************************************* - def test_identify_all_arcs_between_nodes(self): + def test_identify_get_edges_between_nodes(self): G = nx.MultiDiGraph() @@ -1351,80 +2225,80 @@ class TestGisIdentify: # between two nodes that are not connected - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 2, 4) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 4) - assert 0 == len(arc_keys) + assert 0 == len(edge_keys) # between nodes 0 and 1, nominal direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 0, 1) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 0, 1) - true_arc_keys = [(0, 1, 0), (1, 0, 0)] + true_edge_keys = [(0, 1, 0), (1, 0, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # between nodes 0 and 1, reverse direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 1, 0) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 1, 0) - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # between nodes 1 and 2, nominal direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 1, 2) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 1, 2) - true_arc_keys = [(1, 2, 0)] + true_edge_keys = [(1, 2, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # between nodes 1 and 2, reverse direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 2, 1) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 1) - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # between nodes 2 and 3, nominal direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 2, 3) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 3) - true_arc_keys = [(2, 3, 0), (2, 3, 1), (3, 2, 0)] + true_edge_keys = [(2, 3, 0), (2, 3, 1), (3, 2, 0)] - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # between nodes 2 and 3, reverse direction - arc_keys = gis_iden.all_arcs_between_two_nodes(G, 3, 2) + edge_keys = gis_iden.get_edges_between_two_nodes(G, 3, 2) - assert len(true_arc_keys) == len(arc_keys) + assert len(true_edge_keys) == len(edge_keys) - for arc_key in arc_keys: + for edge_key in edge_keys: - assert arc_key in true_arc_keys + assert edge_key in true_edge_keys # ************************************************************************* # ************************************************************************* - def test_identify_arcs_closest_to_nodes(self): + def test_identify_edges_closest_to_nodes(self): # network should be a OSM-nx formatted graph @@ -1441,25 +2315,25 @@ class TestGisIdentify: while len(node_keys) > number_nodes: node_keys.pop(random.randint(0, len(node_keys)-1)) - # find out which arcs are closest - arc_keys, projected_network = gis_iden.identify_arc_closest_to_node( + # find out which edges are closest + edge_keys, projected_network = gis_iden.identify_edge_closest_to_node( network=network, node_keys=node_keys ) - # for each node, verify that there is no closer arc - # prepare the arc geometries - arc_key_geos = [ - (projected_network.edges[arc_key][gis_osm.KEY_OSMNX_GEOMETRY] - if gis_osm.KEY_OSMNX_GEOMETRY in projected_network.edges[arc_key] else + # for each node, verify that there is no closer edge + # prepare the edge geometries + edge_key_geos = [ + (projected_network.edges[edge_key][gis_osm.KEY_OSMNX_GEOMETRY] + if gis_osm.KEY_OSMNX_GEOMETRY in projected_network.edges[edge_key] else LineString([ - (projected_network.nodes[arc_key[0]][gis_osm.KEY_OSMNX_X], - projected_network.nodes[arc_key[0]][gis_osm.KEY_OSMNX_Y]), - (projected_network.nodes[arc_key[1]][gis_osm.KEY_OSMNX_X], - projected_network.nodes[arc_key[1]][gis_osm.KEY_OSMNX_Y]) + (projected_network.nodes[edge_key[0]][gis_osm.KEY_OSMNX_X], + projected_network.nodes[edge_key[0]][gis_osm.KEY_OSMNX_Y]), + (projected_network.nodes[edge_key[1]][gis_osm.KEY_OSMNX_X], + projected_network.nodes[edge_key[1]][gis_osm.KEY_OSMNX_Y]) ]) ) - for arc_key in arc_keys + for edge_key in edge_keys ] for node_index, node_key in enumerate(node_keys): # prepare the node geometry @@ -1467,22 +2341,22 @@ class TestGisIdentify: projected_network.nodes[node_key][gis_osm.KEY_OSMNX_X], projected_network.nodes[node_key][gis_osm.KEY_OSMNX_Y] ) - # calculate the distances to every arc - arc_point_distances = the_point.distance(arc_key_geos) - # find the distance to the arc identified as the closest one - shortest_distance = arc_point_distances[node_index] - # the arc identified must lead to the shortest distance - assert shortest_distance == min(arc_point_distances) - - # find out which arcs are closest using the projected network - arc_keys_proj, _ = gis_iden.identify_arc_closest_to_node( + # calculate the distances to every edge + edge_point_distances = the_point.distance(edge_key_geos) + # find the distance to the edge identified as the closest one + shortest_distance = edge_point_distances[node_index] + # the edge identified must lead to the shortest distance + assert shortest_distance == min(edge_point_distances) + + # find out which edges are closest using the projected network + edge_keys_proj, _ = gis_iden.identify_edge_closest_to_node( network=projected_network, node_keys=node_keys ) - # assert that the same arcs have been returned (with the projected network) - assert len(arc_keys) == len(arc_keys_proj) - for arc_key1, arc_key2 in zip(arc_keys, arc_keys_proj): - assert arc_key1 == arc_key2 + # assert that the same edges have been returned (with the projected network) + assert len(edge_keys) == len(edge_keys_proj) + for edge_key1, edge_key2 in zip(edge_keys, edge_keys_proj): + assert edge_key1 == edge_key2 # ************************************************************************* # ************************************************************************* @@ -1526,7 +2400,7 @@ class TestGisIdentify: # connect roundabouts 1 and 3 with a edge (1, 6, {'length': 9, 'oneway': False}), (6, 1, {'length': 24, 'oneway': False}), - # create an arc between node 7 and 5 that cannot be used, + # create an edge between node 7 and 5 that cannot be used, (7, 5, {'length': 6, 'oneway': False}) ] @@ -1570,7 +2444,7 @@ class TestGisIdentify: [16, 17, 18, 19], # true roundabout with one non-existent node added [2, 3, 'h', 4], - # path whose last node does not lead to the first through a valid arc + # path whose last node does not lead to the first through a valid edge [5, 6, 7], # true roundabout without the last node (8) [5, 6, 7, 19], @@ -1599,101 +2473,6 @@ class TestGisIdentify: # ************************************************************************* # ************************************************************************* - def test_convert_edge_path(self): - - # create network - - network = nx.MultiDiGraph() - - # define and add edges - - list_edges = [ - (0, 1), (1, 1), (1, 2), (2, 3), - (3, 4), (4, 5), (5, 6), - (7, 6), (8, 7), (9, 8), - (6, 7), (7, 8), (8, 9) - ] - - network.add_edges_from(list_edges) - - # ********************************************************************* - # ********************************************************************* - - allow_reversed_edges = False - - edge_paths = [ - [(0, 1)], - [(0, 1), (1, 2)], - [(1, 2), (2, 3)], - [(0, 1), (1, 2), (2, 3)], - [(0, 1), (1, 1), (1, 2)], # self loop - [(6, 7), (7, 8), (8, 9)], # should work - [(7, 6), (8, 7), (9, 8)], # all reversed, should fail - [(7, 6), (7, 8), (8, 9)], # first reversed, should fail - [(6, 7), (8, 7), (8, 9)], # second reversed, should fail - [(6, 7), (7, 8), (9, 8)] # third reversed, should fail - ] - - expected_node_paths = [ - [0, 1], - [0, 1, 2], - [1, 2, 3], - [0, 1, 2, 3], - [0, 1, 2], - [6, 7, 8, 9], - [], - [], - [], - [] - ] - - for edge_index, edge_path in enumerate(edge_paths): - - assert gis_iden.convert_edge_path( - network, - edge_path, - allow_reversed_edges=allow_reversed_edges) == expected_node_paths[ - edge_index] - - # ********************************************************************* - # ********************************************************************* - - allow_reversed_edges = True - - expected_node_paths = [ - [0, 1], - [0, 1, 2], - [1, 2, 3], - [0, 1, 2, 3], - [0, 1, 2], - [6, 7, 8, 9], - [6, 7, 8, 9], - [6, 7, 8, 9], - [6, 7, 8, 9], - [6, 7, 8, 9] - ] - - for edge_index, edge_path in enumerate(edge_paths): - - # try: - # assert gis_mod.convert_edge_path( - # network, - # edge_path, - # allow_reversed_edges=allow_reversed_edges) == expected_node_paths[ - # edge_index] - # except AssertionError: - # print(edge_path) - # print(expected_node_paths[edge_index]) - # assert False - assert gis_iden.convert_edge_path( - network, - edge_path, - allow_reversed_edges=allow_reversed_edges) == expected_node_paths[ - edge_index] - - # ************************************************************************* - # ************************************************************************* - def test_identify_points_extremities(self): # ********************************************************************* @@ -1711,7 +2490,41 @@ class TestGisIdentify: assert repr(close_to_start) == repr([0]) assert repr(close_to_end) == repr([2]) + + # redundant points, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(1, 1), Point(2, 2), Point(3, 0)], + return_distances=True + ) + assert repr(close_to_start) == repr([0]) + assert repr(close_to_end) == repr([2]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0, 0, 0] + true_start_distances = [0, 1.41421356, 2.23606798] + true_end_distances = [2.23606798, 2.23606798, 0] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + + # ********************************************************************* + # ********************************************************************* + # redundant points, different order close_to_start, close_to_end = gis_iden.close_to_extremities( @@ -1721,6 +2534,40 @@ class TestGisIdentify: assert repr(close_to_start) == repr([2]) assert repr(close_to_end) == repr([0]) + + # redundant points, different order, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(3, 0), Point(2, 2), Point(1, 1)], + return_distances=True + ) + + assert repr(close_to_start) == repr([2]) + assert repr(close_to_end) == repr([0]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0, 0, 0] + true_start_distances = [2.23606798, 1.41421356, 0] + true_end_distances = [0, 2.23606798, 2.23606798] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + + # ********************************************************************* + # ********************************************************************* # redundant points, yet another order @@ -1731,7 +2578,38 @@ class TestGisIdentify: assert repr(close_to_start) == repr([2]) assert repr(close_to_end) == repr([1]) - + + # redundant points, yet another order, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(2, 2), Point(3, 0), Point(1, 1)], + return_distances=True + ) + + assert repr(close_to_start) == repr([2]) + assert repr(close_to_end) == repr([1]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0, 0, 0] + true_start_distances = [1.41421356, 2.23606798, 0] + true_end_distances = [2.23606798, 0, 2.23606798] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + # ********************************************************************* # ********************************************************************* @@ -1744,6 +2622,37 @@ class TestGisIdentify: assert repr(close_to_start) == repr([]) assert repr(close_to_end) == repr([1]) + + # new points, directly on the line, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(1.2, 1.2), Point(3, 0), Point(1.5, 1.5)], + return_distances=True + ) + + assert repr(close_to_start) == repr([]) + assert repr(close_to_end) == repr([1]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0, 0, 0] + true_start_distances = [0.28284271, 2.23606798, 0.70710678] + true_end_distances = [2.16333077, 0, 2.12132034] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) # ********************************************************************* # ********************************************************************* @@ -1757,6 +2666,40 @@ class TestGisIdentify: assert repr(close_to_start) == repr([0]) assert repr(close_to_end) == repr([1, 2]) + + # new points, extending beyond the line, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.5), Point(3, 0), Point(4, -2)], + return_distances=True + ) + + assert repr(close_to_start) == repr([0]) + assert repr(close_to_end) == repr([1, 2]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0.70710678, 0, 2.23606798] + true_start_distances = [0.70710678, 2.23606798, 4.24264069] + true_end_distances = [2.54950976, 0, 2.23606798] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + + # ********************************************************************* + # ********************************************************************* # new points, extending beyond the line @@ -1764,42 +2707,166 @@ class TestGisIdentify: line=line, points=[Point(0.5, 0.5), Point(1.0, 1.0), Point(4, -2)], ) - - assert repr(close_to_start) == repr([0, 1]) - assert repr(close_to_end) == repr([2]) + + assert repr(close_to_start) == repr([0, 1]) + assert repr(close_to_end) == repr([2]) + + # new points, extending beyond the line, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.5), Point(1.0, 1.0), Point(4, -2)], + return_distances=True + ) + + assert repr(close_to_start) == repr([0, 1]) + assert repr(close_to_end) == repr([2]) + abs_tols = [1e-3, 1e-3, 1e-3] + true_line_distances = [0.70710678, 0, 2.23606798] + true_start_distances = [0.70710678, 0, 4.24264069] + true_end_distances = [2.54950976, 2.23606798, 2.23606798] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + + # ********************************************************************* + # ********************************************************************* + + # new points, not on the line + # expected result: the new points appear on the geometry + + close_to_start, close_to_end = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.75), Point(3.0, -0.5)], + ) + + assert repr(close_to_start) == repr([0]) + assert repr(close_to_end) == repr([1]) + + # new points, not on the line, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.75), Point(3.0, -0.5)], + return_distances=True + ) + + assert repr(close_to_start) == repr([0]) + assert repr(close_to_end) == repr([1]) + abs_tols = [1e-3, 1e-3] + true_line_distances = [0.55901699, 0.5] + true_start_distances = [0.55901699, 2.5] + true_end_distances = [2.61007663, 0.5] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) + + # ********************************************************************* + # ********************************************************************* + + # new points, close to multiple segments + + line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]) + line = LineString(line_coords) + + close_to_start, close_to_end = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.5)], + ) + + assert repr(close_to_start) == repr([]) + assert repr(close_to_end) == repr([]) + + # new points, close to multiple segments, with distance + + (close_to_start, + close_to_end, + line_distances, + start_distances, + end_distances) = gis_iden.close_to_extremities( + line=line, + points=[Point(0.5, 0.5)], + return_distances=True + ) + + assert repr(close_to_start) == repr([]) + assert repr(close_to_end) == repr([]) + abs_tols = [1e-3] + true_line_distances = [0.5] + true_start_distances = [0.70710678] + true_end_distances = [0.70710678] + for line_d, start_d, end_d, true_line_d, true_start_d, true_end_d, abs_tol in zip( + line_distances, + start_distances, + end_distances, + true_line_distances, + true_start_distances, + true_end_distances, + abs_tols + ): + assert isclose(line_d, true_line_d, abs_tol=abs_tol) + assert isclose(start_d, true_start_d, abs_tol=abs_tol) + assert isclose(end_d, true_end_d, abs_tol=abs_tol) # ********************************************************************* # ********************************************************************* + + # point equidistant to start and end, favour start - # new points, not on the line - # expected result: the new points appear on the geometry + line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]) + line = LineString(line_coords) close_to_start, close_to_end = gis_iden.close_to_extremities( line=line, - points=[Point(0.5, 0.75), Point(3.0, -0.5)], + points=[Point(0.5, -0.5)], + use_start_point_equidistant=True ) assert repr(close_to_start) == repr([0]) - assert repr(close_to_end) == repr([1]) - + assert repr(close_to_end) == repr([]) + # ********************************************************************* # ********************************************************************* - - # new points, close to multiple segments + + # point equidistant to start and end, favour end line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]) line = LineString(line_coords) close_to_start, close_to_end = gis_iden.close_to_extremities( line=line, - points=[Point(0.5, 0.5)], + points=[Point(0.5, -0.5)], + use_start_point_equidistant=False ) assert repr(close_to_start) == repr([]) - assert repr(close_to_end) == repr([]) - - # ********************************************************************* - # ********************************************************************* + assert repr(close_to_end) == repr([0]) # ************************************************************************* # ************************************************************************* @@ -2329,7 +3396,7 @@ class TestGisIdentify: truncate_by_edge=True ) - # create arc to trigger the negative case with a different length + # create edge to trigger the negative case with a different length # (317812803, 317812802, 2) edge_k = network.add_edge( 317812803, @@ -2343,7 +3410,7 @@ class TestGisIdentify: 'length': 27.601+1} ) assert edge_k == 2 - # create arc tp trigger the negative case with a different geometry + # create edge tp trigger the negative case with a different geometry # (317812802, 317812803, 2) edge_k = network.add_edge( 317812802, @@ -2360,31 +3427,31 @@ class TestGisIdentify: ) assert edge_k == 2 - # find arcs that have matching arcs in the reverse direction - for arc_key in network.edges(keys=True): - arc_dict = network.get_edge_data(*arc_key) + # find edges that have matching edges in the reverse direction + for edge_key in network.edges(keys=True): + edge_dict = network.get_edge_data(*edge_key) for _attr in gis_osm.KEYS_OSMNX_EDGES_ESSENTIAL: - assert _attr in arc_dict.keys() - # for each arc, find if there is one in the opposite direction - if network.has_edge(u=arc_key[1], v=arc_key[0]): - # there is an arc in the opposite sense - for other_arc_key in gis_iden.all_arcs_from_a_to_b( + assert _attr in edge_dict.keys() + # for each edge, find if there is one in the opposite direction + if network.has_edge(u=edge_key[1], v=edge_key[0]): + # there is an edge in the opposite sense + for other_edge_key in gis_iden.get_edges_from_a_to_b( network=network, - node_start=arc_key[1], - node_end=arc_key[0] + node_start=edge_key[1], + node_end=edge_key[0] ): - # check if the arcs are the same but in reverse + # check if the edges are the same but in reverse if gis_iden.edges_are_in_reverse( network, - edge_a=arc_key, - edge_b=other_arc_key + edge_a=edge_key, + edge_b=other_edge_key ): - # the arcs are the same but in reverse: + # the edges are the same but in reverse: # - all attributes have to be the same or lists with # the same content, as in a set, except for # the geometry and reversed attributes - fw_dict = network.get_edge_data(*arc_key) - rv_dict = network.get_edge_data(*other_arc_key) + fw_dict = network.get_edge_data(*edge_key) + rv_dict = network.get_edge_data(*other_edge_key) # should have the same attributes assert set(fw_dict.keys()) == set(rv_dict.keys()) @@ -2424,11 +3491,11 @@ class TestGisIdentify: # nor reversed attributes: they must match assert attr_value == rv_dict[attr_key] - else: # the arcs are not the same in reverse + else: # the edges are not the same in reverse # at least one of their attributes must be different or # incompatible - fw_dict = network.get_edge_data(*arc_key) - rv_dict = network.get_edge_data(*other_arc_key) + fw_dict = network.get_edge_data(*edge_key) + rv_dict = network.get_edge_data(*other_edge_key) error_raised = False try: # should have the same attributes @@ -2563,13 +3630,11 @@ class TestGisIdentify: assert gis_iden.is_edge_consistent_with_geometry(network, edge_key) edge_key = (1104936963, 115831, 0) assert gis_iden.is_edge_consistent_with_geometry(network, edge_key) - - - + # ************************************************************************* # ************************************************************************* - def test_valid_paths(self): + def test_valid_node_paths(self): # ********************************************************************* # ********************************************************************* @@ -2604,6 +3669,7 @@ class TestGisIdentify: invalid_node_paths = [ [], # empty list + [1], # single node path [-1,0,1,2,3], # nodes do not belong to the network [3,2,1,0], # path 1 reversed [3,4,5,6], # path 2 reversed @@ -2611,16 +3677,72 @@ class TestGisIdentify: [6,7,8,10] # node 10 is connected to node 8 but not the other way arou. ] + # make sure valid node paths are valid for path in valid_node_paths: - assert gis_iden.is_node_path(network, path) - # make sure invalid node paths are invalid - for path in invalid_node_paths: - assert not gis_iden.is_node_path(network, path) - + + # ********************************************************************* + # ********************************************************************* + + consider_reversed_edges = True + + valid_node_paths = [ + [0,1,2,3], + [6,5,4,3], + [6,7,8,9], + [10,8,9], + [3,2,1,0], # path 1 reversed + [3,4,5,6], # path 2 reversed + [9,8,7,6], # path 3 reversed + [6,7,8,10] # node 10 is connected to node 8 but not the other way arou. + ] + + invalid_node_paths = [ + [], # empty list + [1], # single node path + [-1,0,1,2,3], # nodes do not belong to the network + ] + + # make sure valid node paths are valid + for path in valid_node_paths: + assert gis_iden.is_node_path(network, path, consider_reversed_edges) + # make sure invalid node paths are invalid + for path in invalid_node_paths: + assert not gis_iden.is_node_path( + network, + path, + consider_reversed_edges + ) + + # ************************************************************************* + # ************************************************************************* + + def test_valid_edge_paths(self): + + # ********************************************************************* + # ********************************************************************* + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + + list_edges = [ + (0,1),(1,2),(2,3), # path 1 + (4,3),(5,4),(6,5), # path 2 + (6,7),(7,8),(8,9), # path 3 + (2,2), # self loop on path 1 + (4,4), # self loop on path 2 + (9,9), # self loop on path 3 + (10,8) # extra lone neighbour for node 8 on + ] + + network.add_edges_from(list_edges) + # ********************************************************************* # ********************************************************************* @@ -2790,18 +3912,17 @@ class TestGisIdentify: # ********************************************************************* # ********************************************************************* - - def test_simplifiable_paths(self): + + # ************************************************************************* + # ************************************************************************* - # ********************************************************************* - # ********************************************************************* + def test_straight_paths_reversed_edges_self_loops(self): # create network network = nx.MultiDiGraph() # define and add edges - list_edges = [ (0,1),(1,2),(2,3), # path 1 (4,3),(5,4),(6,5), # path 2 @@ -2811,75 +3932,78 @@ class TestGisIdentify: (9,9), # self loop on path 3 (10,8) # extra lone neighbour for node 8 on ] - network.add_edges_from(list_edges) - # ********************************************************************* - # ********************************************************************* - - # node paths - - path_as_node_keys = True - - # arc directions matter - - ignore_arc_directions = False + # reversed edges are okay, self loops too ignore_self_loops = True + consider_reversed_edges = True # valid node paths - valid_straight_node_paths = [ [0,1,2], [1,2,3], [0,1,2,3], [5,4,3], [6,5,4], - [6,5,4,3] + [6,5,4,3], + [0,1], # just two nodes + [0,1,2,3,4], # node 4 is connected using an edge in the opposite dir. + [6,5,4,3,2], # node 2 is connected using an edge in the opposite dir. + [3,4,5,6], # the path is reversed ] # invalid node paths - invalid_straight_node_paths = [ - [0,1], # just two nodes - [0,1,2,3,4], # node 4 is connected using an arc in the opposite dir. - [6,5,4,3,2], # node 2 is connected using an arc in the opposite dir. [6,7,8,9], # node 8 has three neighbours (7, 9 and 10) - [3,4,5,6], # the path is reversed [0,4,1], # node 4 is not a neighbour of nodes 0 and 1 [11,3,4,5,6] # there is no node 11 ] # make sure valid node paths are valid - for path in valid_straight_node_paths: - - assert gis_iden.is_path_simplifiable( + assert gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) # make sure invalid node paths are invalid - for path in invalid_straight_node_paths: - - assert not gis_iden.is_path_simplifiable( + assert not gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) # ********************************************************************* # ********************************************************************* - # ignore arc directions + # ************************************************************************* + # ************************************************************************* + + def test_straight_paths_no_reversed_edges_self_loops(self): + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + list_edges = [ + (0,1),(1,2),(2,3), # path 1 + (4,3),(5,4),(6,5), # path 2 + (6,7),(7,8),(8,9), # path 3 + (2,2), # self loop on path 1 + (4,4), # self loop on path 2 + (9,9), # self loop on path 3 + (10,8) # extra lone neighbour for node 8 on + ] + network.add_edges_from(list_edges) - ignore_arc_directions = True + # no reversed edges, self loops are okay ignore_self_loops = True + consider_reversed_edges = False # valid node paths @@ -2887,25 +4011,25 @@ class TestGisIdentify: [0,1,2], [1,2,3], [0,1,2,3], - [2,1,0], - [3,2,1], - [3,2,1,0], [5,4,3], [6,5,4], [6,5,4,3], - [3,4,5], - [4,5,6], - [3,4,5,6], - [0,1,2,3,4,5,6,7,8], - [8,7,6,5,4,3,2,1,0], - [0,1,2,3,4], - [6,5,4,3,2] + [0,1], # just two nodes ] # invalid node paths invalid_straight_node_paths = [ - [0,1], # just two nodes + [2,1,0], # reversed path + [3,2,1], # reversed path + [3,2,1,0], # reversed path + [3,4,5], # reversed path + [4,5,6], # reversed path + [3,4,5,6], # reversed path + [0,1,2,3,4,5,6,7,8], # path with reversed elements + [8,7,6,5,4,3,2,1,0], # path with reversed element + [0,1,2,3,4], # path with reversed elements + [6,5,4,3,2], # the last edge is reversed [6,7,8,9], # node 8 has three neighbours (7, 9 and 10) [0,4,1], # node 4 is not a neighbour of nodes 0 and 1 [11,3,4,5,6], # there is no node 11 @@ -2913,34 +4037,50 @@ class TestGisIdentify: [9,8,7,6,5,4,3,2,1,0] # node 8 has three neighbours (7, 9 and 10) ] - # make sure valid node paths are valid - - for path in valid_straight_node_paths: - - assert gis_iden.is_path_simplifiable( + # make sure valid node paths are valid + for path in valid_straight_node_paths: + assert gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) - # make sure invalid node paths are invalid - + # make sure invalid node paths are invalid for path in invalid_straight_node_paths: - - assert not gis_iden.is_path_simplifiable( + assert not gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) # ********************************************************************* # ********************************************************************* - ignore_arc_directions = False + # ************************************************************************* + # ************************************************************************* + + def test_straight_paths_no_reversed_edges_no_self_loops(self): + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + list_edges = [ + (0,1),(1,2),(2,3), # path 1 + (4,3),(5,4),(6,5), # path 2 + (6,7),(7,8),(8,9), # path 3 + (2,2), # self loop on path 1 + (4,4), # self loop on path 2 + (9,9), # self loop on path 3 + (10,8) # extra lone neighbour for node 8 on + ] + network.add_edges_from(list_edges) + + # no reversed edges, self loops are not okay ignore_self_loops = False + consider_reversed_edges = False # (0,1),(1,2),(2,3), # path 1 # (4,3),(5,4),(6,5), # path 2 @@ -2969,36 +4109,50 @@ class TestGisIdentify: [2,1,0] ] - # make sure valid node paths are valid - - for path in valid_straight_node_paths: - - assert gis_iden.is_path_simplifiable( + # make sure valid node paths are valid + for path in valid_straight_node_paths: + assert gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) - # make sure invalid node paths are invalid - + # make sure invalid node paths are invalid for path in invalid_straight_node_paths: - - assert not gis_iden.is_path_simplifiable( + assert not gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) # ********************************************************************* # ********************************************************************* - # ignore arc directions + # ************************************************************************* + # ************************************************************************* + + def test_straight_paths_reversed_edges_no_self_loops(self): + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + list_edges = [ + (0,1),(1,2),(2,3), # path 1 + (4,3),(5,4),(6,5), # path 2 + (6,7),(7,8),(8,9), # path 3 + (2,2), # self loop on path 1 + (4,4), # self loop on path 2 + (9,9), # self loop on path 3 + (10,8) # extra lone neighbour for node 8 on + ] + network.add_edges_from(list_edges) - ignore_arc_directions = True + # reversed edges are okay, self loops are not ignore_self_loops = False + consider_reversed_edges = True # valid node paths @@ -3020,33 +4174,72 @@ class TestGisIdentify: [6,5,4,3] ] - # make sure valid node paths are valid - - for path in valid_straight_node_paths: - - assert gis_iden.is_path_simplifiable( + # make sure valid node paths are valid + for path in valid_straight_node_paths: + assert gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) - # make sure invalid node paths are invalid - + # make sure invalid node paths are invalid for path in invalid_straight_node_paths: + assert not gis_iden.is_path_straight( + network, + path, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops) + + # ********************************************************************* + # ********************************************************************* - assert not gis_iden.is_path_simplifiable( + # ************************************************************************* + # ************************************************************************* + + def test_straight_path_parallel_antiparallel_edges(self): + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + list_edges = [ + (0,1,0),(1,2,0),(2,3,0), # path 1 + (4,5,0),(5,6,0),(6,7,0), # path 2 + (8,9,0),(9,10,0),(10,11,0), # path 3 + # extra edges + (0,1,0), # path 1 + (5,4,0), # path 2 + (8,9,0),(11,10,0) # path 3 + ] + network.add_edges_from(list_edges) + + # reversed edges are okay, self loops are not + + ignore_self_loops = True + consider_reversed_edges = True + + # valid node paths + + valid_straight_node_paths = [ + [0,1,2,3], + [4,5,6,7], + [8,9,10,11] + ] + + # make sure valid node paths are valid + for path in valid_straight_node_paths: + assert gis_iden.is_path_straight( network, path, - path_as_node_keys=path_as_node_keys, - ignore_arc_directions=ignore_arc_directions, + consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops) # ********************************************************************* # ********************************************************************* - #************************************************************************** - #************************************************************************** + # ************************************************************************* + # ************************************************************************* def test_nearest_node_keys(self): @@ -3094,7 +4287,7 @@ class TestGisIdentify: unconnected_node_keys.append(new_node_key) - #************************************************************************** + # ********************************************************************* # find the nearest nodes using the osmnx method @@ -3118,7 +4311,7 @@ class TestGisIdentify: assert node_key == nearest_node_keys[i] - #************************************************************************** + # ********************************************************************* # find the nodes nearest to select nodes excluding themselves @@ -3177,6 +4370,62 @@ class TestGisIdentify: min(all_distances), actual_distance, abs_tol=1) + + # ************************************************************************* + # ************************************************************************* + + def test_finding_roundabouts(self): + + # network should be a OSM-nx formatted graph + network = ox.graph_from_point( + (55.71654,9.11728), + network_type='drive', + custom_filter='["highway"~"residential|tertiary|unclassified|service"]', + truncate_by_edge=True + ) + # find all roundabouts + roundabouts = gis_iden.find_roundabouts(network) + # confirm they are roundabouts + for roundabout in roundabouts: + assert gis_iden.is_roundabout(network, roundabout) + + # find roundabouts with constraints + roundabouts = gis_iden.find_roundabouts( + network, + maximum_perimeter=200, + minimum_perimeter=25, + maximum_number_nodes=6, + minimum_number_nodes=4 + ) + # confirm they are roundabouts + for roundabout in roundabouts: + assert gis_iden.is_roundabout(network, roundabout) + + # ************************************************************************* + # ************************************************************************* + + def test_finding_reversed_edges(self): + + # network should be a OSM-nx formatted graph + network = ox.graph_from_point( + (55.71654,9.11728), + network_type='drive', + custom_filter='["highway"~"residential|tertiary|unclassified|service"]', + truncate_by_edge=True + ) + # find edges in reverse + edges_in_rev = gis_iden.find_edges_in_reverse(network) + # confirm + for edge_key, reversed_edge_keys in edges_in_rev.items(): + for _edge_key in reversed_edge_keys: + assert gis_iden.edges_are_in_reverse( + network, + edge_key, + _edge_key + ) + + # ************************************************************************* + # ************************************************************************* -#****************************************************************************** -#****************************************************************************** \ No newline at end of file +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file diff --git a/tests/test_gis_modify.py b/tests/test_gis_modify.py index 96c21ba8694fe4c60edf75c0b5de20a18a756bb8..4bbd4195dbd1855cc0cb38a7fb9ef0734cc25d04 100644 --- a/tests/test_gis_modify.py +++ b/tests/test_gis_modify.py @@ -146,113 +146,64 @@ class TestGisModify: # get the network _net = ox.graph_from_point( - (55.71654,9.11728), + (55.71654, 9.11728), network_type='drive', custom_filter='["highway"~"residential|tertiary|unclassified|service"]', truncate_by_edge=True ) - - # find non-overlapping paths - node_keys = tuple(_net.nodes()) - number_paths = 3 - min_path_length = 3 - max_path_length = 5 - max_number_tries = 5 - paths = [] - for path_index in range(number_paths): - try_index = 0 - while try_index < max_number_tries: - try_index += 1 - # pick random start node - first_node = node_keys[random.randint(0, len(node_keys)-1)] - # check if it is already on a path - is_in_a_path = False - for _path in paths: - if first_node in _path: - is_in_a_path = True # TODO: reach this statement - break - if is_in_a_path: - # the node is already on a path: restart - continue # TODO: reach this statement - # initialise the path - path = [first_node] - # try forming a path - try_failed = False - while len(path) < max_path_length: - # find successors - for node_key in _net.successors(path[-1]): - # it must not be in any path already identified - is_in_a_path = False - for _path in paths: - if node_key in _path or node_key in path: - is_in_a_path = True # TODO: reach this statement - break - if is_in_a_path: - # the node is already on a path, try another successor - continue # TODO: reach this statement - # the node is not on other paths, append it - path.append(node_key) - # break out of the successor loop - break - # find predecessors - for node_key in _net.predecessors(path[0]): - # it must not be in any path already identified - is_in_a_path = False - for _path in paths: - if node_key in _path or node_key in path: - is_in_a_path = True - break - if is_in_a_path: - # the node is already on a path, try another predecessor - continue - # the node is not on other paths, append it - path.insert(0, node_key) - # break out of the predecessor loop - break - # if the path is not long enough and cannot be extended - # the path cannot be extended if: - # 1) there no neighbours beyond the edges - # 2) the neighbours are already on the path - if (len(path) < min_path_length and - len(set(_net.predecessors(path[0]))-set(path))==0 and - len(set(_net.successors(path[-1]))-set(path))==0 ): - # the try failed: break out of the path forming loop - try_failed = True # TODO: reach this statement - break - elif len(path) >= min_path_length: - # minimum path length achieved, break out of the loop - break - if try_failed: - # the try failed: try again - continue # TODO: reach this statement - # trim it and move on to the next path - path = path[ - 0:random.randint(min_path_length, max_path_length) - ] - break - # the path should be long enough at this point - assert len(path) >= min_path_length - # path is done - paths.append(path) + # find paths + paths = gis_iden.find_simplifiable_paths( + _net, + excluded_nodes=[], + ignore_self_loops=False, + consider_reversed_edges=True) + # modify an edge in one of the paths to have list attributes + _edge_key = tuple(gis_iden.get_edges_from_a_to_b( + _net, + paths[0][0], # first path, first node + paths[0][1] # first paht, second node + ))[0] + _net.add_edge( + *_edge_key, + **{osm.KEY_OSMNX_ONEWAY: [ + _net.edges[_edge_key][osm.KEY_OSMNX_ONEWAY] + ], + osm.KEY_OSMNX_REVERSED: [ + _net.edges[_edge_key][osm.KEY_OSMNX_REVERSED] + ] + } + ) # measure the distances true_path_lengths = [ - gis_calc.node_path_length(_net, path) - for path in paths + gis_calc.node_path_length(_net, path) for path in paths ] - # replace the paths - new_path_arcs = [ - gis_mod.replace_path(_net, path) - for path in paths + new_path_edges = [ + gis_mod.replace_path(_net, path) for path in paths ] - # compare - for arc_key, true_length in zip(new_path_arcs, true_path_lengths): + for edge_key, true_length in zip(new_path_edges, true_path_lengths): assert isclose( - _net.edges[arc_key][gis_iden.osm.KEY_OSMNX_LENGTH], + _net.edges[edge_key][gis_iden.osm.KEY_OSMNX_LENGTH], true_length, - abs_tol=23.41 # 23.400000000000034 + abs_tol=1e-3 # 23.400000000000034 ) + + # ************************************************************************* + # ************************************************************************* + + def test_replace_nonsimplifiable_path(self): + + _net = nx.MultiDiGraph() + + path = [0, 1] + + error_raised = False + try: + gis_mod.replace_path(_net, path) + except ValueError: + error_raised = True + assert error_raised # ************************************************************************* # ************************************************************************* @@ -283,17 +234,17 @@ class TestGisModify: osm.KEY_OSMNX_REVERSED: False, osm.KEY_OSMNX_ONEWAY: False}) ]) - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0)]) + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] number_edges = network.number_of_edges() if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 2 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -301,35 +252,35 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] # the geometry must have 3 points assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 3 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 3.861 # 3.8601244551728087 ) # print('hallo1') - # print(tuple(network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords)) - # print(network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH]) - # print(new_arc_key_lengths[new_arc_key]) + # print(tuple(network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords)) + # print(network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH]) + # print(new_edge_key_lengths[new_edge_key]) # ********************************************************************* - # small path with 3 arcs + # small path with 3 edges network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" @@ -356,7 +307,7 @@ class TestGisModify: osm.KEY_OSMNX_REVERSED: False, osm.KEY_OSMNX_ONEWAY: False}) ]) - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0),(2,3,0)]) + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0),(2,3,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] network.edges[(2,3,0)][osm.KEY_OSMNX_LENGTH] = lengths[(2,3,0)] @@ -364,10 +315,10 @@ class TestGisModify: if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2,3] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 3 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -375,31 +326,31 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] # the geometry must have 4 points assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 4 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 7.33 # 7.327521377403173 ) # print('hallo2') - # print(tuple(network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords)) - # print(network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH]) - # print(new_arc_key_lengths[new_arc_key]) + # print(tuple(network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords)) + # print(network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH]) + # print(new_edge_key_lengths[new_edge_key]) # # ********************************************************************* @@ -426,21 +377,21 @@ class TestGisModify: osm.KEY_OSMNX_ONEWAY: False}) ]) # build create geometries - arc01_geo = LineString([(12.00,56.00),(11.99,56.00),(12.00,56.01)]) - arc12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) - network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = arc01_geo - network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = arc12_geo - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0)]) + edge01_geo = LineString([(12.00,56.00),(11.99,56.00),(12.00,56.01)]) + edge12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) + network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = edge01_geo + network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = edge12_geo + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] number_edges = network.number_of_edges() if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 2 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -448,25 +399,25 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 #1 if not project_the_graph else 1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] # the geometry must have 5 points assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 5 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 12.2 # -12.178460200064364 ) @@ -495,23 +446,23 @@ class TestGisModify: osm.KEY_OSMNX_ONEWAY: False}) ]) # build create geometries - arc01_geo = LineString([(12.00,56.00),(11.99,56.00),(12.00,56.01)]) - arc12_geo = LineString([(12.01,56.02),(12.02,56.00),(12.00,56.01)]) - network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = arc01_geo - network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = arc12_geo + edge01_geo = LineString([(12.00,56.00),(11.99,56.00),(12.00,56.01)]) + edge12_geo = LineString([(12.01,56.02),(12.02,56.00),(12.00,56.01)]) + network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = edge01_geo + network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = edge12_geo network.edges[(0,1,0)][osm.KEY_OSMNX_REVERSED] = False network.edges[(1,2,0)][osm.KEY_OSMNX_REVERSED] = True - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0)]) + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] number_edges = network.number_of_edges() if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 2 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -519,30 +470,30 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 5 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 12.2 # -12.178460200064364 ) # print('hallo4') - # print(tuple(network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords)) - # print(network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH]) - # print(new_arc_key_lengths[new_arc_key]) + # print(tuple(network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords)) + # print(network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH]) + # print(new_edge_key_lengths[new_edge_key]) # ********************************************************************* @@ -569,23 +520,23 @@ class TestGisModify: osm.KEY_OSMNX_ONEWAY: False}) ]) # build create geometries - arc01_geo = LineString([(12.00,56.01),(11.99,56.00),(12.00,56.00)]) - arc12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) - network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = arc01_geo - network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = arc12_geo + edge01_geo = LineString([(12.00,56.01),(11.99,56.00),(12.00,56.00)]) + edge12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) + network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = edge01_geo + network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = edge12_geo network.edges[(0,1,0)][osm.KEY_OSMNX_REVERSED] = True network.edges[(1,2,0)][osm.KEY_OSMNX_REVERSED] = False - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0)]) + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] number_edges = network.number_of_edges() if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 2 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -593,34 +544,34 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 5 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 12.2 # -12.178460200064364 ) # print('hallo5') - # print(tuple(network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords)) - # print(network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH]) - # print(new_arc_key_lengths[new_arc_key]) + # print(tuple(network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords)) + # print(network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH]) + # print(new_edge_key_lengths[new_edge_key]) # ********************************************************************* - # small path with 3 arcs, but featuring reversed geometries already + # small path with 3 edges, but featuring reversed geometries already network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" @@ -648,16 +599,16 @@ class TestGisModify: osm.KEY_OSMNX_ONEWAY: False}) ]) # build create geometries - arc01_geo = LineString([(12.00,56.00),(11.99,56.02),(12.00,56.01)]) - arc12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) - arc23_geo = LineString([(12.01,56.02),(12.05,56.10),(12.02,56.04)]) - network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = arc01_geo - network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = arc12_geo - network.edges[(2,3,0)][osm.KEY_OSMNX_GEOMETRY] = arc23_geo + edge01_geo = LineString([(12.00,56.00),(11.99,56.02),(12.00,56.01)]) + edge12_geo = LineString([(12.00,56.01),(12.02,56.00),(12.01,56.02)]) + edge23_geo = LineString([(12.01,56.02),(12.05,56.10),(12.02,56.04)]) + network.edges[(0,1,0)][osm.KEY_OSMNX_GEOMETRY] = edge01_geo + network.edges[(1,2,0)][osm.KEY_OSMNX_GEOMETRY] = edge12_geo + network.edges[(2,3,0)][osm.KEY_OSMNX_GEOMETRY] = edge23_geo network.edges[(0,1,0)][osm.KEY_OSMNX_REVERSED] = False network.edges[(1,2,0)][osm.KEY_OSMNX_REVERSED] = True network.edges[(2,3,0)][osm.KEY_OSMNX_REVERSED] = False - lengths = gis_calc.arc_lengths(network, arc_keys=[(0,1,0),(1,2,0),(2,3,0)]) + lengths = gis_calc.edge_lengths(network, edge_keys=[(0,1,0),(1,2,0),(2,3,0)]) network.edges[(0,1,0)][osm.KEY_OSMNX_LENGTH] = lengths[(0,1,0)] network.edges[(1,2,0)][osm.KEY_OSMNX_LENGTH] = lengths[(1,2,0)] network.edges[(2,3,0)][osm.KEY_OSMNX_LENGTH] = lengths[(2,3,0)] @@ -665,10 +616,10 @@ class TestGisModify: if project_the_graph: network = ox.project_graph(G=network) path = [0,1,2,3] - new_arc_key = gis_mod.replace_path(network, path=path) - # a new arc should exist - assert network.has_edge(*new_arc_key) - # assert that two arcs are removed and one is created + new_edge_key = gis_mod.replace_path(network, path=path) + # a new edge should exist + assert network.has_edge(*new_edge_key) + # assert that two edges are removed and one is created assert network.number_of_edges() - number_edges == 1 - 3 # intermediate nodes should not exist any longer for node in path[1:-1]: @@ -676,30 +627,30 @@ class TestGisModify: # the distances need to match assert isclose( sum(lengths.values()), - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], abs_tol=1e-3 ) - # the new arc needs to have a geometry because it is not simple - assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_arc_key] + # the new edge needs to have a geometry because it is not simple + assert osm.KEY_OSMNX_GEOMETRY in network.edges[new_edge_key] assert len( tuple( - network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) ) == 7 - new_arc_key_lengths = gis_calc.arc_lengths( + new_edge_key_lengths = gis_calc.edge_lengths( network, - arc_keys=[new_arc_key] + edge_keys=[new_edge_key] ) - # the geometry's length needs to match that of the arc's + # the geometry's length needs to match that of the edge's assert isclose( - network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH], - new_arc_key_lengths[new_arc_key], + network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH], + new_edge_key_lengths[new_edge_key], abs_tol=1 if not project_the_graph else 37.77 # -37.76434146326574 ) # print('hallo6') - # print(tuple(network.edges[new_arc_key][osm.KEY_OSMNX_GEOMETRY].coords)) - # print(network.edges[new_arc_key][osm.KEY_OSMNX_LENGTH]) - # print(new_arc_key_lengths[new_arc_key]) + # print(tuple(network.edges[new_edge_key][osm.KEY_OSMNX_GEOMETRY].coords)) + # print(network.edges[new_edge_key][osm.KEY_OSMNX_LENGTH]) + # print(new_edge_key_lengths[new_edge_key]) # ********************************************************************* # ********************************************************************* @@ -748,7 +699,7 @@ class TestGisModify: for edge_key in removed_edges: # confirm that there is at least one edge in reverse reverse_edge_found = False - for other_edge_key in gis_iden.all_arcs_from_a_to_b( + for other_edge_key in gis_iden.get_edges_from_a_to_b( network, edge_key[1], edge_key[0]): @@ -774,7 +725,7 @@ class TestGisModify: for edge_key in removed_edges: # confirm that there is at least one edge in reverse reverse_edge_found = False - for other_edge_key in gis_iden.all_arcs_from_a_to_b( + for other_edge_key in gis_iden.get_edges_from_a_to_b( network, edge_key[1], edge_key[0]): @@ -834,7 +785,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_remove_longer_arcs(self): + def test_remove_longer_edges(self): # simple example network = nx.MultiDiGraph() @@ -847,13 +798,13 @@ class TestGisModify: (1,2,1,{'length': 5}), (2,0,1,{'length': 6}), ]) - initial_number_arcs = network.number_of_edges() - true_arcs_removed = [(0,1,1),(1,2,1),(2,0,1)] - arcs_removed = gis_mod.remove_longer_parallel_arcs(network) - assert len(arcs_removed) == len(true_arcs_removed) - for arc_key in arcs_removed: - assert arc_key in true_arcs_removed - assert network.number_of_edges() == initial_number_arcs - len(arcs_removed) + initial_number_edges = network.number_of_edges() + true_edges_removed = [(0,1,1),(1,2,1),(2,0,1)] + edges_removed = gis_mod.remove_longer_parallel_edges(network) + assert len(edges_removed) == len(true_edges_removed) + for edge_key in edges_removed: + assert edge_key in true_edges_removed + assert network.number_of_edges() == initial_number_edges - len(edges_removed) # example with more than one alternative network = nx.MultiDiGraph() @@ -866,15 +817,15 @@ class TestGisModify: (0,1,2,{'length': 5}), (0,1,3,{'length': 6}), ]) - initial_number_arcs = network.number_of_edges() - true_arcs_removed = [(0,1,1),(0,1,2),(0,1,3)] - arcs_removed = gis_mod.remove_longer_parallel_arcs(network) - assert len(arcs_removed) == len(true_arcs_removed) - for arc_key in arcs_removed: - assert arc_key in true_arcs_removed - assert network.number_of_edges() == initial_number_arcs - len(arcs_removed) + initial_number_edges = network.number_of_edges() + true_edges_removed = [(0,1,1),(0,1,2),(0,1,3)] + edges_removed = gis_mod.remove_longer_parallel_edges(network) + assert len(edges_removed) == len(true_edges_removed) + for edge_key in edges_removed: + assert edge_key in true_edges_removed + assert network.number_of_edges() == initial_number_edges - len(edges_removed) - # example with opposite arcs (that won't be removed) + # example with opposite edges (that won't be removed) network = nx.MultiDiGraph() network.add_edges_from([ (0,1,0,{'length': 3}), @@ -889,15 +840,15 @@ class TestGisModify: (2,1,0,{'length': 8}), (0,2,0,{'length': 9}), ]) - initial_number_arcs = network.number_of_edges() - true_arcs_removed = [(0,1,1),(1,2,1),(2,0,1)] - arcs_removed = gis_mod.remove_longer_parallel_arcs(network) - assert len(arcs_removed) == len(true_arcs_removed) - for arc_key in arcs_removed: - assert arc_key in true_arcs_removed - assert network.number_of_edges() == initial_number_arcs - len(arcs_removed) + initial_number_edges = network.number_of_edges() + true_edges_removed = [(0,1,1),(1,2,1),(2,0,1)] + edges_removed = gis_mod.remove_longer_parallel_edges(network) + assert len(edges_removed) == len(true_edges_removed) + for edge_key in edges_removed: + assert edge_key in true_edges_removed + assert network.number_of_edges() == initial_number_edges - len(edges_removed) - # example with opposite arcs (that will be removed) + # example with opposite edges (that will be removed) network = nx.MultiDiGraph() network.add_edges_from([ @@ -913,15 +864,15 @@ class TestGisModify: (2,1,0,{'length': 8}), (0,2,0,{'length': 9}), ]) - initial_number_arcs = network.number_of_edges() - true_arcs_removed = [ + initial_number_edges = network.number_of_edges() + true_edges_removed = [ (0,1,1),(1,2,1),(2,0,1),(1,0,0),(2,1,0),(0,2,0) ] - arcs_removed = gis_mod.remove_longer_parallel_arcs(network, True) - assert len(arcs_removed) == len(true_arcs_removed) - for arc_key in arcs_removed: - assert arc_key in true_arcs_removed - assert network.number_of_edges() == initial_number_arcs - len(arcs_removed) + edges_removed = gis_mod.remove_longer_parallel_edges(network, True) + assert len(edges_removed) == len(true_edges_removed) + for edge_key in edges_removed: + assert edge_key in true_edges_removed + assert network.number_of_edges() == initial_number_edges - len(edges_removed) # test using non-integers as node keys network = nx.MultiDiGraph() @@ -938,15 +889,15 @@ class TestGisModify: ('b','a',0,{'length': 8}), (0,'b',0,{'length': 9}), ]) - initial_number_arcs = network.number_of_edges() - true_arcs_removed = [ + initial_number_edges = network.number_of_edges() + true_edges_removed = [ (0,'a',1),('a','b',1),('b',0,1),('a',0,0),('b','a',0),(0,'b',0) ] - arcs_removed = gis_mod.remove_longer_parallel_arcs(network, True) - assert len(arcs_removed) == len(true_arcs_removed) - for arc_key in arcs_removed: - assert arc_key in true_arcs_removed - assert network.number_of_edges() == initial_number_arcs - len(arcs_removed) + edges_removed = gis_mod.remove_longer_parallel_edges(network, True) + assert len(edges_removed) == len(true_edges_removed) + for edge_key in edges_removed: + assert edge_key in true_edges_removed + assert network.number_of_edges() == initial_number_edges - len(edges_removed) # ************************************************************************* # ************************************************************************* @@ -955,10 +906,15 @@ class TestGisModify: # ********************************************************************* - # minimal example - + # example without dead ends network = nx.MultiDiGraph() + nodes_removed = gis_mod.remove_dead_ends(network) + assert len(nodes_removed) == 0 + + # ********************************************************************* + # minimal example + network = nx.MultiDiGraph() network.add_edges_from([ (0,1,0), (1,2,0), (2,0,0), # removable edges @@ -966,15 +922,10 @@ class TestGisModify: (4,1,0), (5,2,0) ]) - nodes_removed = gis_mod.remove_dead_ends(network) - true_nodes_removed = [3,4,5] - for node_key in true_nodes_removed: - assert node_key in nodes_removed - assert len(nodes_removed) == len(true_nodes_removed) # ********************************************************************* @@ -1063,15 +1014,15 @@ class TestGisModify: # ********************************************************************* - # example with forward and reverse arcs + # example with forward and reverse edges network = nx.MultiDiGraph() network.add_edges_from([ (0,1,0), (1,2,0), (2,0,0), # removable edges - (3,0,0),(0,3,0),(3,6,0), # branch has forward and reverse arcs - (4,1,0),(1,4,0),(4,7,0), # branch has forward and reverse arcs + (3,0,0),(0,3,0),(3,6,0), # branch has forward and reverse edges + (4,1,0),(1,4,0),(4,7,0), # branch has forward and reverse edges (5,2,0),(5,8,0) # branch not affected ]) @@ -1116,10 +1067,10 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def example_connect_points_to_arcs_osmnx( + def example_connect_points_to_edges_osmnx( self, _net: nx.MultiDiGraph, - use_two_arcs = False, + use_two_edges = False, store_unsimplified_geo: bool = False, project_network: bool = False): @@ -1127,8 +1078,8 @@ class TestGisModify: # _net = network.copy() all_node_keys = tuple(_net.nodes()) - all_arc_keys = tuple(_net.edges(keys=True)) - number_arcs = len(all_arc_keys) + all_edge_keys = tuple(_net.edges(keys=True)) + number_edges = len(all_edge_keys) number_nodes = len(all_node_keys) # create three random nodes @@ -1158,57 +1109,57 @@ class TestGisModify: # store the key node_keys.append(node_key) - # pick random arcs to which to connect each new point - arc_keys = [ - all_arc_keys[random.randint(0,number_arcs-1)] + # pick random edges to which to connect each new point + edge_keys = [ + all_edge_keys[random.randint(0,number_edges-1)] for i in range(number_new_points) ] # record paths and distances all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( _net, - arc_key[0], - arc_key[1], + edge_key[0], + edge_key[1], cutoff=2+len(node_keys) # only one extra node ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length(_net, path) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = _net.number_of_edges() + initial_number_edges = _net.number_of_edges() if project_network: _net = ox.project_graph(G=_net) # connect them - mod_net, _, _, _ = gis_mod.connect_nodes_to_arcs( + mod_net, _, _, _ = gis_mod.connect_nodes_to_edges( _net, node_keys, - arc_keys, + edge_keys, store_unsimplified_geometries=store_unsimplified_geo, - use_one_arc_per_direction=use_two_arcs + use_one_edge_per_direction=use_two_edges ) # check the changes: all self.check_split_recreate_connect( network=mod_net, - arc_keys=arc_keys, + edge_keys=edge_keys, node_keys=node_keys, all_paths_ab=all_paths_ab, original_path_lengths_ab=original_path_lengths_ab, abs_tol=0.294 # 0.29327665321937957 ) - # there should be at least one extra arc per node + # there should be at least one extra edge per node - assert mod_net.number_of_edges() >= initial_number_arcs + len(node_keys) + assert mod_net.number_of_edges() >= initial_number_edges + len(node_keys) # ********************************************************************* # ********************************************************************* @@ -1216,7 +1167,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_connect_points_to_arcs_osmnx_default(self): + def test_connect_points_to_edges_osmnx_default(self): # get the network network = ox.graph_from_point( @@ -1227,10 +1178,20 @@ class TestGisModify: ), truncate_by_edge=True ) - - self.example_connect_points_to_arcs_osmnx( + # find one edge and create a reversed version w/ inconsistent geometry + # to cover a few more lines of tests in check_split_recreate_connect + for edge_key in network.edges(keys=True): + break + edge_dict = network.get_edge_data(*edge_key) + network.add_edge( + edge_key[1], + edge_key[0], + **edge_dict + ) + # try method + self.example_connect_points_to_edges_osmnx( _net=network, - use_two_arcs=False, + use_two_edges=False, store_unsimplified_geo=False, project_network=False ) @@ -1238,7 +1199,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_connect_points_to_arcs_osmnx_2arcs(self): + def test_connect_points_to_edges_osmnx_2edges(self): # get the network network = ox.graph_from_point( @@ -1249,10 +1210,20 @@ class TestGisModify: ), truncate_by_edge=True ) - - self.example_connect_points_to_arcs_osmnx( + # find one edge and create a reversed version w/ inconsistent geometry + # to cover a few more lines of tests in check_split_recreate_connect + for edge_key in network.edges(keys=True): + break + edge_dict = network.get_edge_data(*edge_key) + network.add_edge( + edge_key[1], + edge_key[0], + **edge_dict + ) + # try method + self.example_connect_points_to_edges_osmnx( _net=network, - use_two_arcs=True, + use_two_edges=True, store_unsimplified_geo=False, project_network=False ) @@ -1260,7 +1231,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_connect_points_to_arcs_osmnx_unsimplified(self): + def test_connect_points_to_edges_osmnx_unsimplified(self): # get the network network = ox.graph_from_point( @@ -1271,10 +1242,10 @@ class TestGisModify: ), truncate_by_edge=True ) - - self.example_connect_points_to_arcs_osmnx( + # try method + self.example_connect_points_to_edges_osmnx( _net=network, - use_two_arcs=False, + use_two_edges=False, store_unsimplified_geo=True, project_network=False ) @@ -1282,7 +1253,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_connect_points_to_arcs_osmnx_projected(self): + def test_connect_points_to_edges_osmnx_projected(self): # get the network network = ox.graph_from_point( @@ -1293,10 +1264,10 @@ class TestGisModify: ), truncate_by_edge=True ) - - self.example_connect_points_to_arcs_osmnx( + # try method + self.example_connect_points_to_edges_osmnx( _net=network, - use_two_arcs=False, + use_two_edges=False, store_unsimplified_geo=False, project_network=True ) @@ -1307,41 +1278,41 @@ class TestGisModify: def check_split_recreate_connect( self, network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol: float = 2e-3): new_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1], + edge_key[0], + edge_key[1], cutoff=2+len(node_keys) # keep it as short as possible ) ) - for arc_key in arc_keys + for edge_key in edge_keys } # how to check? - # for each arc and node pair - for arc_key, node_key in zip(arc_keys, node_keys): + # for each edge and node pair + for edge_key, node_key in zip(edge_keys, node_keys): # there must be a path between the original start and end nodes - assert nx.has_path(network, arc_key[0], arc_key[1]) + assert nx.has_path(network, edge_key[0], edge_key[1]) # the nodes must be connected to the start node - assert nx.has_path(network, arc_key[0], node_key) + assert nx.has_path(network, edge_key[0], node_key) # length from beginning to end must be roughly the same - for new_path in new_paths_ab[arc_key]: - if new_path in all_paths_ab[arc_key]: - # old path: it must have the same length (the arc is unchanged) + for new_path in new_paths_ab[edge_key]: + if new_path in all_paths_ab[edge_key]: + # old path: it must have the same length (the edge is unchanged) assert isclose( gis_calc.edge_path_length( network, new_path ), - original_path_lengths_ab[arc_key][ - all_paths_ab[arc_key].index(new_path) + original_path_lengths_ab[edge_key][ + all_paths_ab[edge_key].index(new_path) ], abs_tol=abs_tol ) @@ -1352,32 +1323,32 @@ class TestGisModify: network, new_path ), - original_path_lengths_ab[arc_key][ - all_paths_ab[arc_key].index([arc_key]) + original_path_lengths_ab[edge_key][ + all_paths_ab[edge_key].index([edge_key]) ], abs_tol=abs_tol ) - # each arc with a geometry must have the correct start and end points - for arc_key in network.edges(keys=True): + # each edge with a geometry must have the correct start and end points + for edge_key in network.edges(keys=True): # if gis_mod.KEY_OSMNX_GEOMETRY - if osm.KEY_OSMNX_GEOMETRY in network.edges[arc_key]: + if osm.KEY_OSMNX_GEOMETRY in network.edges[edge_key]: # has geometry, now check if the start/end points add up - start_x = network.nodes[arc_key[0]][osm.KEY_OSMNX_X] - start_y = network.nodes[arc_key[0]][osm.KEY_OSMNX_Y] - end_x = network.nodes[arc_key[1]][osm.KEY_OSMNX_X] - end_y = network.nodes[arc_key[1]][osm.KEY_OSMNX_Y] + start_x = network.nodes[edge_key[0]][osm.KEY_OSMNX_X] + start_y = network.nodes[edge_key[0]][osm.KEY_OSMNX_Y] + end_x = network.nodes[edge_key[1]][osm.KEY_OSMNX_X] + end_y = network.nodes[edge_key[1]][osm.KEY_OSMNX_Y] coords = tuple( - network.edges[arc_key][osm.KEY_OSMNX_GEOMETRY].coords + network.edges[edge_key][osm.KEY_OSMNX_GEOMETRY].coords ) - if gis_iden.is_edge_consistent_with_geometry(network, arc_key): + if gis_iden.is_edge_consistent_with_geometry(network, edge_key): # no reversed attr or not reversed assert start_x == coords[0][0] assert start_y == coords[0][1] assert end_x == coords[-1][0] assert end_y == coords[-1][1] - else: + else: # reversed attr and reversed assert start_x == coords[-1][0] assert start_y == coords[-1][1] @@ -1390,113 +1361,113 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_connect_points_to_arcs(self): + def test_connect_points_to_edges(self): - # single arc, one intermediate point + # single edge, one intermediate point network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=0.4, y=0.6) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2] - arc_keys = [arc_key] + edge_keys = [edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length(network, path) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert not network.has_edge(*arc_keys[0]) - # arc balance: one arc less, two replacements, plus one per node - assert network.number_of_edges() - initial_number_arcs == 2-1+1 - # make sure the arc was correctly recreated + # the original edge must no longer exist + assert not network.has_edge(*edge_keys[0]) + # edge balance: one edge less, two replacements, plus one per node + assert network.number_of_edges() - initial_number_edges == 2-1+1 + # make sure the edge was correctly recreated self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol=1e-3) - # make sure the number of arcs is the expected one + # make sure the number of edges is the expected one # ********************************************************************* - # single arc, one point (start) + # single edge, one point (start) network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=0.3, y=0) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2] - arc_keys = [arc_key] + edge_keys = [edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length(network, path) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert network.has_edge(*arc_keys[0]) - # arc balance: one extra arc - assert network.number_of_edges() - initial_number_arcs == 1 + # the original edge must no longer exist + assert network.has_edge(*edge_keys[0]) + # edge balance: one extra edge + assert network.number_of_edges() - initial_number_edges == 1 self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, @@ -1504,57 +1475,57 @@ class TestGisModify: # ********************************************************************* - # single arc, one point (end) + # single edge, one point (end) network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=0.6, y=1) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2] - arc_keys = [arc_key] + edge_keys = [edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc should still exist - assert network.has_edge(*arc_keys[0]) - # arc balance: one extra - assert network.number_of_edges() - initial_number_arcs == 1 + # the original edge should still exist + assert network.has_edge(*edge_keys[0]) + # edge balance: one extra + assert network.number_of_edges() - initial_number_edges == 1 self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, @@ -1562,59 +1533,59 @@ class TestGisModify: # ********************************************************************* - # single arc, one point of each kind + # single edge, one point of each kind network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=0.3, y=0) # start network.add_node(3, x=0.5, y=0.5) network.add_node(4, x=0.7, y=1) # end network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2,3,4] - arc_keys = [arc_key,arc_key,arc_key] + edge_keys = [edge_key,edge_key,edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert not network.has_edge(*arc_keys[0]) - # arc balance: one arc less, two replacements, plus one per node - assert network.number_of_edges() - initial_number_arcs == 2-1+3 + # the original edge must no longer exist + assert not network.has_edge(*edge_keys[0]) + # edge balance: one edge less, two replacements, plus one per node + assert network.number_of_edges() - initial_number_edges == 2-1+3 self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, @@ -1622,60 +1593,60 @@ class TestGisModify: # ********************************************************************* - # test multiple nodes closer to the same arc + # test multiple nodes closer to the same edge network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=-0.3, y=0.2) network.add_node(3, x=0.5, y=0.5) network.add_node(4, x=-0.7, y=0.8) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2,3,4] - arc_keys = [arc_key,arc_key,arc_key] + edge_keys = [edge_key,edge_key,edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert not network.has_edge(*arc_keys[0]) - # arc balance: one arc less, four replacements, plus one per node - assert network.number_of_edges() - initial_number_arcs == 4-1+3 + # the original edge must no longer exist + assert not network.has_edge(*edge_keys[0]) + # edge balance: one edge less, four replacements, plus one per node + assert network.number_of_edges() - initial_number_edges == 4-1+3 self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, @@ -1683,12 +1654,12 @@ class TestGisModify: # ********************************************************************* - # test arc geometries with three points + # test edge geometries with three points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,0.5),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=0.1, y=0.25) # closer to the first segment (split) @@ -1697,187 +1668,187 @@ class TestGisModify: network.add_node(5, x=0.1, y=0.5) # closer to the middle point (split) network.add_node(6, x=0, y=1.1) # closer to the end point (no split) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2,3,4,5,6] - arc_keys = [arc_key,arc_key,arc_key,arc_key,arc_key] + edge_keys = [edge_key,edge_key,edge_key,edge_key,edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert not network.has_edge(*arc_keys[0]) + # the original edge must no longer exist + assert not network.has_edge(*edge_keys[0]) # make sure everything adds up self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol=1e-3) - # arc balance: one arc less, four replacements, plus one per node - assert network.number_of_edges() - initial_number_arcs == 4-1+5 + # edge balance: one edge less, four replacements, plus one per node + assert network.number_of_edges() - initial_number_edges == 4-1+5 # ********************************************************************* - # test nodes closer to the same point on an arc + # test nodes closer to the same point on an edge network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,0.5),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_node(2, x=-0.1, y=0.5) # closer to the middle point (split) network.add_node(5, x=0.1, y=0.5) # closer to the middle point (split) network.add_edge( - *arc_key, + *edge_key, geometry=line, length=gis_calc.great_circle_distance_along_path(line), undirected=False ) node_keys = [2,5] - arc_keys = [arc_key,arc_key] + edge_keys = [edge_key,edge_key] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc must no longer exist - assert not network.has_edge(*arc_keys[0]) + # the original edge must no longer exist + assert not network.has_edge(*edge_keys[0]) # make sure everything adds up self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol=1e-3) - # arc balance: one arc less, two replacements, plus one per node - assert network.number_of_edges() - initial_number_arcs == 2-1+2 + # edge balance: one edge less, two replacements, plus one per node + assert network.number_of_edges() - initial_number_edges == 2-1+2 # ********************************************************************* - # test connecting points to two different arcs + # test connecting points to two different edges network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line1 = LineString([(0,0),(0,0.5),(0,1)]) - arc_key1 = (0,1,0) + edge_key1 = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) line2 = LineString([(1,0),(1,0.5),(1,1)]) - arc_key2 = (2,3,0) + edge_key2 = (2,3,0) network.add_node(2, x=1, y=0) network.add_node(3, x=1, y=1) # unconnected nodes - network.add_node(4, x=-0.5, y=0.5) # closer to the first arc - network.add_node(5, x=1.5, y=0.5) # closer to the second arc + network.add_node(4, x=-0.5, y=0.5) # closer to the first edge + network.add_node(5, x=1.5, y=0.5) # closer to the second edge network.add_edge( - *arc_key1, + *edge_key1, geometry=line1, length=gis_calc.great_circle_distance_along_path(line1), undirected=False ) network.add_edge( - *arc_key2, + *edge_key2, geometry=line2, length=gis_calc.great_circle_distance_along_path(line2), undirected=False ) node_keys = [4,5] - arc_keys = [arc_key1,arc_key2] + edge_keys = [edge_key1,edge_key2] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arcs should no longer exist - assert not network.has_edge(*arc_keys[0]) - assert not network.has_edge(*arc_keys[1]) + # the original edges should no longer exist + assert not network.has_edge(*edge_keys[0]) + assert not network.has_edge(*edge_keys[1]) # make sure everything adds up self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol=1e-3) - # arc balance: two fewer arcs, four extra, plus one per node - assert network.number_of_edges() - initial_number_arcs == 4-2+2 + # edge balance: two fewer edges, four extra, plus one per node + assert network.number_of_edges() - initial_number_edges == 4-2+2 # ********************************************************************* @@ -1886,350 +1857,350 @@ class TestGisModify: network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line1 = LineString([(0,0),(0,0.5),(0,1)]) - arc_key1 = (0,1,0) + edge_key1 = (0,1,0) network.add_node(0, x=0, y=0) network.add_node(1, x=0, y=1) network.add_edge( - *arc_key1, + *edge_key1, geometry=line1, length=gis_calc.great_circle_distance_along_path(line1), undirected=False ) node_keys = [1] - arc_keys = [arc_key1] + edge_keys = [edge_key1] all_paths_ab = { - arc_key: list( + edge_key: list( nx.all_simple_edge_paths( network, - arc_key[0], - arc_key[1] + edge_key[0], + edge_key[1] ) ) - for arc_key in arc_keys + for edge_key in edge_keys } original_path_lengths_ab = { - arc_key: [ + edge_key: [ gis_calc.edge_path_length( network, path ) - for path in all_paths_ab[arc_key] + for path in all_paths_ab[edge_key] ] - for arc_key in arc_keys + for edge_key in edge_keys } - initial_number_arcs = network.number_of_edges() - network, _, _, _ = gis_mod.connect_nodes_to_arcs( + initial_number_edges = network.number_of_edges() + network, _, _, _ = gis_mod.connect_nodes_to_edges( network=network, node_keys=node_keys, - arc_keys=arc_keys + edge_keys=edge_keys ) - # the original arc still exists - assert network.has_edge(*arc_keys[0]) + # the original edge still exists + assert network.has_edge(*edge_keys[0]) # make sure everything adds up self.check_split_recreate_connect( network, - arc_keys, + edge_keys, node_keys, all_paths_ab, original_path_lengths_ab, abs_tol=1e-3) - # arc balance: two fewer arcs, four extra, plus one per node - assert network.number_of_edges() - initial_number_arcs == 0 + # edge balance: two fewer edges, four extra, plus one per node + assert network.number_of_edges() - initial_number_edges == 0 # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_01(self): + def test_recreate_edges_01(self): # create network network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" - # create and add simple arc to the network + # create and add simple edge to the network line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - # recreate the arc using an intermediate point + # recreate the edge using an intermediate point - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0.5)] + edge_key: [Point(0,0.5)] } ) - # verify if the arc was recreated - assert len(recreated_arcs) == 1 - assert arc_key in recreated_arcs + # verify if the edge was recreated + assert len(recreated_edges) == 1 + assert edge_key in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 1 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - # verify that the replacing arcs exist + assert len(connection_node_keys_per_edge[edge_key]) == 1 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + # verify that the replacing edges exist assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][0], + v=edge_key[1], ) # make sure the geometries make sense assert isclose( length( network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) ]['geometry'] ) + length( network.edges[ - (connection_node_keys_per_arc[arc_key][0], arc_key[1], 0) + (connection_node_keys_per_edge[edge_key][0], edge_key[1], 0) ]['geometry'] ), - length(network.edges[arc_key]['geometry']), + length(network.edges[edge_key]['geometry']), abs_tol=1e-3 ) # verify the geometries - arc_01 = (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) - arc_02 = (connection_node_keys_per_arc[arc_key][0], arc_key[1], 0) + edge_01 = (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) + edge_02 = (connection_node_keys_per_edge[edge_key][0], edge_key[1], 0) assert tuple( - network.edges[arc_01]['geometry'].coords) == ((0,0),(0,0.5)) + network.edges[edge_01]['geometry'].coords) == ((0,0),(0,0.5)) assert tuple( - network.edges[arc_02]['geometry'].coords) == ((0,0.5),(0,1)) + network.edges[edge_02]['geometry'].coords) == ((0,0.5),(0,1)) # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_02(self): + def test_recreate_edges_02(self): # test using multiple segments and points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" - # create and add simple arc to the network + # create and add simple edge to the network line = LineString([(0,0),(0,0.5),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - # recreate the arc using intermediate points + # recreate the edge using intermediate points - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0.2),Point(0,0.8)] + edge_key: [Point(0,0.2),Point(0,0.8)] } ) - # verify if the arc was recreated - assert len(recreated_arcs) == 1 - assert arc_key in recreated_arcs + # verify if the edge was recreated + assert len(recreated_edges) == 1 + assert edge_key in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 2 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) - # verify that the replacing arcs exist + assert len(connection_node_keys_per_edge[edge_key]) == 2 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) + # verify that the replacing edges exist assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=connection_node_keys_per_arc[arc_key][1], + u=connection_node_keys_per_edge[edge_key][0], + v=connection_node_keys_per_edge[edge_key][1], ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][1], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][1], + v=edge_key[1], ) # make sure the geometries make sense assert isclose( length( network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) ]['geometry'] ) + length( network.edges[ - (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][1], + (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][1], 0) ]['geometry'] ) + length( network.edges[ - (connection_node_keys_per_arc[arc_key][1], arc_key[1], 0) + (connection_node_keys_per_edge[edge_key][1], edge_key[1], 0) ]['geometry'] ), - length(network.edges[arc_key]['geometry']), + length(network.edges[edge_key]['geometry']), abs_tol=1e-3 ) # verify the geometries - arc_01 = (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) - arc_02 = (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][1], + edge_01 = (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) + edge_02 = (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][1], 0) - arc_03 = (connection_node_keys_per_arc[arc_key][1], arc_key[1], 0) + edge_03 = (connection_node_keys_per_edge[edge_key][1], edge_key[1], 0) assert tuple( - network.edges[arc_01]['geometry'].coords) == ((0,0),(0,0.2)) + network.edges[edge_01]['geometry'].coords) == ((0,0),(0,0.2)) assert tuple( - network.edges[arc_02]['geometry'].coords) == ( + network.edges[edge_02]['geometry'].coords) == ( (0,0.2),(0,0.5),(0,0.8)) assert tuple( - network.edges[arc_03]['geometry'].coords) == ((0,0.8),(0,1)) + network.edges[edge_03]['geometry'].coords) == ((0,0.8),(0,1)) # ********************************************************************* # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_03(self): + def test_recreate_edges_03(self): # test using equidistant points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" - # create and add simple arc to the network + # create and add simple edge to the network line = LineString([(0,0),(0,0.5),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - # recreate the arc using an intermediate point + # recreate the edge using an intermediate point - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0.25),Point(0,0.75)] + edge_key: [Point(0,0.25),Point(0,0.75)] } ) - # verify if the arc was recreated - assert len(recreated_arcs) == 1 - assert arc_key in recreated_arcs + # verify if the edge was recreated + assert len(recreated_edges) == 1 + assert edge_key in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 2 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) - # verify that the replacing arcs exist + assert len(connection_node_keys_per_edge[edge_key]) == 2 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) + # verify that the replacing edges exist assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=connection_node_keys_per_arc[arc_key][1], + u=connection_node_keys_per_edge[edge_key][0], + v=connection_node_keys_per_edge[edge_key][1], ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][1], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][1], + v=edge_key[1], ) # make sure the geometries make sense assert isclose( length( network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) ]['geometry'] ) + length( network.edges[ - (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][1], + (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][1], 0) ]['geometry'] ) + length( network.edges[ - (connection_node_keys_per_arc[arc_key][1], arc_key[1], 0) + (connection_node_keys_per_edge[edge_key][1], edge_key[1], 0) ]['geometry'] ), - length(network.edges[arc_key]['geometry']), + length(network.edges[edge_key]['geometry']), abs_tol=1e-3 ) # verify the geometries - arc_01 = (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) - arc_02 = (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][1], + edge_01 = (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) + edge_02 = (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][1], 0) - arc_03 = (connection_node_keys_per_arc[arc_key][1], arc_key[1], 0) + edge_03 = (connection_node_keys_per_edge[edge_key][1], edge_key[1], 0) assert tuple( - network.edges[arc_01]['geometry'].coords) == ((0,0),(0,0.25)) + network.edges[edge_01]['geometry'].coords) == ((0,0),(0,0.25)) assert tuple( - network.edges[arc_02]['geometry'].coords) == ( + network.edges[edge_02]['geometry'].coords) == ( (0,0.25),(0,0.5),(0,0.75)) assert tuple( - network.edges[arc_03]['geometry'].coords) == ((0,0.75),(0,1)) + network.edges[edge_03]['geometry'].coords) == ((0,0.75),(0,1)) # ********************************************************************* # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_04(self): + def test_recreate_edges_04(self): # test using start points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0)] + edge_key: [Point(0,0)] } ) - # verify that the arc was not recreated - assert len(recreated_arcs) == 0 # it should not be recreated - assert arc_key not in recreated_arcs + # verify that the edge was not recreated + assert len(recreated_edges) == 0 # it should not be recreated + assert edge_key not in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 1 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert arc_key[0] == connection_node_keys_per_arc[arc_key][0] - # verify that there are not replacing arcs + assert len(connection_node_keys_per_edge[edge_key]) == 1 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert edge_key[0] == connection_node_keys_per_edge[edge_key][0] + # verify that there are not replacing edges assert not network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][0], + v=edge_key[1], ) # ********************************************************************* @@ -2237,42 +2208,42 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_05(self): + def test_recreate_edges_05(self): # test using end points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,1)] + edge_key: [Point(0,1)] } ) - # verify that the arc was not recreated - assert len(recreated_arcs) == 0 # it should not be recreated - assert arc_key not in recreated_arcs + # verify that the edge was not recreated + assert len(recreated_edges) == 0 # it should not be recreated + assert edge_key not in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 1 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert arc_key[1] == connection_node_keys_per_arc[arc_key][0] - # verify that there are no replacing arcs + assert len(connection_node_keys_per_edge[edge_key]) == 1 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert edge_key[1] == connection_node_keys_per_edge[edge_key][0] + # verify that there are no replacing edges assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert not network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][0], + v=edge_key[1], ) # ********************************************************************* @@ -2280,54 +2251,54 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_06(self): + def test_recreate_edges_06(self): # test using multiple start points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0),Point(0,0)] + edge_key: [Point(0,0),Point(0,0)] } ) - # verify that the arc was not recreated - assert len(recreated_arcs) == 0 # it should not be recreated - assert arc_key not in recreated_arcs + # verify that the edge was not recreated + assert len(recreated_edges) == 0 # it should not be recreated + assert edge_key not in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 2 + assert len(connection_node_keys_per_edge[edge_key]) == 2 # verify that both nodes exist - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) # verify that both nodes are the start nodes - assert arc_key[0] == connection_node_keys_per_arc[arc_key][0] - assert arc_key[0] == connection_node_keys_per_arc[arc_key][1] - # verify that there are not replacing arcs + assert edge_key[0] == connection_node_keys_per_edge[edge_key][0] + assert edge_key[0] == connection_node_keys_per_edge[edge_key][1] + # verify that there are not replacing edges assert not network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert not network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][1] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][1] ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][0], + v=edge_key[1], ) assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][1], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][1], + v=edge_key[1], ) # ********************************************************************* @@ -2335,69 +2306,69 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_recreate_arcs_07(self): + def test_recreate_edges_07(self): # test using multiple end points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" line = LineString([(0,0),(0,1)]) - arc_key = (0,1,0) + edge_key = (0,1,0) network.add_edge( - *arc_key, + *edge_key, geometry=line ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,1),Point(0,1)] + edge_key: [Point(0,1),Point(0,1)] } ) - # verify that the arc was not recreated - assert len(recreated_arcs) == 0 # it should not be recreated - assert arc_key not in recreated_arcs + # verify that the edge was not recreated + assert len(recreated_edges) == 0 # it should not be recreated + assert edge_key not in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 2 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) - assert arc_key[1] == connection_node_keys_per_arc[arc_key][0] - assert arc_key[1] == connection_node_keys_per_arc[arc_key][1] - # verify that there are no replacing arcs + assert len(connection_node_keys_per_edge[edge_key]) == 2 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) + assert edge_key[1] == connection_node_keys_per_edge[edge_key][0] + assert edge_key[1] == connection_node_keys_per_edge[edge_key][1] + # verify that there are no replacing edges assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][1] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][1] ) assert not network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][0], + v=edge_key[1], ) assert not network.has_edge( - u=connection_node_keys_per_arc[arc_key][1], - v=arc_key[1], + u=connection_node_keys_per_edge[edge_key][1], + v=edge_key[1], ) # ********************************************************************* - def test_recreate_arcs_08(self): + def test_recreate_edges_08(self): - # test using geometries that do not match the arc declaration + # test using geometries that do not match the edge declaration network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" - # create and add simple arc to the network + # create and add simple edge to the network line = LineString([(0,1),(0,0.5),(0,0)]) - arc_key = (0,1,0) - arc_dict = { + edge_key = (0,1,0) + edge_dict = { 'geometry':line, 'reversed':False, 'oneway':True, @@ -2405,74 +2376,74 @@ class TestGisModify: 'length': length(line) } network.add_edge( - *arc_key, - **arc_dict + *edge_key, + **edge_dict ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - # recreate the arc using intermediate points + # recreate the edge using intermediate points - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [Point(0,0),Point(0,0.2),Point(0,0.8),Point(0,1)] + edge_key: [Point(0,0),Point(0,0.2),Point(0,0.8),Point(0,1)] } ) - # verify if the arc was recreated - assert len(recreated_arcs) == 1 - assert arc_key in recreated_arcs + # verify if the edge was recreated + assert len(recreated_edges) == 1 + assert edge_key in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 4 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) - assert network.has_node(connection_node_keys_per_arc[arc_key][2]) - assert network.has_node(connection_node_keys_per_arc[arc_key][3]) - # verify that the replacing arcs exist + assert len(connection_node_keys_per_edge[edge_key]) == 4 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) + assert network.has_node(connection_node_keys_per_edge[edge_key][2]) + assert network.has_node(connection_node_keys_per_edge[edge_key][3]) + # verify that the replacing edges exist # the first connection node is the start node - assert arc_key[0] == connection_node_keys_per_arc[arc_key][0] + assert edge_key[0] == connection_node_keys_per_edge[edge_key][0] # the last connection node is the end node - assert arc_key[1] == connection_node_keys_per_arc[arc_key][3] + assert edge_key[1] == connection_node_keys_per_edge[edge_key][3] # the start node connects to the second point assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][1] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][1] ) # the second point connects to the third assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][1], - v=connection_node_keys_per_arc[arc_key][2] + u=connection_node_keys_per_edge[edge_key][1], + v=connection_node_keys_per_edge[edge_key][2] ) # the third point connects to the end node assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][2], - v=arc_key[1] + u=connection_node_keys_per_edge[edge_key][2], + v=edge_key[1] ) # make sure the geometries make sense assert network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][1], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][1], 0) ]['geometry'] == LineString([(0,0),(0,0.2)]) assert network.edges[ - (connection_node_keys_per_arc[arc_key][1], - connection_node_keys_per_arc[arc_key][2], + (connection_node_keys_per_edge[edge_key][1], + connection_node_keys_per_edge[edge_key][2], 0) ]['geometry'] == LineString([(0,0.2),(0,0.5),(0,0.8)]) assert network.edges[ - (connection_node_keys_per_arc[arc_key][2], arc_key[1], 0) + (connection_node_keys_per_edge[edge_key][2], edge_key[1], 0) ]['geometry'] == LineString([(0,0.8),(0,1)]) # make sure the lengths add up assert isclose( network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][1], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][1], 0) ]['length'], gis_calc.great_circle_distance_along_path(LineString([(0,0),(0,0.2)])), abs_tol=2e-3 ) assert isclose( network.edges[ - (connection_node_keys_per_arc[arc_key][1], - connection_node_keys_per_arc[arc_key][2], + (connection_node_keys_per_edge[edge_key][1], + connection_node_keys_per_edge[edge_key][2], 0)]['length'], gis_calc.great_circle_distance_along_path( LineString([(0,0.2),(0,0.5),(0,0.8)]) @@ -2481,7 +2452,7 @@ class TestGisModify: ) assert isclose( network.edges[ - (connection_node_keys_per_arc[arc_key][2], arc_key[1], 0)]['length'], + (connection_node_keys_per_edge[edge_key][2], edge_key[1], 0)]['length'], gis_calc.great_circle_distance_along_path( LineString([(0,0.8),(0,1)]) ), @@ -2492,44 +2463,44 @@ class TestGisModify: if key == osm.KEY_OSMNX_GEOMETRY or key == osm.KEY_OSMNX_LENGTH: continue assert network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][1], 0) - ][key] == arc_dict[key] + (edge_key[0], connection_node_keys_per_edge[edge_key][1], 0) + ][key] == edge_dict[key] assert network.edges[ - (connection_node_keys_per_arc[arc_key][1], - connection_node_keys_per_arc[arc_key][2], - 0)][key] == arc_dict[key] + (connection_node_keys_per_edge[edge_key][1], + connection_node_keys_per_edge[edge_key][2], + 0)][key] == edge_dict[key] assert network.edges[ - (connection_node_keys_per_arc[arc_key][2], arc_key[1], 0) - ][key] == arc_dict[key] + (connection_node_keys_per_edge[edge_key][2], edge_key[1], 0) + ][key] == edge_dict[key] # verify the geometries - arc_01 = (arc_key[0], connection_node_keys_per_arc[arc_key][1], 0) - arc_02 = (connection_node_keys_per_arc[arc_key][1], - connection_node_keys_per_arc[arc_key][2], + edge_01 = (edge_key[0], connection_node_keys_per_edge[edge_key][1], 0) + edge_02 = (connection_node_keys_per_edge[edge_key][1], + connection_node_keys_per_edge[edge_key][2], 0) - arc_03 = (connection_node_keys_per_arc[arc_key][2], arc_key[1], 0) + edge_03 = (connection_node_keys_per_edge[edge_key][2], edge_key[1], 0) assert tuple( - network.edges[arc_01]['geometry'].coords) == ((0,0),(0,0.2)) + network.edges[edge_01]['geometry'].coords) == ((0,0),(0,0.2)) assert tuple( - network.edges[arc_02]['geometry'].coords) == ( + network.edges[edge_02]['geometry'].coords) == ( (0,0.2),(0,0.5),(0,0.8)) assert tuple( - network.edges[arc_03]['geometry'].coords) == ((0,0.8),(0,1)) + network.edges[edge_03]['geometry'].coords) == ((0,0.8),(0,1)) # ********************************************************************* - def test_recreate_arcs_09(self): + def test_recreate_edges_09(self): # test using reversed geometries and multiple start points network = nx.MultiDiGraph() network.graph['crs'] = "EPSG:4326" - # create and add simple arc to the network + # create and add simple edge to the network line = LineString([(0,1),(0,0)]) - arc_key = (0,1,0) - arc_dict = { + edge_key = (0,1,0) + edge_dict = { 'geometry':line, 'reversed':True, 'oneway':True, @@ -2537,28 +2508,28 @@ class TestGisModify: 'length': length(line) } network.add_edge( - *arc_key, - **arc_dict + *edge_key, + **edge_dict ) network.add_node(0,x=0,y=0) network.add_node(1,x=0,y=1) - # arc_key = (0,1,1) + # edge_key = (0,1,1) # network.add_edge( - # *arc_key, + # *edge_key, # geometry=line.reverse(), #gis_mod.reverse_linestring(line), # reversed=False, # oneway=False, # osmid=2, # length=length(line) # ) - # arc_key = (0,1,0) + # edge_key = (0,1,0) - # recreate the arc using an intermediate point + # recreate the edge using an intermediate point - connection_node_keys_per_arc, recreated_arcs = gis_mod.recreate_arcs( + connection_node_keys_per_edge, recreated_edges = gis_mod.recreate_edges( network=network, points={ - arc_key: [ + edge_key: [ Point(0,0.2), Point(0,0), Point(0,0.5), @@ -2568,70 +2539,70 @@ class TestGisModify: } ) - # verify if the arc was recreated - assert len(recreated_arcs) == 1 - assert arc_key in recreated_arcs + # verify if the edge was recreated + assert len(recreated_edges) == 1 + assert edge_key in recreated_edges # verify that the connection nodes exist - assert len(connection_node_keys_per_arc[arc_key]) == 5 - assert network.has_node(connection_node_keys_per_arc[arc_key][0]) - assert network.has_node(connection_node_keys_per_arc[arc_key][1]) - assert network.has_node(connection_node_keys_per_arc[arc_key][2]) - assert network.has_node(connection_node_keys_per_arc[arc_key][3]) - assert network.has_node(connection_node_keys_per_arc[arc_key][4]) - # verify that the replacing arcs exist + assert len(connection_node_keys_per_edge[edge_key]) == 5 + assert network.has_node(connection_node_keys_per_edge[edge_key][0]) + assert network.has_node(connection_node_keys_per_edge[edge_key][1]) + assert network.has_node(connection_node_keys_per_edge[edge_key][2]) + assert network.has_node(connection_node_keys_per_edge[edge_key][3]) + assert network.has_node(connection_node_keys_per_edge[edge_key][4]) + # verify that the replacing edges exist # the second connection node is the start node - assert arc_key[0] == connection_node_keys_per_arc[arc_key][1] + assert edge_key[0] == connection_node_keys_per_edge[edge_key][1] # the last connection node is the start node too - assert arc_key[0] == connection_node_keys_per_arc[arc_key][4] + assert edge_key[0] == connection_node_keys_per_edge[edge_key][4] # the start node connects to the first point assert network.has_edge( - u=arc_key[0], - v=connection_node_keys_per_arc[arc_key][0] + u=edge_key[0], + v=connection_node_keys_per_edge[edge_key][0] ) # the first point connects to the third assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][0], - v=connection_node_keys_per_arc[arc_key][2] + u=connection_node_keys_per_edge[edge_key][0], + v=connection_node_keys_per_edge[edge_key][2] ) # the third point connects to the fourth node assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][2], - v=connection_node_keys_per_arc[arc_key][3] + u=connection_node_keys_per_edge[edge_key][2], + v=connection_node_keys_per_edge[edge_key][3] ) # the fourth point connects to the end node assert network.has_edge( - u=connection_node_keys_per_arc[arc_key][3], - v=arc_key[1] + u=connection_node_keys_per_edge[edge_key][3], + v=edge_key[1] ) # make sure the geometries make sense assert network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) ]['geometry'] == LineString([(0,0),(0,0.2)]) assert network.edges[ - (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][2], + (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][2], 0) ]['geometry'] == LineString([(0,0.2),(0,0.5)]) assert network.edges[ - (connection_node_keys_per_arc[arc_key][2], - connection_node_keys_per_arc[arc_key][3], + (connection_node_keys_per_edge[edge_key][2], + connection_node_keys_per_edge[edge_key][3], 0) ]['geometry'] == LineString([(0,0.5),(0,0.8)]) assert network.edges[ - (connection_node_keys_per_arc[arc_key][3], arc_key[1], 0) + (connection_node_keys_per_edge[edge_key][3], edge_key[1], 0) ]['geometry'] == LineString([(0,0.8),(0,1)]) # make sure the lengths add up assert isclose( network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) ]['length'], gis_calc.great_circle_distance_along_path(LineString([(0,0),(0,0.2)])), abs_tol=2e-3 ) assert isclose( network.edges[ - (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][2], + (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][2], 0)]['length'], gis_calc.great_circle_distance_along_path( LineString([(0,0.2),(0,0.5)]) @@ -2640,8 +2611,8 @@ class TestGisModify: ) assert isclose( network.edges[ - (connection_node_keys_per_arc[arc_key][2], - connection_node_keys_per_arc[arc_key][3], + (connection_node_keys_per_edge[edge_key][2], + connection_node_keys_per_edge[edge_key][3], 0)]['length'], gis_calc.great_circle_distance_along_path( LineString([(0,0.5),(0,0.8)]) @@ -2650,48 +2621,48 @@ class TestGisModify: ) assert isclose( network.edges[ - (connection_node_keys_per_arc[arc_key][3], arc_key[1], 0)]['length'], + (connection_node_keys_per_edge[edge_key][3], edge_key[1], 0)]['length'], gis_calc.great_circle_distance_along_path( LineString([(0,0.8),(0,1)]) ), abs_tol=2e-3 ) # verify the geometries - arc_01 = (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) - arc_02 = (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][2], + edge_01 = (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) + edge_02 = (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][2], 0) - arc_03 = (connection_node_keys_per_arc[arc_key][2], - connection_node_keys_per_arc[arc_key][3], + edge_03 = (connection_node_keys_per_edge[edge_key][2], + connection_node_keys_per_edge[edge_key][3], 0) - arc_04 = (connection_node_keys_per_arc[arc_key][3], arc_key[1], 0) + edge_04 = (connection_node_keys_per_edge[edge_key][3], edge_key[1], 0) assert tuple( - network.edges[arc_01]['geometry'].coords) == ((0,0),(0,0.2)) + network.edges[edge_01]['geometry'].coords) == ((0,0),(0,0.2)) assert tuple( - network.edges[arc_02]['geometry'].coords) == ((0,0.2),(0,0.5)) + network.edges[edge_02]['geometry'].coords) == ((0,0.2),(0,0.5)) assert tuple( - network.edges[arc_03]['geometry'].coords) == ((0,0.5),(0,0.8)) + network.edges[edge_03]['geometry'].coords) == ((0,0.5),(0,0.8)) assert tuple( - network.edges[arc_04]['geometry'].coords) == ((0,0.8),(0,1)) + network.edges[edge_04]['geometry'].coords) == ((0,0.8),(0,1)) # make sure the edges have the original 'reversed' value for key in osm.KEYS_OSMNX_EDGES_ESSENTIAL: if key == osm.KEY_OSMNX_GEOMETRY or key == osm.KEY_OSMNX_LENGTH: continue assert network.edges[ - (arc_key[0], connection_node_keys_per_arc[arc_key][0], 0) - ][key] == arc_dict[key] + (edge_key[0], connection_node_keys_per_edge[edge_key][0], 0) + ][key] == edge_dict[key] assert network.edges[ - (connection_node_keys_per_arc[arc_key][0], - connection_node_keys_per_arc[arc_key][2], - 0)][key] == arc_dict[key] + (connection_node_keys_per_edge[edge_key][0], + connection_node_keys_per_edge[edge_key][2], + 0)][key] == edge_dict[key] assert network.edges[ - (connection_node_keys_per_arc[arc_key][2], - connection_node_keys_per_arc[arc_key][3], - 0)][key] == arc_dict[key] + (connection_node_keys_per_edge[edge_key][2], + connection_node_keys_per_edge[edge_key][3], + 0)][key] == edge_dict[key] assert network.edges[ - (connection_node_keys_per_arc[arc_key][3], arc_key[1], 0) - ][key] == arc_dict[key] + (connection_node_keys_per_edge[edge_key][3], edge_key[1], 0) + ][key] == edge_dict[key] # ************************************************************************* # ************************************************************************* @@ -3199,6 +3170,25 @@ class TestGisModify: # ********************************************************************* # ********************************************************************* - + + def test_merge_points_into_linestring_wo_fixed_extremities(self): + + # simple line + line_coords = tuple([(0.0,0.0),(0.0,1.0)]) + line = LineString(line_coords) + + error_raised = False + try: + (_line, + close_to_start, + close_to_end) = gis_mod.merge_points_into_linestring( + line, + points=[Point(0,0.5)], + fixed_extremities=False + ) + except NotImplementedError: + error_raised = True + assert error_raised + # ***************************************************************************** # ***************************************************************************** \ No newline at end of file diff --git a/tests/test_gis_utils.py b/tests/test_gis_utils.py index 05697d809d3800b87bba1fb5bfffba47fc6ef6fa..f67148a40087558ece70fb8512f3b33614fbced9 100644 --- a/tests/test_gis_utils.py +++ b/tests/test_gis_utils.py @@ -21,8 +21,8 @@ import src.topupopt.data.gis.osm as _osm ox.settings.use_cache = True -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** class TestGisUtils: @@ -66,47 +66,47 @@ class TestGisUtils: self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=False) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=False) - # no driveway, all nodes, multiple addresses per arc + # no driveway, all nodes, multiple addresses per edge self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, use_multiple_addresses=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, use_multiple_addresses=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=False, use_multiple_addresses=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=False, use_multiple_addresses=True) @@ -114,7 +114,7 @@ class TestGisUtils: self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=False, revert_to_original_crs=True) @@ -122,112 +122,112 @@ class TestGisUtils: self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=True) self.example_identify_entrances_simple_no_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=True) # driveway, all nodes self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False) + create_reversed_edges=False) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False) + create_reversed_edges=False) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True) + create_reversed_edges=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True) + create_reversed_edges=True) - # driveway, all nodes, multiple addresses per arc + # driveway, all nodes, multiple addresses per edge self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, use_multiple_addresses=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, use_multiple_addresses=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True, + create_reversed_edges=True, use_multiple_addresses=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, use_multiple_addresses=True) # driveway, limited selection self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=True) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=True, + create_reversed_edges=True, focus_on_node_P_only=True) # driveway variation self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, BD_with_name=False, BD_right_address=False) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, BD_with_name=True, BD_right_address=False) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=False, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, BD_with_name=True, BD_right_address=False) self.example_identify_entrances_simple_driveway( AB_right_BC_wrong=True, - create_reversed_arcs=False, + create_reversed_edges=False, focus_on_node_P_only=False, BD_with_name=True, BD_right_address=True) @@ -235,34 +235,34 @@ class TestGisUtils: # special cases self.example_identify_entrances_simple_special( - create_reversed_arcs=False, + create_reversed_edges=False, revert_to_original_crs=False, focus_on_node_P_only=False) self.example_identify_entrances_simple_special( - create_reversed_arcs=True, + create_reversed_edges=True, revert_to_original_crs=False, focus_on_node_P_only=False) self.example_identify_entrances_simple_special( - create_reversed_arcs=False, + create_reversed_edges=False, revert_to_original_crs=False, focus_on_node_P_only=True) self.example_identify_entrances_simple_special( - create_reversed_arcs=True, + create_reversed_edges=True, revert_to_original_crs=False, focus_on_node_P_only=True) # no matching street name in the entire network self.example_identify_entrances_simple_special( - create_reversed_arcs=False, + create_reversed_edges=False, revert_to_original_crs=False, focus_on_node_P_only=False, CE_wrong=True) - # TODO: test a case with multiple parallel arcs + # TODO: test a case with multiple parallel edges # ************************************************************************* # ************************************************************************* @@ -365,8 +365,8 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def get_node_gdf_B(self, right_address: str = 'right', @@ -413,8 +413,8 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def get_node_gdf_C(self, right_address: str = 'right', @@ -459,8 +459,8 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def get_network_A(self, gdf: GeoDataFrame, @@ -488,13 +488,13 @@ class TestGisUtils: #************************************************************************** - # two arcs: AB and BC + # two edges: AB and BC node_key_A = 'A' node_key_B = 'B' node_key_C = 'C' - # arc AB + # edge AB geo_AB = LineString( [(network.nodes[node_key_A][_osm.KEY_OSMNX_X], @@ -519,7 +519,7 @@ class TestGisUtils: ) }) - # arc BC + # edge BC geo_BC = LineString( [(network.nodes[node_key_B][_osm.KEY_OSMNX_X], @@ -550,8 +550,8 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def get_network_B(self, gdf: GeoDataFrame, @@ -575,12 +575,12 @@ class TestGisUtils: AB_right_BC_wrong=AB_right_BC_wrong, use_multiple_addresses=use_multiple_addresses) - # add nameless BD arc + # add nameless BD edge node_key_B = 'B' node_key_D = 'D' - # arc AB + # edge AB geo_BD = LineString( [(network.nodes[node_key_B][_osm.KEY_OSMNX_X], @@ -617,8 +617,8 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def get_network_C(self, gdf: GeoDataFrame, @@ -639,12 +639,12 @@ class TestGisUtils: country_code=country_code, AB_right_BC_wrong=True) - # add a CE arc with the right name + # add a CE edge with the right name node_key_C = 'C' node_key_E = 'E' - # arc AB + # edge AB geo_CE = LineString( [(network.nodes[node_key_C][_osm.KEY_OSMNX_X], @@ -672,12 +672,12 @@ class TestGisUtils: #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def example_identify_entrances_simple_special( self, - create_reversed_arcs: bool = False, + create_reversed_edges: bool = False, revert_to_original_crs: bool = False, focus_on_node_P_only: bool = False, CE_wrong: bool = False): @@ -693,29 +693,29 @@ class TestGisUtils: country_code=country_code, CE_wrong=CE_wrong) - # create reverse arcs + # create reverse edges - if create_reversed_arcs: + if create_reversed_edges: - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) + previous_edge_keys = list( + edge_key for edge_key in network.edges(keys=True) ) - for arc_key in previous_arc_keys: + for edge_key in previous_edge_keys: - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) + edge_dict = network.get_edge_data(u=edge_key[0], + v=edge_key[1], + key=edge_key[2]) - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) + network.add_edge(u_for_edge=edge_key[1], + v_for_edge=edge_key[0], + **edge_dict) - # find out which is the closest arc + # find out which is the closest edge if focus_on_node_P_only: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -727,7 +727,7 @@ class TestGisUtils: else: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -739,27 +739,27 @@ class TestGisUtils: if CE_wrong: - # no arcs with the right address, the closest arc should be selected + # no edges with the right address, the closest edge should be selected - assert (('B','D', 0) == nearest_arc_keys['P'] or - ('D','B', 0) == nearest_arc_keys['P']) + assert (('B','D', 0) == nearest_edge_keys['P'] or + ('D','B', 0) == nearest_edge_keys['P']) else: # CE has the right address, it should be selected - assert (('C','E', 0) == nearest_arc_keys['P'] or - ('E','C', 0) == nearest_arc_keys['P']) + assert (('C','E', 0) == nearest_edge_keys['P'] or + ('E','C', 0) == nearest_edge_keys['P']) #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def example_identify_entrances_simple_driveway( self, AB_right_BC_wrong: bool = True, - create_reversed_arcs: bool = False, + create_reversed_edges: bool = False, revert_to_original_crs: bool = False, focus_on_node_P_only: bool = False, BD_with_name: bool = False, @@ -781,29 +781,29 @@ class TestGisUtils: AB_right_BC_wrong=AB_right_BC_wrong, use_multiple_addresses=use_multiple_addresses) - # create reverse arcs + # create reverse edges - if create_reversed_arcs: + if create_reversed_edges: - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) + previous_edge_keys = list( + edge_key for edge_key in network.edges(keys=True) ) - for arc_key in previous_arc_keys: + for edge_key in previous_edge_keys: - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) + edge_dict = network.get_edge_data(u=edge_key[0], + v=edge_key[1], + key=edge_key[2]) - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) + network.add_edge(u_for_edge=edge_key[1], + v_for_edge=edge_key[0], + **edge_dict) - # find out which is the closest arc + # find out which is the closest edge if focus_on_node_P_only: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -815,7 +815,7 @@ class TestGisUtils: else: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -829,33 +829,33 @@ class TestGisUtils: if AB_right_BC_wrong: - assert (('A','B', 0) == nearest_arc_keys['P'] or - ('B','A', 0) == nearest_arc_keys['P']) + assert (('A','B', 0) == nearest_edge_keys['P'] or + ('B','A', 0) == nearest_edge_keys['P']) else: - assert (('B','C', 0) == nearest_arc_keys['P'] or - ('C','B', 0) == nearest_arc_keys['P']) + assert (('B','C', 0) == nearest_edge_keys['P'] or + ('C','B', 0) == nearest_edge_keys['P']) # elif BD_with_name and BD_right_address: - # assert (('B','D', 0) == nearest_arc_keys['P'] or - # ('D','B', 0) == nearest_arc_keys['P']) + # assert (('B','D', 0) == nearest_edge_keys['P'] or + # ('D','B', 0) == nearest_edge_keys['P']) else: - assert (('B','D', 0) == nearest_arc_keys['P'] or - ('D','B', 0) == nearest_arc_keys['P']) + assert (('B','D', 0) == nearest_edge_keys['P'] or + ('D','B', 0) == nearest_edge_keys['P']) #************************************************************************** - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** def example_identify_entrances_simple_no_driveway( self, AB_right_BC_wrong: bool = True, - create_reversed_arcs: bool = False, + create_reversed_edges: bool = False, focus_on_node_P_only: bool = False, revert_to_original_crs: bool = False, use_multiple_addresses: bool = False): @@ -872,29 +872,29 @@ class TestGisUtils: AB_right_BC_wrong=AB_right_BC_wrong, use_multiple_addresses=use_multiple_addresses) - # create reverse arcs + # create reverse edges - if create_reversed_arcs: + if create_reversed_edges: - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) + previous_edge_keys = list( + edge_key for edge_key in network.edges(keys=True) ) - for arc_key in previous_arc_keys: + for edge_key in previous_edge_keys: - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) + edge_dict = network.get_edge_data(u=edge_key[0], + v=edge_key[1], + key=edge_key[2]) - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) + network.add_edge(u_for_edge=edge_key[1], + v_for_edge=edge_key[0], + **edge_dict) - # find out which is the closest arc + # find out which is the closest edge if focus_on_node_P_only: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -906,7 +906,7 @@ class TestGisUtils: else: - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( + nearest_edge_keys, _, _ = gis_utils.identify_building_entrance_edges( gdf=gdf, gdf_street_column=_osm.KEY_OSM_STREET, network=network, @@ -918,15 +918,15 @@ class TestGisUtils: if AB_right_BC_wrong: - # the closest arc should be AB + # the closest edge should be AB - assert ('A','B', 0) == nearest_arc_keys['P'] + assert ('A','B', 0) == nearest_edge_keys['P'] else: - # the closest arc should be BC + # the closest edge should be BC - assert ('B','C', 0) == nearest_arc_keys['P'] + assert ('B','C', 0) == nearest_edge_keys['P'] # ************************************************************************* # ************************************************************************* @@ -943,17 +943,17 @@ class TestGisUtils: gdf=gdf, country_code=country_code) - # find out which is the closest arc + # find out which is the closest edge - nearest_arc_keys, network = gis_iden.identify_arc_closest_to_node( + nearest_edge_keys, network = gis_iden.identify_edge_closest_to_node( network, node_keys=['P']) - # the closest arc should be AB + # the closest edge should be AB - assert ('A','B', 0) in nearest_arc_keys + assert ('A','B', 0) in nearest_edge_keys - assert len(nearest_arc_keys) == 1 + assert len(nearest_edge_keys) == 1 # ************************************************************************* # ************************************************************************* @@ -1059,8 +1059,8 @@ class TestGisUtils: error_triggered = True assert error_triggered - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** # test the counting of occurrences in a geodataframe @@ -1113,8 +1113,8 @@ class TestGisUtils: gis_utils.count_ocurrences(gdf, 'column_E', [None]) == {None: 2} ) - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** # test creating osmnx-like geodataframes for nodes @@ -1893,8 +1893,9 @@ class TestGisUtils: # ************************************************************************* def test_discrete_plot_gdf(self): - - #************************************************************************** + + # ********************************************************************* + # ********************************************************************* G = ox.graph_from_point( (55.71654,9.11728), @@ -1902,12 +1903,14 @@ class TestGisUtils: custom_filter='["highway"~"residential|tertiary|unclassified|service"]', truncate_by_edge=True ) - - #************************************************************************** + + # ********************************************************************* + # ********************************************************************* gdf = ox.utils_graph.graph_to_gdfs(G, edges=False) # nodes only - - #************************************************************************** + + # ********************************************************************* + # ********************************************************************* # add add random discrete element to gdf @@ -1933,7 +1936,8 @@ class TestGisUtils: gdf[column] = Series(data=category, index=gdf.index) - #************************************************************************** + # ********************************************************************* + # ********************************************************************* gis_utils.plot_discrete_attributes( gdf, @@ -1943,66 +1947,116 @@ class TestGisUtils: # ********************************************************************* # ********************************************************************* - - # ************************************************************************* + # ************************************************************************* + # ************************************************************************* - def test_convert_edge_paths(self): - - # define how to validate the method - - def convert_edge_path_validation(edge_path, node_path, true_node_path): - - # check for empty paths - - if len(true_node_path) == 0: - - assert len(edge_path) == 0 + def test_convert_edge_path(self): + + # create network + + network = nx.MultiDiGraph() + + # define and add edges + + list_edges = [ + (0, 1), (1, 1), (1, 2), (2, 3), + (3, 4), (4, 5), (5, 6), + (7, 6), (8, 7), (9, 8), + (6, 7), (7, 8), (8, 9) + ] + + network.add_edges_from(list_edges) + + # ********************************************************************* + # ********************************************************************* + + allow_reversed_edges = False + + edge_paths = [ + [(0, 1)], + [(0, 1), (1, 2)], + [(1, 2), (2, 3)], + [(0, 1), (1, 2), (2, 3)], + [(0, 1), (1, 1), (1, 2)], # self loop + [(6, 7), (7, 8), (8, 9)], # should work + ] + + expected_node_paths = [ + [0, 1], + [0, 1, 2], + [1, 2, 3], + [0, 1, 2, 3], + [0, 1, 2], + [6, 7, 8, 9] + ] + + for edge_index, edge_path in enumerate(edge_paths): + + assert gis_utils.convert_edge_path( + network, + edge_path, + allow_reversed_edges=allow_reversed_edges) == expected_node_paths[ + edge_index] - assert len(node_path) == 0 - - return None - - # assert that all nodes are in the node path - - for node_key in node_path: - - assert node_key in true_node_path - - # assert that there is the same number of nodes - - assert len(node_path) == len(true_node_path) - - # assert that they form the right sequence - - for edge_index, edge_key in enumerate(edge_path): - - assert node_path[edge_index] == edge_key[0] - - assert node_path[-1] == edge_key[1] - - # example 1 - - edge_path = [(1,3),(3,7),(7,4)] - - node_path = gis_utils.convert_edge_path(edge_path) - - true_node_path = [1,3,7,4] - - convert_edge_path_validation(edge_path, node_path, true_node_path) - - # example 2 - - edge_path = [] - - node_path = gis_utils.convert_edge_path(edge_path) - - true_node_path = [] - - convert_edge_path_validation(edge_path, node_path, true_node_path) - + # invalid edge paths + + invalid_edge_paths = [ + [(7, 6), (8, 7), (9, 8)], # all reversed, should fail + [(7, 6), (7, 8), (8, 9)], # first reversed, should fail + [(6, 7), (8, 7), (8, 9)], # second reversed, should fail + [(6, 7), (7, 8), (9, 8)] # third reversed, should fail + ] + for edge_path in invalid_edge_paths: + error_raised = False + try: + gis_utils.convert_edge_path( + network, + edge_path, + allow_reversed_edges=allow_reversed_edges + ) + except ValueError: + error_raised = True + assert error_raised + # ********************************************************************* # ********************************************************************* + + allow_reversed_edges = True + + edge_paths = [ + [(0, 1)], + [(0, 1), (1, 2)], + [(1, 2), (2, 3)], + [(0, 1), (1, 2), (2, 3)], + [(0, 1), (1, 1), (1, 2)], # self loop + [(6, 7), (7, 8), (8, 9)], # should work + [(7, 6), (8, 7), (9, 8)], # all reversed, should fail + [(7, 6), (7, 8), (8, 9)], # first reversed, should fail + [(6, 7), (8, 7), (8, 9)], # second reversed, should fail + [(6, 7), (7, 8), (9, 8)] # third reversed, should fail + ] + + expected_node_paths = [ + [0, 1], + [0, 1, 2], + [1, 2, 3], + [0, 1, 2, 3], + [0, 1, 2], + [6, 7, 8, 9], + [6, 7, 8, 9], + [6, 7, 8, 9], + [6, 7, 8, 9], + [6, 7, 8, 9] + ] + + for edge_index, edge_path in enumerate(edge_paths): + + assert gis_utils.convert_edge_path( + network, + edge_path, + allow_reversed_edges=allow_reversed_edges) == expected_node_paths[ + edge_index] # ************************************************************************* # ************************************************************************* @@ -2019,45 +2073,30 @@ class TestGisUtils: ) # convert to undirected - undirected_network = ox.get_undirected(network) # convert to directed - directed_network = gis_utils.get_directed(undirected_network) # make sure the same nodes exist on both objects - for node_key in network.nodes(): - assert node_key in directed_network.nodes() - assert network.number_of_nodes() == directed_network.number_of_nodes() # assert that all edges on the directed network exist on the undirected one - assert network.number_of_edges() >= directed_network.number_of_edges() - + # for each edge in the directed graph object for edge_key in directed_network.edges(keys=True): - # make sure at least one suitable edge exists - assert network.has_edge(*edge_key) - # cycle through suitable edges on the original network until the one is # found that has all the matching attributes and content - edge_dict = directed_network.edges[edge_key] - - for other_edge_key in gis_iden.all_arcs_from_a_to_b( + for other_edge_key in gis_iden.get_edges_from_a_to_b( network, edge_key[0], edge_key[1]): - # check all attributes - number_matching_attributes = 0 - for edge_attr, edge_data in edge_dict.items(): - try: assert edge_data == network.edges[other_edge_key][edge_attr] except AssertionError: @@ -2066,25 +2105,15 @@ class TestGisUtils: except KeyError: # the attribute does not exist continue - # print(edge_key) - # print(other_edge_key) - # print(edge_dict) - # print(network.edges[other_edge_key]) - - # assert False - number_matching_attributes += 1 if number_matching_attributes == len(edge_dict): - # a compatible edge was found, break - break - assert number_matching_attributes == len(edge_dict) # ************************************************************************* # ************************************************************************* -# ************************************************************************* -# ************************************************************************* \ No newline at end of file +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file