diff --git a/src/topupopt/data/gis/calculate.py b/src/topupopt/data/gis/calculate.py index 8846eb9be5b7d824992f9b90dcf7d722b2d674dd..ad236f7155e17d3b73fda4e3b8dc87079026c815 100644 --- a/src/topupopt/data/gis/calculate.py +++ b/src/topupopt/data/gis/calculate.py @@ -19,101 +19,80 @@ 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. """ - + # determine if the graph is projected or not graph_is_projected = is_projected(network.graph['crs']) - - # for each arc on the graph - - if type(arc_keys) == type(None): - - arc_keys = network.edges(keys=True) # tuple(network.edges(keys=True)) - + # check if edge keys were specified + if type(edge_keys) == type(None): + # no particular edge keys were provided: consider all edges (default) + edge_keys = network.edges(keys=True) # tuple(network.edges(keys=True)) + # initialise length dict length_dict = {} - - for arc_key in arc_keys: - + # for each edge on the graph + for edge_key in edge_keys: # calculate it using the library - if graph_is_projected: - # 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: - # 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 the dict with lengths of each edge return length_dict -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def great_circle_distance_along_path(path: LineString) -> float: """ @@ -133,15 +112,10 @@ def great_circle_distance_along_path(path: LineString) -> float: The sum of the individual great circle distances along the path. """ - # get coordinates - lon = tuple(path.coords.xy[0]) - lat = tuple(path.coords.xy[1]) - # sum individual distances and return - return sum( great_circle( lat[:-1], # latitudes of starting points @@ -151,8 +125,8 @@ def great_circle_distance_along_path(path: LineString) -> float: ) ) -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def update_street_count(network: MultiDiGraph): """ @@ -168,18 +142,15 @@ def update_street_count(network: MultiDiGraph): None. """ - # 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 +158,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 +166,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 +178,79 @@ 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.get_edges_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 +260,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 ------- @@ -333,27 +270,19 @@ def edge_path_length(network: MultiDiGraph, """ # check the number of - path_length = len(path) - if path_length == 0: - return inf - 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: - # no path provided - return inf -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def count_ocurrences(gdf: GeoDataFrame, column: str, @@ -382,62 +311,34 @@ def count_ocurrences(gdf: GeoDataFrame, """ if type(column_entries) == list: - # find entries also present in the dict - # initialise dict - count_dict = {} - # for each key in the dict - for key in column_entries: - # # store the number of rows - # count_dict[key] = gdf[gdf[column]==key].shape[0] - # count the number of rows with this key - if isna(key): - count_dict[key] = gdf[gdf[column].isnull()].shape[0] - else: - count_dict[key] = gdf[gdf[column]==key].shape[0] - else: - # find all unique entries - # initialise dict - count_dict = {} - for entry in gdf[column]: - # check if it is already in the dict - if entry in count_dict: - # it is, skip - continue - # it is not, count and store the number of rows with said entry - if isna(entry): #type(entry) == type(None): - count_dict[entry] = gdf[gdf[column].isnull()].shape[0] - else: - count_dict[entry] = gdf[gdf[column]==entry].shape[0] - # return statement - return count_dict -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file diff --git a/src/topupopt/data/gis/identify.py b/src/topupopt/data/gis/identify.py index 940a0081d708a64f590c9d0627df32e2f887d2e4..33ccd46ce1eca3398895d557224b2339108489eb 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. @@ -216,7 +235,6 @@ def edges_are_in_reverse( type(rv_dict[attr_key]) != list)): # the sets of list arguments do not match # or, the arguments are not equivalent - # print('ping2:'+str(attr_key)) return False elif (type(attr_value) == list and type(rv_dict[attr_key]) == list and @@ -232,7 +250,6 @@ def edges_are_in_reverse( # either the geometries are not reversed # or, there is no geometry attribute in the reverse dict # 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 type(rv_dict[attr_key]) == LineString and @@ -247,7 +264,6 @@ def edges_are_in_reverse( type(rv_dict[attr_key]) != bool)): # either the reversed flags match # or, there is no reversed flag in the reverse dict - # print('ping4:'+str(attr_key)) return False elif (attr_key == osm.KEY_OSMNX_REVERSED and attr_key in rv_dict and @@ -262,7 +278,6 @@ def edges_are_in_reverse( # either the lengths differ too much # or, there is no length attribute in the reverse dict # or it is not a numeric type - # print('ping5:'+str(attr_key)) return False elif (attr_key == osm.KEY_OSMNX_LENGTH and attr_key in rv_dict and @@ -272,15 +287,14 @@ def edges_are_in_reverse( continue elif attr_key in rv_dict and attr_value != rv_dict[attr_key]: # either the attributes do not match - # print('ping6:'+str(attr_key)) return False # else: # the argument does not exist # all other possibilities have been exhausted: return True return True -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def close_to_extremities( line: LineString, @@ -322,82 +336,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: - + # 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 +433,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): - 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 - + # 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 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 - # 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, @@ -735,10 +667,8 @@ def is_roundabout(network: nx.MultiDiGraph, return True -#****************************************************************************** -#****************************************************************************** - -# TODO: change name to get_multi_edge_tuples +# ***************************************************************************** +# ***************************************************************************** def get_edges_from_a_to_b(network: nx.MultiDiGraph, node_start, @@ -767,8 +697,8 @@ def get_edges_from_a_to_b(network: nx.MultiDiGraph, else: return [] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def get_edges_between_two_nodes(network: nx.MultiDiGraph, u, v) -> list: """ @@ -811,8 +741,8 @@ def get_edges_between_two_nodes(network: nx.MultiDiGraph, u, v) -> list: # no edges found return [] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def get_edges_involving_node(network: nx.MultiDiGraph, node_key, @@ -860,8 +790,8 @@ def get_edges_involving_node(network: nx.MultiDiGraph, include_self_loops) ] -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def neighbours(network: nx.MultiDiGraph or nx.MultiGraph, node_key, @@ -1066,91 +996,6 @@ def is_edge_path(network: nx.MultiDiGraph, return True -#****************************************************************************** -#****************************************************************************** - -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, edges 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 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 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: - # no reversed edges - 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 [] - # ***************************************************************************** # ***************************************************************************** @@ -1182,11 +1027,9 @@ def is_path_straight(network: nx.MultiDiGraph, A boolean indicating whether the path is straight or not. """ - # confirm that it is a path if not is_node_path(network, path, consider_reversed_edges): return False - # a straight path requires at least two nodes path_length = len(path) if path_length == 2: @@ -1194,7 +1037,7 @@ def is_path_straight(network: nx.MultiDiGraph, # check if the intermediate nodes have the right number of neighbours for intermediate_node in path[1:-1]: - if len(tuple(neighbours( + if len(set(neighbours( network, intermediate_node, ignore_self_loops=ignore_self_loops)) @@ -1206,8 +1049,8 @@ def is_path_straight(network: nx.MultiDiGraph, # if all intermediate nodes have two neighbours, return True return True -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def find_simplifiable_paths(network: nx.MultiDiGraph, excluded_nodes: list, @@ -1253,7 +1096,7 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, # locate all the non-excluded nodes that can form straight paths - intermediate_candidate_nodes = [ + intermediate_candidate_nodes = set([ node_key for node_key in network.nodes() # the node cannot be among those excluded @@ -1272,7 +1115,7 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, if (ignore_self_loops or (not ignore_self_loops and not network.has_edge(node_key, node_key))) - ] + ]) # ************************************************************************* @@ -1280,12 +1123,10 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, list_paths = [] list_paths_nodes = [] - - list_nodes_joined = [] + list_nodes_joined = set([]) # 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 @@ -1295,21 +1136,21 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, # reversed edges are accepted new_sequence = _find_path_direction_insensitive( network, - list_valid_nodes=intermediate_candidate_nodes, - start_node=candidate_node + list_valid_nodes=intermediate_candidate_nodes-list_nodes_joined, + start_node=candidate_node, + ignore_self_loops=ignore_self_loops ) else: # reversed edges are not accepted new_sequence = _find_path_direction_sensitive( network, - list_valid_nodes=intermediate_candidate_nodes, - start_node=candidate_node + list_valid_nodes=intermediate_candidate_nodes-list_nodes_joined, + start_node=candidate_node, + ignore_self_loops=ignore_self_loops ) - # 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): + new_sequence in list_paths): # path is just one edge or has already been included continue @@ -1319,12 +1160,8 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, 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? - ) + # update the list of intermediate nodes already on paths + list_nodes_joined.update(set(new_sequence[1:-1])) # ************************************************************************* # ************************************************************************* @@ -1340,7 +1177,8 @@ def find_simplifiable_paths(network: nx.MultiDiGraph, def _find_path_direction_sensitive( network: nx.MultiDiGraph, list_valid_nodes: list, - start_node + start_node, + ignore_self_loops: bool ) -> list: def find_path_forward(network: nx.MultiDiGraph, @@ -1360,7 +1198,7 @@ def _find_path_direction_sensitive( # 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 arc) = ignore + # 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: @@ -1378,12 +1216,19 @@ def _find_path_direction_sensitive( 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 + elif a_neighbour == path[0] and len(path) >= 3: + # the path length is greater than or equal to 3, + # neighbour is already on the path, matches the start, + # and has two neighbours other than itself: + # close the loop and return the path + if (len(set(neighbours( + network, + a_neighbour, + ignore_self_loops=ignore_self_loops))) == 2): + # 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 @@ -1403,7 +1248,6 @@ def _find_path_direction_sensitive( # >> 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 @@ -1414,7 +1258,7 @@ def _find_path_direction_sensitive( # 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 arc) = ignore + # 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: @@ -1433,9 +1277,18 @@ def _find_path_direction_sensitive( # 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 + # the path length is greater than or equal to 3, + # neighbour is already on the path, matches the end, + # and has two neighbours other than itself: + # close the loop and return the path + # if (len(set(neighbours( + # network, + # a_neighbour, + # ignore_self_loops=ignore_self_loops))) == 2): + # # add the neighbour to the start of the path + # path.insert(0, a_neighbour) + # # return the path + # return path path.insert(0, a_neighbour) # return the path return path @@ -1470,7 +1323,8 @@ def _find_path_direction_sensitive( def _find_path_direction_insensitive( network: nx.MultiDiGraph, list_valid_nodes: list, - start_node + start_node, + ignore_self_loops: bool ) -> list: def find_path_forward(network: nx.MultiDiGraph, @@ -1489,7 +1343,7 @@ def _find_path_direction_insensitive( # 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 arc) = ignore + # 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: @@ -1508,12 +1362,18 @@ def _find_path_direction_insensitive( # 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 + # the path length is greater than or equal to 3, + # neighbour is already on the path, matches the start, + # and has two neighbours other than itself: + # close the loop and return the path + if (len(set(neighbours( + network, + a_neighbour, + ignore_self_loops=ignore_self_loops))) == 2): + # 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 @@ -1543,7 +1403,7 @@ def _find_path_direction_insensitive( # 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 arc) = ignore + # 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: @@ -1562,14 +1422,24 @@ def _find_path_direction_insensitive( # 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 + # the path length is greater than or equal to 3, + # neighbour is already on the path, matches the end, + # and has two neighbours other than itself: + # close the loop and return the path + # if (len(set(neighbours( + # network, + # a_neighbour, + # ignore_self_loops=ignore_self_loops))) == 2): + # # add the neighbour to the start of the path + # path.insert(0, a_neighbour) + # # return the path + # return path # 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 # TODO: reach statement + return path # ************************************************************************* @@ -1619,8 +1489,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: """ @@ -1646,8 +1516,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, @@ -1696,8 +1566,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, @@ -1753,8 +1623,8 @@ def is_start_or_end_point_or_close(line: LineString, return False -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def is_start_or_end_point(line: LineString, point: Point) -> bool: diff --git a/src/topupopt/data/gis/modify.py b/src/topupopt/data/gis/modify.py index 2ff69dbb18b38db0db9d9a6f9817ae17f7b37d0d..52bd2b452f7a180d99999cc722f1c21b13788cd0 100644 --- a/src/topupopt/data/gis/modify.py +++ b/src/topupopt/data/gis/modify.py @@ -1,7 +1,5 @@ # standard library imports -import math - # from typing import Tuple # local, external @@ -23,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): """ @@ -50,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, @@ -77,88 +75,57 @@ def transform_roundabouts_into_crossroads( """ # declare the output list - list_roundabout_centroids = [] # for each roundabout - for roundabout in roundabouts: - - #********************************************************************** - # make sure roundabout qualifies as a roundabout - if not gis_iden.is_roundabout(network, roundabout): - + # not a roundabout, skip list_roundabout_centroids.append(None) - continue - # if any node in the roundabout also appears in another one - roundabout_overlaps = False - # for each node forming this roundabout - for node_roundabout in roundabout: - # check every other roundabout - for other_roundabout in roundabouts: - if other_roundabout == roundabout: - continue - # if the node exists in another roundabout - if node_roundabout in other_roundabout: - + # roundabouts overlap roundabout_overlaps = True - break - if roundabout_overlaps: - + # break out of the loop break - if roundabout_overlaps: - + # the roundabout overlaps with some other one, skip it list_roundabout_centroids.append(None) - continue - - # do nothing - - #********************************************************************** - + + # ********************************************************************* + # ********************************************************************* # create a new node whose location is the roundabout's centroid - list_point_coordinates = [ (network.nodes[node_key][osm.KEY_OSMNX_X], network.nodes[node_key][osm.KEY_OSMNX_Y]) for node_key in roundabout ] - new_geo = LineString(list_point_coordinates) - roundabout_centroid_key = generate_pseudo_unique_key(network) - network.add_node( roundabout_centroid_key, **{osm.KEY_OSMNX_X: new_geo.centroid.coords.xy[0][0], osm.KEY_OSMNX_Y: new_geo.centroid.coords.xy[1][0]} ) - list_roundabout_centroids.append(roundabout_centroid_key) - - #********************************************************************** - + # ********************************************************************* + # ********************************************************************* # create new edges to link each node leading to the roundabout to the # node just created (new_node_key) - # find the edges leading to the roundabout - list_edges_leading_to_roundabout = [ edge_key # for each node in the roundabout @@ -167,30 +134,22 @@ 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 each edge between the two nodes for edge_key in gis_iden.get_edges_between_two_nodes( network, node_key, other_node_key) ] - # for each edge leading to the roundabout - for edge_key in list_edges_leading_to_roundabout: - # replace it with a new edge to the new node - # get edge dict - edge_dict = network.get_edge_data(edge_key[0], edge_key[1], edge_key[2]) - if osm.KEY_OSMNX_GEOMETRY in edge_dict: - # geometry exists old_geometry = edge_dict[osm.KEY_OSMNX_GEOMETRY] - else: # geometry does not exist # create it @@ -200,53 +159,37 @@ def transform_roundabouts_into_crossroads( (network.nodes[edge_key[1]][osm.KEY_OSMNX_X], network.nodes[edge_key[1]][osm.KEY_OSMNX_Y])] ) - - if osm.KEY_OSMNX_LENGTH in edge_dict: - # length exists - old_length = edge_dict[osm.KEY_OSMNX_LENGTH] - else: - # length does not exist - old_length = arc_lengths( - network, - arc_keys=[edge_key])[edge_key] - - # # calculate it - # old_length = 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] - # ) - - #****************************************************************** - #****************************************************************** + # if osm.KEY_OSMNX_LENGTH in edge_dict: + # # length exists + # old_length = edge_dict[osm.KEY_OSMNX_LENGTH] + # else: + # # length does not exist + # old_length = edge_lengths( + # network, + # edge_keys=[edge_key])[edge_key] + # the old length has to exist + old_length = edge_dict[osm.KEY_OSMNX_LENGTH] + + # ***************************************************************** + # ***************************************************************** # find closest point - if edge_key[0] in roundabout: - # this edge starts from the roundabout - new_edge_start_node = roundabout_centroid_key - new_edge_end_node = edge_key[1] - # create geometry object between old roundabout point to the # roundabout's centroid - extra_geometry = LineString( [(network.nodes[roundabout_centroid_key][osm.KEY_OSMNX_X], network.nodes[roundabout_centroid_key][osm.KEY_OSMNX_Y]), (network.nodes[edge_key[0]][osm.KEY_OSMNX_X], network.nodes[edge_key[0]][osm.KEY_OSMNX_Y])] ) - if is_projected(network.graph['crs']): - + # projected graph: use direct method extra_length = length(extra_geometry) - - else: - + else: # unprojected graph: use great circle method extra_length = great_circle( lat1=network.nodes[ roundabout_centroid_key][osm.KEY_OSMNX_Y], @@ -255,31 +198,23 @@ def transform_roundabouts_into_crossroads( lat2=network.nodes[edge_key[0]][osm.KEY_OSMNX_Y], lon2=network.nodes[edge_key[0]][osm.KEY_OSMNX_X] ) - elif edge_key[1] in roundabout: - # this edge ends in the roundabout - new_edge_start_node = edge_key[0] - new_edge_end_node = roundabout_centroid_key - # create geometry object between old roundabout point to the # roundabout's centroid - extra_geometry = LineString( [(network.nodes[roundabout_centroid_key][osm.KEY_OSMNX_X], network.nodes[roundabout_centroid_key][osm.KEY_OSMNX_Y]), (network.nodes[edge_key[1]][osm.KEY_OSMNX_X], network.nodes[edge_key[1]][osm.KEY_OSMNX_Y])] ) - if is_projected(network.graph['crs']): - + # projected graph, use direct method extra_length = length(extra_geometry) - else: - + # unprojected graph, use great circle method extra_length = great_circle( lat1=network.nodes[ roundabout_centroid_key][osm.KEY_OSMNX_Y], @@ -289,43 +224,35 @@ def transform_roundabouts_into_crossroads( lon2=network.nodes[edge_key[1]][osm.KEY_OSMNX_X] ) - #****************************************************************** - #****************************************************************** + # ***************************************************************** + # ***************************************************************** edge_dict[osm.KEY_OSMNX_GEOMETRY] = linemerge( [old_geometry, extra_geometry]) - edge_dict[osm.KEY_OSMNX_LENGTH] = old_length+extra_length - network.add_edge(new_edge_start_node, new_edge_end_node, **edge_dict) - #************************************************************************** - #************************************************************************** + # ************************************************************************* + # ************************************************************************* # remove the roundabout nodes for roundabout_index, roundabout in enumerate(roundabouts): - # if the transformation of the roundabout was successful... - if list_roundabout_centroids[roundabout_index] != None: - # remove the roundabout nodes - network.remove_nodes_from(roundabout) - # return - return list_roundabout_centroids - #************************************************************************** - #************************************************************************** + # ************************************************************************* + # ************************************************************************* -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** # TODO: develop algorithm to traverse the graph in search of dead ends @@ -356,19 +283,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 @@ -381,152 +302,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 @@ -541,9 +341,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 ------ @@ -559,9 +356,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.') # ************************************************************************* @@ -576,21 +378,16 @@ def replace_path( edge_length = 0 for node_pair_index in range(len(path)-1): - - if ignore_directions: - # edge directions can be ignored - edge_key = list(gis_iden.get_edges_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.get_edges_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 # check if the geometry is consistent with the edge declaration @@ -644,35 +441,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 @@ -713,112 +502,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.get_edges_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.get_edges_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 ---------- @@ -828,11 +523,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 @@ -843,40 +538,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.get_edges_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.get_edges_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, @@ -998,8 +693,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, @@ -1039,8 +737,8 @@ def merge_points_into_linestring( return line, close_to_start, close_to_end -#****************************************************************************** -#****************************************************************************** +# ***************************************************************************** +# ***************************************************************************** def split_linestring(line: LineString, points: list, @@ -1074,7 +772,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, @@ -1083,41 +780,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] @@ -1125,7 +807,6 @@ def split_linestring(line: LineString, ) # store new end/start point - previous_split_index = coords_index # else: @@ -1137,7 +818,6 @@ def split_linestring(line: LineString, # next iteration # add the last segment - line_segments.append( LineString( new_line.coords[previous_split_index:] @@ -1145,74 +825,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 @@ -1221,63 +900,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: @@ -1288,9 +967,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( @@ -1299,7 +978,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: @@ -1311,8 +990,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( @@ -1321,7 +1000,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 @@ -1336,7 +1015,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( @@ -1355,43 +1034,37 @@ 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 ) ) # update the outputs - if len(line_segments) > 0: - - recreated_arcs[arc_key] = segment_keys - # print('here segment keyse') - # print(segment_keys) - connection_node_keys_per_arc[arc_key] = _node_keys - - return connection_node_keys_per_arc, recreated_arcs + recreated_edges[edge_key] = segment_keys + connection_node_keys_per_edge[edge_key] = _node_keys + # return statement + 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 ---------- @@ -1399,180 +1072,164 @@ 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 - - points_per_arc = {} - - for arc_key, _node_keys in nodes_to_connect_to_arc.items(): + # 2) for each edge, and node that is to be connected to it, find its closest + # point on the edge + points_per_edge = {} + 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 - if store_unsimplified_geometries: - # update the edge - - network.add_edge(*arc_key, - **{osm.KEY_OSMNX_GEOMETRY: arc_geo}) - - # use nearest_points to locate the closest points on the arc - - points_per_arc[arc_key] = [ + network.add_edge(*edge_key, + **{osm.KEY_OSMNX_GEOMETRY: edge_geo}) + # use nearest_points to locate the closest points on the edge + 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 ] - # TIP: exclude the points that can be considered close to the start or end nodes - # TIP: use the shortest line method to obtain the line geometry #************************************************************************** #************************************************************************** - # 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( @@ -1582,24 +1239,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 # ***************************************************************************** # ***************************************************************************** @@ -1609,7 +1266,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 2e6e0826b0a0c2a33632def5deeac60545b3fc07..6e7b42f91a699e8349fcec5a670d8ded4f142298 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 @@ -49,7 +48,7 @@ def find_gpkg_packable_columns(gdf: GeoDataFrame) -> set: # packable columns: 1), 2) and 3) - #************************************************************************** + # ************************************************************************* # 1) columns with equivalent lowercase names @@ -64,7 +63,7 @@ def find_gpkg_packable_columns(gdf: GeoDataFrame) -> set: if lowercase_columns.count(lccolumn) >= 2 ) - #************************************************************************** + # ************************************************************************* # for each column @@ -109,12 +108,12 @@ def find_gpkg_packable_columns(gdf: GeoDataFrame) -> set: set_columns.add(column) - #************************************************************************** + # ************************************************************************* 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, @@ -573,12 +572,12 @@ def prepare_node_data_from_geodataframe( node_key_to_gdf_index_dict[node_key] = gdf.index[gdf_entry] - #************************************************************************** + # ************************************************************************* 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. @@ -813,27 +812,22 @@ def simplify_network(network: MultiDiGraph, protected_nodes, max_iterations=dead_end_probing_depth ) - # 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) gis_mod.remove_self_loops(network) - # 4) join segments (can create self-loops) - simplifiable_paths = gis_iden.find_all_straight_paths( + simplifiable_paths = gis_iden.find_simplifiable_paths( network, protected_nodes ) for path in simplifiable_paths: gis_mod.replace_path(network, path) - # 4) remove self loops (tends to create straight paths and dead ends) gis_mod.remove_self_loops(network) - # 5) transform roundabouts into crossroads (can create straight paths) list_roundabout_nodes = gis_iden.find_roundabouts( network, @@ -842,15 +836,14 @@ def simplify_network(network: MultiDiGraph, network, list_roundabout_nodes ) - - # update street count + # 6) update street count 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,61 +896,60 @@ 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 - #************************************************************************** + # ************************************************************************* if revert_to_original_crs: original_crs = network.graph['crs'] - #************************************************************************** + # ************************************************************************* - # 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_edge_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 +961,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 +971,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 +1002,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 +1020,18 @@ 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 - - other_arcs = gis_iden.get_edges_involving_node( + # get adjacent/neighbouring edges + 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( + 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 +1040,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 +1076,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] ) @@ -1110,53 +1100,53 @@ def identify_building_entrance_arcs( trouble_nodes.append(node_key) - #************************************************************************** + # ************************************************************************* - # 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,13 +1154,13 @@ 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 - #************************************************************************** + # ************************************************************************* # revert network crs back to the original, if necessary @@ -1178,41 +1168,92 @@ 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 diff --git a/src/topupopt/problems/esipp/model.py b/src/topupopt/problems/esipp/model.py index 89899041596f5cf46d07f45fe6f74285d347d113..e686001a0a68374f52726398e2ef5d87ec7bd023 100644 --- a/src/topupopt/problems/esipp/model.py +++ b/src/topupopt/problems/esipp/model.py @@ -2827,123 +2827,123 @@ def create_model(name: str, for l_circ in m.set_L[g] if l_circ not in m.set_L_exp[g] if l_circ != l - ) <= 1 + - M_gl*sum( - #********************************************************** - # outgoing arcs in interfaced groups, nominal direction - sum(sum(1 - for j in m.set_J_col[(g,l,l_diamond)] - #if j in m.set_J_int[(g,l,l_diamond)] - if (g,l,l_diamond,j) in m.set_GLLJ_col_t[t] - )*m.var_xi_arc_inv_t[t] - for t in m.set_T_int - ) if (g,l,l_diamond) in m.set_J_col else 0 - + - # outgoing arcs in interfaced groups, reverse direction - sum(sum(1 - for j in m.set_J_col[(g,l_diamond,l)] - #if j in m.set_J_int[(g,l_diamond,l)] - if j in m.set_J_und[(g,l_diamond,l)] - if (g,l_diamond,l,j) in m.set_GLLJ_col_t[t] - )*m.var_xi_arc_inv_t[t] - for t in m.set_T_int - ) if (g,l_diamond,l) in m.set_J_col else 0 - + - #********************************************************** - # TODO: outgoing arcs in non-interfaced optional groups, nominal - sum(sum(1 - for j in m.set_J_col[(g,l,l_diamond)] - #if j in m.set_J_int[(g,l,l_diamond)] - if (g,l,l_diamond,j) in m.set_GLLJ_col_t[t] - )*sum( - m.var_delta_arc_inv_th[(t,h)] - for h in m.set_H_t[t] - ) - for t in m.set_T - if t not in m.set_T_mdt - if t not in m.set_T_int - ) if (g,l,l_diamond) in m.set_J_col else 0 - + - # TODO: outgoing arcs in non-interfaced optional groups, reverse - sum(sum(1 - for j in m.set_J_col[(g,l_diamond,l)] - #if j in m.set_J_int[(g,l_diamond,l)] - if j in m.set_J_und[(g,l_diamond,l)] - if (g,l_diamond,l,j) in m.set_GLLJ_col_t[t] - )*sum( - m.var_delta_arc_inv_th[(t,h)] - for h in m.set_H_t[t] - ) - for t in m.set_T - if t not in m.set_T_mdt - if t not in m.set_T_int - ) if (g,l_diamond,l) in m.set_J_col else 0 - + - #********************************************************** - # interfaced individual outgoing arcs, nominal direction - sum(m.var_xi_arc_inv_gllj[(g,l,l_diamond,j)] - for j in m.set_J_int[(g,l,l_diamond)] # interfaced - if j not in m.set_J_col[(g,l,l_diamond)] # individual - ) if (g,l,l_diamond) in m.set_J_int else 0 - + - #********************************************************** - # interfaced individual undirected arcs, reverse direction - sum(m.var_xi_arc_inv_gllj[(g,l,l_diamond,j)] - for j in m.set_J_und[(g,l_diamond,l)] # undirected - if j in m.set_J_int[(g,l_diamond,l)] # interfaced - if j not in m.set_J_col[(g,l_diamond,l)] # individual - ) if (g,l_diamond,l) in m.set_J_und else 0 - + - #********************************************************** - # outgoing non-interfaced individual optional arcs - sum( - sum(m.var_delta_arc_inv_glljh[(g,l,l_diamond,j,h)] - for h in m.set_H_gllj[(g,l,l_diamond,j)]) - for j in m.set_J[(g,l,l_diamond)] - if j not in m.set_J_col[(g,l,l_diamond)] # individual - if j not in m.set_J_mdt[(g,l,l_diamond)] # optional - if j not in m.set_J_int[(g,l,l_diamond)] # interfaced - ) if (g,l,l_diamond) in m.set_J else 0 - + - #********************************************************** - # individual non-interfaced undirected arcs, reverse dir. - sum( - sum(m.var_delta_arc_inv_glljh[(g,l_diamond,l,j,h)] - for h in m.set_H_gllj[(g,l_diamond,l,j)]) - for j in m.set_J_und[(g,l_diamond,l)] # undirected - if j not in m.set_J_col[(g,l_diamond,l)] # individual - if j not in m.set_J_mdt[(g,l_diamond,l)] # optional - if j not in m.set_J_int[(g,l_diamond,l)] # interfaced - ) if (g,l_diamond,l) in m.set_J_und else 0 - + - #********************************************************** - # preselected outgonig arcs, nominal direction - len(m.set_J_pre[(g,l,l_diamond)] - ) if (g,l,l_diamond) in m.set_J_pre else 0 - + - #********************************************************** - # mandatory outgoing arcs, nominal direction - len(m.set_J_mdt[(g,l,l_diamond)] - ) if (g,l,l_diamond) in m.set_J_mdt else 0 - + - #********************************************************** - # undirected preselected arcs, reverse direction - sum(1 - for j in m.set_J_pre[(g,l_diamond,l)] - if j in m.set_J_und[(g,l_diamond,l)] - ) if (g,l_diamond,l) in m.set_J_pre else 0 - + - #********************************************************** - # undirected mandatory arcs, reverse direction - sum(1 - for j in m.set_J_mdt[(g,l_diamond,l)] - if j in m.set_J_und[(g,l_diamond,l)] - ) if (g,l_diamond,l) in m.set_J_mdt else 0 - #********************************************************** - for l_diamond in m.set_L[g] - if l_diamond not in m.set_L_imp[g] - if l_diamond != l - ) + ) <= 1 #+ + # M_gl*sum( + # #********************************************************** + # # outgoing arcs in interfaced groups, nominal direction + # sum(sum(1 + # for j in m.set_J_col[(g,l,l_diamond)] + # #if j in m.set_J_int[(g,l,l_diamond)] + # if (g,l,l_diamond,j) in m.set_GLLJ_col_t[t] + # )*m.var_xi_arc_inv_t[t] + # for t in m.set_T_int + # ) if (g,l,l_diamond) in m.set_J_col else 0 + # + + # # outgoing arcs in interfaced groups, reverse direction + # sum(sum(1 + # for j in m.set_J_col[(g,l_diamond,l)] + # #if j in m.set_J_int[(g,l_diamond,l)] + # if j in m.set_J_und[(g,l_diamond,l)] + # if (g,l_diamond,l,j) in m.set_GLLJ_col_t[t] + # )*m.var_xi_arc_inv_t[t] + # for t in m.set_T_int + # ) if (g,l_diamond,l) in m.set_J_col else 0 + # + + # #********************************************************** + # # TODO: outgoing arcs in non-interfaced optional groups, nominal + # sum(sum(1 + # for j in m.set_J_col[(g,l,l_diamond)] + # #if j in m.set_J_int[(g,l,l_diamond)] + # if (g,l,l_diamond,j) in m.set_GLLJ_col_t[t] + # )*sum( + # m.var_delta_arc_inv_th[(t,h)] + # for h in m.set_H_t[t] + # ) + # for t in m.set_T + # if t not in m.set_T_mdt + # if t not in m.set_T_int + # ) if (g,l,l_diamond) in m.set_J_col else 0 + # + + # # TODO: outgoing arcs in non-interfaced optional groups, reverse + # sum(sum(1 + # for j in m.set_J_col[(g,l_diamond,l)] + # #if j in m.set_J_int[(g,l_diamond,l)] + # if j in m.set_J_und[(g,l_diamond,l)] + # if (g,l_diamond,l,j) in m.set_GLLJ_col_t[t] + # )*sum( + # m.var_delta_arc_inv_th[(t,h)] + # for h in m.set_H_t[t] + # ) + # for t in m.set_T + # if t not in m.set_T_mdt + # if t not in m.set_T_int + # ) if (g,l_diamond,l) in m.set_J_col else 0 + # + + # #********************************************************** + # # interfaced individual outgoing arcs, nominal direction + # sum(m.var_xi_arc_inv_gllj[(g,l,l_diamond,j)] + # for j in m.set_J_int[(g,l,l_diamond)] # interfaced + # if j not in m.set_J_col[(g,l,l_diamond)] # individual + # ) if (g,l,l_diamond) in m.set_J_int else 0 + # + + # #********************************************************** + # # interfaced individual undirected arcs, reverse direction + # sum(m.var_xi_arc_inv_gllj[(g,l,l_diamond,j)] + # for j in m.set_J_und[(g,l_diamond,l)] # undirected + # if j in m.set_J_int[(g,l_diamond,l)] # interfaced + # if j not in m.set_J_col[(g,l_diamond,l)] # individual + # ) if (g,l_diamond,l) in m.set_J_und else 0 + # + + # #********************************************************** + # # outgoing non-interfaced individual optional arcs + # sum( + # sum(m.var_delta_arc_inv_glljh[(g,l,l_diamond,j,h)] + # for h in m.set_H_gllj[(g,l,l_diamond,j)]) + # for j in m.set_J[(g,l,l_diamond)] + # if j not in m.set_J_col[(g,l,l_diamond)] # individual + # if j not in m.set_J_mdt[(g,l,l_diamond)] # optional + # if j not in m.set_J_int[(g,l,l_diamond)] # interfaced + # ) if (g,l,l_diamond) in m.set_J else 0 + # + + # #********************************************************** + # # individual non-interfaced undirected arcs, reverse dir. + # sum( + # sum(m.var_delta_arc_inv_glljh[(g,l_diamond,l,j,h)] + # for h in m.set_H_gllj[(g,l_diamond,l,j)]) + # for j in m.set_J_und[(g,l_diamond,l)] # undirected + # if j not in m.set_J_col[(g,l_diamond,l)] # individual + # if j not in m.set_J_mdt[(g,l_diamond,l)] # optional + # if j not in m.set_J_int[(g,l_diamond,l)] # interfaced + # ) if (g,l_diamond,l) in m.set_J_und else 0 + # + + # #********************************************************** + # # preselected outgonig arcs, nominal direction + # len(m.set_J_pre[(g,l,l_diamond)] + # ) if (g,l,l_diamond) in m.set_J_pre else 0 + # + + # #********************************************************** + # # mandatory outgoing arcs, nominal direction + # len(m.set_J_mdt[(g,l,l_diamond)] + # ) if (g,l,l_diamond) in m.set_J_mdt else 0 + # + + # #********************************************************** + # # undirected preselected arcs, reverse direction + # sum(1 + # for j in m.set_J_pre[(g,l_diamond,l)] + # if j in m.set_J_und[(g,l_diamond,l)] + # ) if (g,l_diamond,l) in m.set_J_pre else 0 + # + + # #********************************************************** + # # undirected mandatory arcs, reverse direction + # sum(1 + # for j in m.set_J_mdt[(g,l_diamond,l)] + # if j in m.set_J_und[(g,l_diamond,l)] + # ) if (g,l_diamond,l) in m.set_J_mdt else 0 + # #********************************************************** + # for l_diamond in m.set_L[g] + # if l_diamond not in m.set_L_imp[g] + # if l_diamond != l + # ) ) if temp_constr == True: diff --git a/tests/examples_gis.py b/tests/examples_gis.py deleted file mode 100644 index 9a8d30a3adc6d71e6ee59c1544f5930294d07bce..0000000000000000000000000000000000000000 --- a/tests/examples_gis.py +++ /dev/null @@ -1,1642 +0,0 @@ -# imports - -# standard - -import uuid -import random -import math - -# local, external - -import networkx as nx -import osmnx as ox -from shapely.geometry import Point, LineString -from numpy.testing import assert_allclose -from osmnx.stats import count_streets_per_node -# from osmnx.projection import project_graph -from osmnx.routing import k_shortest_paths - -# local, internal - -import src.topupopt.data.gis.identify as gis_ident -import src.topupopt.data.gis.calculate as gis_calc -import src.topupopt.data.gis.modify as gis_mod -import src.topupopt.data.gis.utils as gis_utils -import src.topupopt.data.gis.osm as _osm - -#****************************************************************************** -#****************************************************************************** - -def examples(seed_number: int = None): - - # seed_number = random.randint(1,int(1e5)) - - # # # seed_number = 871 - - # # seed_number = 57945 - - # # seed_number = 299 - - # seed_number = 57129 - - print('Seed number: ' + str(seed_number)) - - #************************************************************************** - - # test calculating path lengths - - examples_path_length(seed_number) - - # test the identification and removal of intermediate nodes - - examples_finding_replacing_nodes(seed_number) - - # test finding nearest nodes - - example_nearest_node_keys(seed_number) - - # test the removal of redundant arcs - - example_removal_redundant_arcs(seed_number) - - # test removing roundabouts - - examples_transform_roundabouts(seed_number) - -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** - -def get_network_A(): - - #************************************************************************** - - # create network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - # add nodes - - number_nodes = 8 - - for node_index in range(number_nodes): - - xy = (random.random(), random.random()) - - geo = Point(xy) - - network.add_node(node_index, x=xy[0], y=xy[1], geometry=geo) - - # edges: should include straight paths, self-loops, redundant paths, dead ends - - list_edges = [ - (0,2),(1,2),(2,3),(3,4),(5,4),(6,5),(5,7) - ] - - for _edge in list_edges: - - geo = LineString( - [(network.nodes[_edge[0]]['x'], - network.nodes[_edge[0]]['y']), - (network.nodes[_edge[1]]['x'], - network.nodes[_edge[1]]['y'])] - ) - - length = network.nodes[_edge[0]]['geometry'].distance( - network.nodes[_edge[1]]['geometry'] - ) - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length) - - # add duplicate edges - - network.add_edge(_edge[0], - _edge[1], - length=length) - - network.add_edge(_edge[0], - _edge[1], - length=length+1) - - protected_nodes = [] - - return network, protected_nodes - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_B(): - - #************************************************************************** - - # create network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - # add nodes - - number_nodes = 20 - - for node_index in range(number_nodes): - - xy = (random.random(), random.random()) - - geo = Point(xy) - - network.add_node(node_index, x=xy[0], y=xy[1], geometry=geo) - - # edges: should include straight paths, self-loops, redundant paths, dead ends - - list_edges = [ - (0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10), - (0,11),(11,12),(12,13),(13,5), - (6,14),(14,15),(15,16),(16,17),(17,18),(18,19), - #(3,3), # self-loop - (8,8), # self-loop - (15,16) # redundant - ] - - for _edge in list_edges: - - geo = LineString( - [(network.nodes[_edge[0]]['x'], - network.nodes[_edge[0]]['y']), - (network.nodes[_edge[1]]['x'], - network.nodes[_edge[1]]['y'])] - ) - - length = network.nodes[_edge[0]]['geometry'].distance( - network.nodes[_edge[1]]['geometry'] - ) - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length) - - protected_nodes = [] - - return network, protected_nodes - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_C(): - - #************************************************************************** - - # create network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - # add nodes - - number_nodes = 20 - - for node_index in range(number_nodes): - - xy = (random.random(), random.random()) - - geo = Point(xy) - - network.add_node(node_index, x=xy[0], y=xy[1], geometry=geo) - - # network should include: - # 1) self loops - # 2) redundant arcs - # 3) dead ends - # 4) dead end loops - # 5) protected nodes - - protected_nodes = [17, 8] - - list_edges = [ - (0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10), - (0,11),(11,12),(12,13),(13,5), - (6,14),(14,15),(16,15),(16,17),(18,17),(18,19), - #(3,3), # self-loop - (8,8), # self-loop - (15,16) # redundant - ] - - for _edge in list_edges: - - geo = LineString( - [(network.nodes[_edge[0]]['x'], - network.nodes[_edge[0]]['y']), - (network.nodes[_edge[1]]['x'], - network.nodes[_edge[1]]['y'])] - ) - - length = network.nodes[_edge[0]]['geometry'].distance( - network.nodes[_edge[1]]['geometry'] - ) - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length) - - return network, protected_nodes - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_D(): - - #************************************************************************** - - # create network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - # add nodes - - number_nodes = 14 - - for node_index in range(number_nodes): - - xy = (random.random(), random.random()) - - geo = Point(xy) - - network.add_node(node_index, x=xy[0], y=xy[1], geometry=geo) - - # edges: should include straight paths, self-loops, redundant paths, dead ends - - list_edges = [ - (0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10), - (5,0),(10,6), - (10,11),(11,12),(12,13),(13,0) - ] - - for _edge in list_edges: - - geo = LineString( - [(network.nodes[_edge[0]]['x'], - network.nodes[_edge[0]]['y']), - (network.nodes[_edge[1]]['x'], - network.nodes[_edge[1]]['y'])] - ) - - length = network.nodes[_edge[0]]['geometry'].distance( - network.nodes[_edge[1]]['geometry'] - ) - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length) - - protected_nodes = [] - - return network, protected_nodes - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def examples_finding_replacing_nodes(seed_number: int = None): - - #************************************************************************** - - # paths via node keys - - paths_as_arc_keys = False - - if seed_number != None: - - random.seed(seed_number) - - networks = [ - get_network_A(), - get_network_B(), - get_network_C(), - get_network_D() - ] - - protected_nodes = [ - network[1] - for network in networks - ] - - networks = [ - network[0] - for network in networks - ] - - for network_index, network in enumerate(networks): - - example_intermediate_nodes_along_path( - network, - protected_nodes[network_index], - return_paths_as_arc_keys=paths_as_arc_keys - ) - - #************************************************************************** - - # paths via arc keys - - paths_as_arc_keys = True - - if seed_number != None: - - random.seed(seed_number) - - networks = [ - get_network_A(), - get_network_B(), - get_network_C(), - get_network_D() - ] - - protected_nodes = [ - network[1] - for network in networks - ] - - networks = [ - network[0] - for network in networks - ] - - for network_index, network in enumerate(networks): - - example_intermediate_nodes_along_path( - network, - protected_nodes[network_index], - return_paths_as_arc_keys=paths_as_arc_keys - ) - -#****************************************************************************** -#****************************************************************************** - -def examples_transform_roundabouts(seed_number: int = None): - - if seed_number != None: - - random.seed(seed_number) - - #************************************************************************** - - network, fake_roundabouts = get_network_with_roundabouts(seed_number) - - example_roundabouts_protocol(network, - seed_number=seed_number, - minimum_perimeter=None, - maximum_perimeter=None, - minimum_number_nodes=None, - maximum_number_nodes=None) - - #************************************************************************** - - # roundabouts with perimeter limits - - network, fake_roundabouts = get_network_with_roundabouts(seed_number) - - example_roundabouts_protocol(network, - seed_number=seed_number, - minimum_perimeter=0.5, - maximum_perimeter=1.5, - minimum_number_nodes=None, - maximum_number_nodes=None) - - # roundabouts with node number limits - - network, fake_roundabouts = get_network_with_roundabouts(seed_number) - - example_roundabouts_protocol(network, - seed_number=seed_number, - minimum_perimeter=None, - maximum_perimeter=None, - minimum_number_nodes=3, - maximum_number_nodes=3) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_with_roundabouts(seed_number: int = None): - - if seed_number != None: - - random.seed(seed_number) - - # create network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - # add nodes - - number_nodes = 20 - - for node_index in range(number_nodes): - - xy = (random.random(), random.random()) - - geo = Point(xy) - - network.add_node(node_index, x=xy[0], y=xy[1], geometry=geo) - - # roundabouts: - # 1) roundabout with 2 nodes - # 2) roundabout with 3 nodes - # 3) roundabout with 4 nodes - # 4) roundabout within roundabout 2 - # 5) roundabout overlapping with roundabouts 1 and 3 - - list_roundabout_edges = [ - # roundabout with 2 nodes - (0, 1), (1, 0), - # roundabout with 3 nodes - (2, 3), (3, 4), (4, 2), - # roundabout with 4 nodes - (5, 6), (6, 7), (7, 8), (8, 5), - # roundabout within roundabout 2 - (2, 4), - # roundabout overlapping with roundabouts 1 and 3 - (9, 10), (10, 11), (11, 9), - # fake roundabouts - # self loop - (12, 12), - # no oneway attr - (13, 14), (14, 15), (15, 13), - # oneway = False - (16, 17), (17, 18), (18, 19), (19, 16), - # connect 1 and 3 with a edge - (1, 6), (6, 1) - ] - - # add - - for _edge in list_roundabout_edges: - - geo = LineString( - [(network.nodes[_edge[0]]['x'], - network.nodes[_edge[0]]['y']), - (network.nodes[_edge[1]]['x'], - network.nodes[_edge[1]]['y'])] - ) - - length = network.nodes[_edge[0]]['geometry'].distance( - network.nodes[_edge[1]]['geometry'] - ) - - # handle the various cases - - if _edge == (12, 12): - - # self loop - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length, - oneway=True) - - elif _edge == (14, 15): - - # no one way attr - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length) - - elif (_edge == (17, 18) or _edge == (1, 6) or _edge == (6, 1)): - - # oneway = False - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length, - oneway=False) - - else: - - # general case - - network.add_edge(_edge[0], - _edge[1], - geometry=geo, - length=length, - oneway=True) - - # add connecting edges - - list_connecting_edges = [ - (node_key, other_node_key) - for node_key in network.nodes() - for other_node_key in network.nodes() - if random.randint(0, 1) - ] - - while len(list_connecting_edges) > len(list_roundabout_edges): - - random_pop = random.randint(0, len(list_connecting_edges)-1) - - list_connecting_edges.pop(random_pop) - - for edge_key in list_connecting_edges: - - geo = LineString( - [(network.nodes[edge_key[0]]['x'], - network.nodes[edge_key[0]]['y']), - (network.nodes[edge_key[1]]['x'], - network.nodes[edge_key[1]]['y'])] - ) - - length = network.nodes[edge_key[0]]['geometry'].distance( - network.nodes[edge_key[1]]['geometry'] - ) - - network.add_edge(edge_key[0], - edge_key[1], - geometry=geo, - length=length) - - # fake roundabouts: - # 1) no oneway attr - # 2) oneway=False - # 3) self loop - - # fake roundabouts - - fake_roundabouts = [ - # self-loop - [12], - # no oneway attr - [13, 14, 15], - # oneway = False - [16, 17, 18, 19]] - - return network, fake_roundabouts - -#****************************************************************************** -#****************************************************************************** - -def example_roundabouts_protocol(network: nx.MultiDiGraph, - seed_number: int = None, - minimum_perimeter: float = 0, - maximum_perimeter: float = 100, - minimum_number_nodes: int = 0, - maximum_number_nodes: int = 5): - - #************************************************************************** - - # find all loops - - all_loops = nx.simple_cycles(network) - - initial_number_loops = len(list(all_loops)) - - assert initial_number_loops != 0 - - #************************************************************************** - - # find the nodes constituting roundabouts - - original_roundabouts = gis_ident.find_roundabouts( - network, - minimum_perimeter=minimum_perimeter, - maximum_perimeter=maximum_perimeter, - minimum_number_nodes=minimum_number_nodes, - maximum_number_nodes=maximum_number_nodes) - - initial_number_roundabouts_found = len(original_roundabouts) - - # assert that there is at least one roundabout - - assert initial_number_roundabouts_found >= 1 - - for roundabout in original_roundabouts: - - assert gis_ident.is_roundabout(network, roundabout) - - #************************************************************************** - - # locate all external nodes affected by transforming the roundabout - - arcs_affected_by_roundabout = { - i: [ - edge_key - # for each node in the roundabout - for node_key in original_roundabout_nodes - # for each neighbouring nodes - for other_node_key in gis_ident.neighbours(network, node_key) - # 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.get_edges_between_two_nodes( - network, - node_key, - other_node_key) - ] - for i, original_roundabout_nodes in enumerate(original_roundabouts)} - - # get all the respective lengths - - dict_edge_key_lengths = { - edge_key: network.edges[edge_key][_osm.KEY_OSMNX_LENGTH] - for roundabout_index in range(len(original_roundabouts)) - for edge_key in arcs_affected_by_roundabout[roundabout_index] - } - - #************************************************************************** - - # transform the roundabouts - - new_roundabout_nodes = gis_mod.transform_roundabouts_into_crossroads( - network, - original_roundabouts) - - # assert that the nr. of roundabout nodes matches the number of roundabouts - - assert len(new_roundabout_nodes) == len(original_roundabouts) - - # final network - - final_loops = nx.simple_cycles(network) - - final_number_loops = len(list(final_loops)) - - # assert that the initial number of loops is greater than or equal to the - # number of loops in the final object plus those removed - - assert initial_number_loops >= final_number_loops - - roundabout_nodes = gis_ident.find_roundabouts( - network, - minimum_perimeter=minimum_perimeter, - maximum_perimeter=maximum_perimeter, - minimum_number_nodes=minimum_number_nodes, - maximum_number_nodes=maximum_number_nodes) - - final_number_roundabouts_found = len(roundabout_nodes) - - assert initial_number_roundabouts_found >= final_number_roundabouts_found - - #************************************************************************** - - # verify that the external nodes affected by the transformation are still - # connected to the respective roundabouts - - # for each roundabout - - for roundabout_index, new_roundabout_node_key in enumerate(new_roundabout_nodes): - - # for each edge involving the roundabout under consideration - - for edge_key in arcs_affected_by_roundabout[roundabout_index]: - - # check if the current roundabout has been transformed - - if new_roundabout_node_key is None: - - # cases: 1) overlapping roundabouts - - # the current roundabout has not been transformed: all original - # arcs should still exist unless they are part of roundabouts - # that were also transformed - - if network.has_edge(u=edge_key[0], - v=edge_key[1], - key=edge_key[2]): - - # the arc exists - - # assert that it has the same length - - assert ( - dict_edge_key_lengths[edge_key] == network.edges[ - edge_key][_osm.KEY_OSMNX_LENGTH]) - - # it does, now continue - - continue - - # the original arc does not exist: it was modified or deleted - - # check if the source node exists - - if network.has_node(edge_key[0]): - - # TODO: introduce a roundabout to enter this test case - - # it does, now check that the sink node does not exist - - assert not network.has_node(edge_key[1]) - - # find original roundabouts to which the sink node belonged - - roundabout_candidates = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[1] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates) >= 1 - - # assert that the length is longer than the original - - for ra_i in roundabout_candidates: - - for arc_key in gis_ident.get_edges_from_a_to_b( - network, - edge_key[0], - new_roundabout_nodes[ra_i]): - - assert (dict_edge_key_lengths[edge_key] <= - network.edges[arc_key][_osm.KEY_OSMNX_LENGTH]) - - continue - - # check if the sink node exists - - if network.has_node(edge_key[1]): - - # it does, now check that the source node does not exist - - assert not network.has_node(edge_key[0]) - - # find original roundabouts to which the source node belonged - - roundabout_candidates = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[0] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates) >= 1 - - # assert that the length is longer than the original - - for ra_i in roundabout_candidates: - - for arc_key in gis_ident.get_edges_from_a_to_b( - network, - new_roundabout_nodes[ra_i], - edge_key[1]): - - assert (dict_edge_key_lengths[edge_key] <= - network.edges[arc_key][_osm.KEY_OSMNX_LENGTH]) - - continue - - #****************************************************************** - #****************************************************************** - #****************************************************************** - - # the roundabout was transformed: - # 1) the source node no longer exists, the sink node does - # 2) the sink node no longer exists, the source node does - # 3) none of the nodes exists any more - - #****************************************************************** - #****************************************************************** - #****************************************************************** - - # the original arc does not exist: it was modified or deleted - - #****************************************************************** - - # check if the source node exists - - if network.has_node(edge_key[0]): - - # 2) the sink node no longer exists, the source node does - - assert not network.has_node(edge_key[1]) - - # find original roundabouts to which the sink node belonged - - roundabout_candidates = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[1] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates) >= 1 - - # assert that the length is longer than the original - - for ra_i in roundabout_candidates: - - for arc_key in gis_ident.get_edges_from_a_to_b( - network, - edge_key[0], - new_roundabout_nodes[ra_i]): - - assert (dict_edge_key_lengths[edge_key] <= - network.edges[arc_key][_osm.KEY_OSMNX_LENGTH]) - - continue - - #****************************************************************** - - # check if the sink node exists - - if network.has_node(edge_key[1]): - - # 1) the source node no longer exists, the sink node does - - assert not network.has_node(edge_key[0]) - - # find original roundabouts to which the source node belonged - - roundabout_candidates = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[0] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates) >= 1 - - # assert that the length is longer than the original - - for ra_i in roundabout_candidates: - - for arc_key in gis_ident.get_edges_from_a_to_b( - network, - new_roundabout_nodes[ra_i], - edge_key[1]): - - assert (dict_edge_key_lengths[edge_key] <= - network.edges[arc_key][_osm.KEY_OSMNX_LENGTH]) - - continue - - #****************************************************************** - - # 3) none of the nodes exists any more - - assert not network.has_node(edge_key[0]) - - assert not network.has_node(edge_key[1]) - - # find original roundabouts to which the source node belonged - - roundabout_candidates_source = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[0] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates_source) >= 1 - - # assert that the length is longer than the original - - # find original roundabouts to which the source node belonged - - roundabout_candidates_sink = [ - ra_i - for ra_i, ra_nodes in enumerate(original_roundabouts) - if edge_key[1] in ra_nodes - if new_roundabout_nodes[ra_i] != None - ] - - # assert that at least one was transformed - - assert len(roundabout_candidates_sink) >= 1 - - # assert that the length is longer than the original - - for ra_so_i in roundabout_candidates_source: - - for ra_si_i in roundabout_candidates_sink: - - 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]): - - assert (dict_edge_key_lengths[edge_key] <= - network.edges[arc_key][_osm.KEY_OSMNX_LENGTH]) - - continue - -#****************************************************************************** -#****************************************************************************** - -def example_intermediate_nodes_along_path(network: nx.MultiDiGraph, - excluded_nodes: list, - return_paths_as_arc_keys: bool, - ignore_edge_direction: bool = True): - - #************************************************************************** - #************************************************************************** - - # find straight paths - - simplifiable_paths = gis_ident.find_all_straight_paths( - network=network, - excluded_nodes=excluded_nodes, - return_paths_as_arc_keys=return_paths_as_arc_keys) - - # test the paths - - for path in simplifiable_paths: - - assert gis_ident.is_path_simplifiable( - network, - path, - path_as_node_keys=(not return_paths_as_arc_keys) - ) - - # get the length of each path - - if return_paths_as_arc_keys: - - path_lengths = [ - gis_calc.edge_path_length( - network, - path, - ignore_edge_direction=ignore_edge_direction) - for path in simplifiable_paths - ] - - else: - - path_lengths = [ - gis_calc.node_path_length( - network, - path, - return_minimum_length_only=True) - for path in simplifiable_paths - ] - - # replace the paths - - for path_index, path in enumerate(simplifiable_paths): - - # replace the path - - gis_mod.replace_path(network, - path, - path_as_arc_keys=return_paths_as_arc_keys) - - # try to find the paths again - - new_simplifiable_paths = gis_ident.find_all_straight_paths( - network=network, - excluded_nodes=excluded_nodes, - return_paths_as_arc_keys=return_paths_as_arc_keys) - - # test the paths again - - for path in new_simplifiable_paths: - - assert gis_ident.is_path_simplifiable( - network, - path, - path_as_node_keys=(not return_paths_as_arc_keys) - ) - - #************************************************************************** - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** - -def example_removal_redundant_arcs(seed_number: int = None): - - #************************************************************************** - - if seed_number != None: - - random.seed(seed_number) - - #************************************************************************** - - # create a network - - network_order = random.randint(4,6) - - network = nx.MultiDiGraph( - incoming_graph_data=nx.binomial_tree(network_order, - nx.MultiDiGraph) - ) - - # add multiple edges between two nodes - - list_arcs = [arc for arc in network.edges(keys=True)] - - for arc in list_arcs: - - # add length to first arc - - network.add_edge( - arc[0], - arc[1], - key=arc[2], - length=random.random() - ) - - # add more arcs, also with a length attribute - - if random.randint(0, 1): - - # add more arcs and lengths - - network.add_edge( - u_for_edge=arc[0], - v_for_edge=arc[1], - length=random.random() - ) - - # remove redundant arcs - - gis_mod.remove_redundant_arcs(network) - - # verify results - - # for each arc, make sure there is only one arc per direction - - for arc in network.edges(keys=True): - - assert len( - gis_ident.get_edges_from_a_to_b(network,arc[0],arc[1]) - ) == 1 - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** - -def get_network_with_unconnected_points( - add_geometry_attr: bool = False): - - # from shapely.geometry import LineString - # import osmnx as ox - # import networkx as nx - - # create a network - - network = nx.MultiDiGraph() - - x_offset = 15 - - y_offset = 45 - - x_amp = 0.01 - - y_amp = 0.01 - - network.graph['crs'] = 'epsg:4326' - - network.graph['simplified'] = True - - #************************************************************************** - - # define the nodes - - list_nodes = [ - # connected nodes - (0, {'x':x_offset+0*x_amp,'y':y_offset+0*y_amp}), - (1, {'x':x_offset+1*x_amp,'y':y_offset+0*y_amp}), - (2, {'x':x_offset+2*x_amp,'y':y_offset+0*y_amp}), - (3, {'x':x_offset+3*x_amp,'y':y_offset+1*y_amp}), - (4, {'x':x_offset+3*x_amp,'y':y_offset-1*y_amp}), - (5, {'x':x_offset+3*x_amp,'y':y_offset-2*y_amp}), - # unconnected nodes - (6, {'x':x_offset+0.5*x_amp,'y':y_offset+0.5*y_amp}), - (7, {'x':x_offset+2.0*x_amp,'y':y_offset+0.5*y_amp}), - (8, {'x':x_offset+2.0*x_amp,'y':y_offset+1.0*y_amp}), - (9, {'x':x_offset+3.0*x_amp,'y':y_offset+1.5*y_amp}), - (10, {'x':x_offset+4.0*x_amp,'y':y_offset+0.0*y_amp}), - - (11, {'x':x_offset+2.0*x_amp,'y':y_offset-1.5*y_amp}), - (12, {'x':x_offset+4.0*x_amp,'y':y_offset-1.2*y_amp}), - (13, {'x':x_offset+5.0*x_amp,'y':y_offset-1.8*y_amp}), - (14, {'x':x_offset+3.0*x_amp,'y':y_offset-4.0*y_amp}), - (15, {'x':x_offset-1.0*x_amp,'y':y_offset+0.0*y_amp}), - - ] - - network.add_nodes_from(list_nodes) - - # define the edges - - list_edges = [ - (0,1), - (1,2), - (2,3), - (3,4), - (4,5), - ] - - network.add_edges_from(list_edges) - - #************************************************************************** - - # add geometry to select edges - - network.add_edge( - u_for_edge=1, - v_for_edge=2, - key=0, - geometry=LineString( - [(x_offset+1*x_amp, y_offset+0*y_amp), - (x_offset+1.5*x_amp, y_offset+0.25*y_amp), - (x_offset+2*x_amp, y_offset+0*y_amp)] - ) - ) - - network.add_edge( - u_for_edge=3, - v_for_edge=4, - key=0, - geometry=LineString( - [(x_offset+3.0*x_amp, y_offset+1*y_amp), - (x_offset+3.2*x_amp, y_offset+0*y_amp), - (x_offset+3.0*x_amp, y_offset-1*y_amp)] - ) - ) - - # TODO: add oneway and reversed attributes - - #************************************************************************** - - # add lengths to edges - - for edge_key in network.edges(keys=True): - - # get its data dict - - edge_dict = network.get_edge_data(u=edge_key[0], - v=edge_key[1], - key=edge_key[2]) - - # prepare the geometry - - if 'geometry' in edge_dict: - - edge_geo = edge_dict['geometry'] - - else: - - edge_geo_point_list = [ - (network.nodes[edge_key[0]]['x'], - network.nodes[edge_key[0]]['y']), - (network.nodes[edge_key[1]]['x'], - network.nodes[edge_key[1]]['y'])] - - edge_geo = LineString( - edge_geo_point_list - ) - - if add_geometry_attr: - - edge_dict['geometry'] = edge_geo - - edge_dict['length'] = ( - gis_calc.great_circle_distance_along_path( - edge_geo) - ) - - network.add_edge( - edge_key[0], - edge_key[1], - edge_key[2], - **edge_dict - ) - - #************************************************************************** - - return network - -#****************************************************************************** -#****************************************************************************** - -def example_simplify_network(network: nx.MultiDiGraph, - nodes_excluded: list = None, - seed_number: int = None, - update_street_count: bool = True): - - #************************************************************************** - - if seed_number != None: - - random.seed(seed_number) - - #************************************************************************** - - # exclude some nodes - - if nodes_excluded == None or len(nodes_excluded) == 0: - - nodes_excluded = [ - node_key - for node_key in network.nodes() - if random.randint(0,1)] - - #************************************************************************** - - gis_utils.simplify_network( - network, - nodes_excluded, - update_street_count_per_node=update_street_count) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def verify_street_count(network: nx.MultiDiGraph): - - # count the number of streets per node - - street_count_dict = count_streets_per_node(network) - - # for each node - - for node_key in network.nodes(): - - try: - street_count = network.nodes[node_key][_osm.KEY_OSMNX_STREET_COUNT] - except KeyError: - continue - - assert street_count == street_count_dict[node_key] - -#****************************************************************************** -#****************************************************************************** - -def protocol_validate_path_length(network: nx.MultiDiGraph): - - protocol_validate_node_path_length(network) - - protocol_validate_edge_path_length(network) - -#****************************************************************************** -#****************************************************************************** - -def protocol_validate_edge_path_length(network: nx.MultiDiGraph): - - # paths as arc keys, minimum only - - # for each pair of nodes - - for node_key in network.nodes(): - - for other_node_key in network.nodes(): - - if not nx.has_path(network, node_key, other_node_key): - - continue - - # for each possible path - - list_simple_paths = [ - path - for path in nx.all_simple_edge_paths(network, - node_key, - other_node_key) - ] - - list_simple_path_lengths = [ - gis_calc.edge_path_length( - network, - path - ) - for path in list_simple_paths] - - # use k_shortest_paths to identify the shortest paths - - list_k_shortest_paths = list( - k_shortest_paths( - network, - node_key, - other_node_key, - len(list_simple_paths)) - ) - - # sort list_simple_path_lengths and list_simple_paths - - temp_sort = sorted( - ((v, i) for i, v in enumerate(list_simple_path_lengths)), - reverse=False) - - # sorted_list_simple_path_lengths = [temp[0] - # for temp in temp_sort] - - sorted_list_simple_paths = [list_simple_paths[temp[1]] - for temp in temp_sort] - - # make sure the list_k_shortest_paths and sorted_list_simple_paths - # have the same size by adding elements to list_k_shortest_paths - - # if list_k_shortest_paths is smaller than sorted_list_simple_paths - - if len(list_k_shortest_paths) < len(sorted_list_simple_paths): - - # if the node paths on list_k_shortest_paths are the same as in - # sorted_list_simple_paths, add them to list_kso - - # for each (edge) path - - for path_index, path in enumerate(sorted_list_simple_paths): - - # transform it into a node path - - path_in_nodes = [ - arc_key[1] - for arc_key in path - if arc_key[0] != arc_key[1]] - path_in_nodes.insert(0, path[0][0]) - - - # make sure this path is in list_k_shortest_paths - - assert path_in_nodes in list_k_shortest_paths - - # assert that the paths are the same and on the same position - - try: - - assert path_in_nodes == list_k_shortest_paths[path_index] - - except IndexError: - - # index exceeded: append path - - list_k_shortest_paths.append(path_in_nodes) - - except AssertionError: - - # incorrect order: insert path in the path_index index - - list_k_shortest_paths.insert(path_index, path_in_nodes) - - # for each simple path - - for path_index, path in enumerate(sorted_list_simple_paths): - - # convert path to nodes - - path_in_nodes = [ - arc_key[1] - for arc_key in path - if arc_key[0] != arc_key[1]] - path_in_nodes.insert(0, path[0][0]) - - # assert that the ordered paths match - - assert path_in_nodes == list_k_shortest_paths[path_index] - -#****************************************************************************** -#****************************************************************************** - -def protocol_validate_node_path_length(network: nx.MultiDiGraph): - - # paths as node keys, minimum only - - return_minimum_path_length = True - - # for each pair of nodes - - for node_key in network.nodes(): - - for other_node_key in network.nodes(): - - if not nx.has_path(network, node_key, other_node_key): - - continue - - # for each possible path - - list_simple_paths = [ - path - for path in nx.all_simple_paths(network, - node_key, - other_node_key) - ] - - # remove duplicates - - for path in list_simple_paths: - while list_simple_paths.count(path) != 1: - list_simple_paths.remove(path) - - list_simple_path_lengths = [ - gis_calc.node_path_length( - network, - path, - return_minimum_length_only=return_minimum_path_length) - for path in list_simple_paths] - - # use k_shortest_paths to identify the shortest paths - - list_k_shortest_paths = list( - k_shortest_paths(network, - node_key, - other_node_key, - len(list_simple_paths)) - ) - - # sort list_simple_path_lengths and list_simple_paths - - temp_sort = sorted( - ((v, i) for i, v in enumerate(list_simple_path_lengths)), - reverse=False) - - # sorted_list_simple_path_lengths = [temp[0] - # for temp in temp_sort] - - sorted_list_simple_paths = [list_simple_paths[temp[1]] - for temp in temp_sort] - - # assert that the ordered paths match - - assert list_k_shortest_paths == sorted_list_simple_paths - - #************************************************************************** - - # paths as node keys, all lengths - - return_minimum_path_length = True - - # for each pair of nodes - - for node_key in network.nodes(): - - for other_node_key in network.nodes(): - - if not nx.has_path(network, node_key, other_node_key): - - continue - - # for each possible path - - list_simple_paths = [ - path - for path in nx.all_simple_paths(network, - node_key, - other_node_key) - ] - - # remove duplicates (paths_as_node_keys = True) - - for path in list_simple_paths: - while list_simple_paths.count(path) != 1: - list_simple_paths.remove(path) - - # for each path, get all the possible lengths - - list_simple_path_lengths = [ - gis_calc.node_path_length( - network, - path, - return_minimum_length_only=return_minimum_path_length) - for path in list_simple_paths] - - # use k_shortest_paths to identify the shortest paths - - list_k_shortest_paths = list( - k_shortest_paths(network, - node_key, - other_node_key, - len(list_simple_paths)) - ) - - if len(list_simple_path_lengths) == 0: - - assert len(list_k_shortest_paths) == 0 - - continue - - # sort list_simple_path_lengths and list_simple_paths - - temp_sort = sorted( - ((v, i) for i, v in enumerate(list_simple_path_lengths)), - reverse=False) - - # sorted_list_simple_path_lengths = [temp[0] - # for temp in temp_sort] - - sorted_list_simple_paths = [list_simple_paths[temp[1]] - for temp in temp_sort] - - # for each simple path - - assert list_k_shortest_paths == sorted_list_simple_paths - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def examples_path_length(seed_number: int = None): - - if seed_number != None: - - random.seed(seed_number) - - networks = [ - get_network_A()[0], - get_network_B()[0], - get_network_C()[0], - get_network_D()[0] - ] - - for network in networks: - - protocol_validate_path_length(network) - - #************************************************************************** - - # test unusual cases - - network = get_network_A()[0] - - # empty path - - path = [] - - my_path_length = gis_calc.node_path_length( - network, - path, - return_minimum_length_only=True) - - assert my_path_length == math.inf - - # invalid path, minimum only - - path = ['node1','node2','node3'] - - my_path_length = gis_calc.node_path_length( - network, - path, - return_minimum_length_only=True) - - assert my_path_length == math.inf - - # invalid path, all paths - - path = ['node1','node2','node3'] - - my_path_length = gis_calc.node_path_length( - network, - path, - return_minimum_length_only=False) - - assert my_path_length == [math.inf] - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** diff --git a/tests/examples_gis_utils.py b/tests/examples_gis_utils.py deleted file mode 100644 index d591726383f89e67fc6322a93c21ffac7b0a7c0b..0000000000000000000000000000000000000000 --- a/tests/examples_gis_utils.py +++ /dev/null @@ -1,2063 +0,0 @@ -# imports - -# standard - -from ast import literal_eval -import random - -# local, external - -from geopandas import GeoDataFrame -from pandas import concat, MultiIndex, Series -import networkx as nx -import osmnx as ox -from shapely.geometry import Point, LineString - -# local, internal - -import src.topupopt.data.gis.identify as gis_iden -import src.topupopt.data.gis.utils as gis_utils -import src.topupopt.data.gis.osm as _osm - -ox.settings.use_cache = True - -#****************************************************************************** -#****************************************************************************** - -def examples(network: nx.MultiDiGraph, seed_number: int = None): - - if type(seed_number) == type(None): - - seed_number = random.randint(0,int(1e5)) - - random.seed(a=seed_number) - - print('Seed number: ' + str(seed_number)) - - #************************************************************************** - - # test io - - examples_gpkg_write_errors() - - example_io_geodataframe( - preserve_original_gdf=True, identify_columns=False - ) - - example_io_geodataframe( - preserve_original_gdf=False, identify_columns=False - ) - - # example_io_geodataframe( - # preserve_original_gdf=True, identify_columns=True - # ) - - # example_io_geodataframe( - # preserve_original_gdf=False, identify_columns=True - # ) - - # TODO: handle GeoJSON files - - # example_io_geodataframe(preserve_original_gdf=True, - # file_extension='.json') - - # example_io_geodataframe(preserve_original_gdf=False, - # file_extension='.json') - - # test generating containers - - example_generate_node_container(False, False) - - example_generate_node_container(True, False) - - example_generate_node_container(False, True) - - example_generate_node_container(True, True) - - example_generate_node_container(False, False, True) - - # trigger error when generating node containers - - example_node_container_error() - - #************************************************************************** - - # test finding the building entrances - - example_identify_entrances_simple_no_driveway_closest() - - # no driveway, all nodes - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=False) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - focus_on_node_P_only=False) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True, - focus_on_node_P_only=False) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - focus_on_node_P_only=False) - - # no driveway, all nodes, multiple addresses per arc - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=False, - use_multiple_addresses=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - focus_on_node_P_only=False, - use_multiple_addresses=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True, - focus_on_node_P_only=False, - use_multiple_addresses=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - focus_on_node_P_only=False, - use_multiple_addresses=True) - - # no driveway, all nodes, revert projection - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - focus_on_node_P_only=False, - revert_to_original_crs=True) - - # no driveway, limited selection of nodes - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - focus_on_node_P_only=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True, - focus_on_node_P_only=True) - - example_identify_entrances_simple_no_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - focus_on_node_P_only=True) - - # driveway, all nodes - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True) - - # driveway, all nodes, multiple addresses per arc - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - use_multiple_addresses=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - use_multiple_addresses=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True, - use_multiple_addresses=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - use_multiple_addresses=True) - - # driveway, limited selection - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - focus_on_node_P_only=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=True, - focus_on_node_P_only=True) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=True, - focus_on_node_P_only=True) - - # driveway variation - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=False, - BD_with_name=False, - BD_right_address=False) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=False, - BD_with_name=True, - BD_right_address=False) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=False, - create_reversed_arcs=False, - focus_on_node_P_only=False, - BD_with_name=True, - BD_right_address=False) - - example_identify_entrances_simple_driveway(AB_right_BC_wrong=True, - create_reversed_arcs=False, - focus_on_node_P_only=False, - BD_with_name=True, - BD_right_address=True) - - # special cases - - example_identify_entrances_simple_special(create_reversed_arcs=False, - revert_to_original_crs=False, - focus_on_node_P_only=False) - - example_identify_entrances_simple_special(create_reversed_arcs=True, - revert_to_original_crs=False, - focus_on_node_P_only=False) - - example_identify_entrances_simple_special(create_reversed_arcs=False, - revert_to_original_crs=False, - focus_on_node_P_only=True) - - example_identify_entrances_simple_special(create_reversed_arcs=True, - revert_to_original_crs=False, - focus_on_node_P_only=True) - - # no matching street name in the entire network - - example_identify_entrances_simple_special(create_reversed_arcs=False, - revert_to_original_crs=False, - focus_on_node_P_only=False, - CE_wrong=True) - - # TODO: test a case with multiple parallel arcs - - #************************************************************************** - - # test counting occurrences in a geodataframe - - example_occurrences() - - example_create_osmnx_gdf() - - example_discrete_plot_gdf() - - examples_convert_edge_paths() - - example_get_directed(network) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** -#****************************************************************************** - -def get_node_gdf_A(right_address: str = 'right', - wrong_address: str = 'wrong', - country_code: str = _osm.KEY_COUNTRY_DK): - - #************************************************************************** - - # 4 nodes: A, B, C and P - - # node A - - osmid_A = 'A' - - xy_A = (0, 0) - - geo_A = Point(xy_A) - - node_uid_A = 5123 - - address_A = None - - # node B - - osmid_B = 'B' - - xy_B = (1, 0) - - geo_B = Point(xy_B) - - node_uid_B = 1844 - - address_B = None - - # node C - - osmid_C = 'C' - - xy_C = (2, 0) - - geo_C = Point(xy_C) - - node_uid_C = 1845 - - address_C = None - - # node P - - osmid_P = 'P' - - xy_P = (0.5, 1) - - geo_P = Point(xy_P) - - node_uid_P = 9475 - - address_P = right_address - - #************************************************************************** - - # geodataframe: should have 'addr:street', 'osak:identifier' and index - - gdf = GeoDataFrame( - data={_osm.KEY_OSM_STREET: [address_A, # A - address_B, # B - address_C, # C - address_P], # P - _osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code]: [node_uid_A, # A - node_uid_B, # B - node_uid_C, # C - node_uid_P],# P - _osm.KEY_OSMNX_ELEMENT_TYPE: ['node','node','node','node'], - _osm.KEY_OSMNX_OSMID: [osmid_A, osmid_B, osmid_C, osmid_P]}, - geometry=[geo_A, geo_B, geo_C, geo_P] - ) - - gdf.set_index([_osm.KEY_OSMNX_ELEMENT_TYPE, _osm.KEY_OSMNX_OSMID], - drop=True, - inplace=True) - - return gdf - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_node_gdf_B(right_address: str = 'right', - wrong_address: str = 'wrong', - country_code: str = _osm.KEY_COUNTRY_DK): - - #************************************************************************** - - gdf = get_node_gdf_A(right_address=right_address, - wrong_address=wrong_address, - country_code=country_code) - - # add another node D closer to P than A, B and C - - osmid_D = 'D' - - xy_D = (0.75, 1) - - geo_D = Point(xy_D) - - node_uid_D = 8842 - - # address_D = None - - gdf_node_D = GeoDataFrame({_osm.KEY_OSMNX_GEOMETRY: [geo_D], - _osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code]: - [node_uid_D], - #_osm.KEY_OSM_STREET: [address_D],# P - #_osm.KEY_OSMNX_ELEMENT_TYPE: ['node'], - #_osm.KEY_OSMNX_OSMID: [osmid_D] - }, - #index=[('node', osmid_D)], - index=MultiIndex.from_tuples( - [('node',osmid_D)], - names=[_osm.KEY_OSMNX_ELEMENT_TYPE, - _osm.KEY_OSMNX_OSMID]) - ) - - #************************************************************************** - - gdf = concat([gdf, gdf_node_D]) - - return gdf - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_node_gdf_C(right_address: str = 'right', - wrong_address: str = 'wrong', - country_code: str = _osm.KEY_COUNTRY_DK): - - #************************************************************************** - - gdf = get_node_gdf_B(right_address=right_address, - wrong_address=wrong_address, - country_code=country_code) - - # add another node E, east of C - - osmid_E = 'E' - - xy_E = (3, 0) - - geo_E = Point(xy_E) - - node_uid_E = 9173 - - #address_E = right_address - - gdf_node_E = GeoDataFrame({_osm.KEY_OSMNX_GEOMETRY: [geo_E], - _osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code]: - [node_uid_E], - #_osm.KEY_OSM_STREET: [address_E], - }, - #index=[('node', osmid_E)] - index=MultiIndex.from_tuples( - [('node',osmid_E)], - names=[_osm.KEY_OSMNX_ELEMENT_TYPE, - _osm.KEY_OSMNX_OSMID]) - ) - - #************************************************************************** - - gdf = concat([gdf, gdf_node_E]) - - return gdf - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_A(gdf: GeoDataFrame, - right_address: str = 'right', - wrong_address: str = 'wrong', - AB_right_BC_wrong: bool = True, - country_code: str = _osm.KEY_COUNTRY_DK, - use_multiple_addresses: bool = False): - - #************************************************************************** - - # create network - - (node_keys, - node_data_container, - node_key_to_gdf_index_dict) = gis_utils.prepare_node_data_from_geodataframe( - include_geometry=True, - gdf=gdf) - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - network.add_nodes_from(node_data_container) - - #************************************************************************** - - # two arcs: AB and BC - - node_key_A = 'A' - node_key_B = 'B' - node_key_C = 'C' - - # arc AB - - geo_AB = LineString( - [(network.nodes[node_key_A][_osm.KEY_OSMNX_X], - network.nodes[node_key_A][_osm.KEY_OSMNX_Y]), - (network.nodes[node_key_B][_osm.KEY_OSMNX_X], - network.nodes[node_key_B][_osm.KEY_OSMNX_Y])] - ) - - length_AB = network.nodes[node_key_A][_osm.KEY_OSMNX_GEOMETRY].distance( - network.nodes[node_key_B][_osm.KEY_OSMNX_GEOMETRY] - ) - - network.add_edge(node_key_A, - node_key_B, - **{_osm.KEY_OSMNX_GEOMETRY: geo_AB, - _osm.KEY_OSMNX_LENGTH: length_AB, - _osm.KEY_OSMNX_NAME: ( - ['HZ', (right_address if AB_right_BC_wrong else - wrong_address)] if use_multiple_addresses else ( - right_address if AB_right_BC_wrong else - wrong_address) - ) - }) - - # arc BC - - geo_BC = LineString( - [(network.nodes[node_key_B][_osm.KEY_OSMNX_X], - network.nodes[node_key_B][_osm.KEY_OSMNX_Y]), - (network.nodes[node_key_C][_osm.KEY_OSMNX_X], - network.nodes[node_key_C][_osm.KEY_OSMNX_Y])] - ) - - length_BC = network.nodes[node_key_B][_osm.KEY_OSMNX_GEOMETRY].distance( - network.nodes[node_key_C][_osm.KEY_OSMNX_GEOMETRY] - ) - - network.add_edge(node_key_B, - node_key_C, - **{_osm.KEY_OSMNX_GEOMETRY: geo_BC, - _osm.KEY_OSMNX_LENGTH: length_BC, - _osm.KEY_OSMNX_NAME: ( - [(wrong_address if AB_right_BC_wrong else - right_address), 'UQ'] if use_multiple_addresses - else (wrong_address if AB_right_BC_wrong else - right_address) - ) - }) - - #************************************************************************** - - return network, node_keys, node_key_to_gdf_index_dict - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_B(gdf: GeoDataFrame, - right_address: str = 'right', - wrong_address: str = 'wrong', - AB_right_BC_wrong: bool = True, - BD_with_name: bool = False, - BD_right_address: bool = False, - country_code: str = _osm.KEY_COUNTRY_DK, - use_multiple_addresses: bool = False): - - #************************************************************************** - - # create network - - network, node_keys, node_key_to_gdf_index_dict = get_network_A( - gdf=gdf, - right_address=right_address, - wrong_address=wrong_address, - country_code=country_code, - AB_right_BC_wrong=AB_right_BC_wrong, - use_multiple_addresses=use_multiple_addresses) - - # add nameless BD arc - - node_key_B = 'B' - node_key_D = 'D' - - # arc AB - - geo_BD = LineString( - [(network.nodes[node_key_B][_osm.KEY_OSMNX_X], - network.nodes[node_key_B][_osm.KEY_OSMNX_Y]), - (network.nodes[node_key_D][_osm.KEY_OSMNX_X], - network.nodes[node_key_D][_osm.KEY_OSMNX_Y])] - ) - - length_BD = network.nodes[node_key_B][_osm.KEY_OSMNX_GEOMETRY].distance( - network.nodes[node_key_D][_osm.KEY_OSMNX_GEOMETRY] - ) - - BD_dict = { - _osm.KEY_OSMNX_GEOMETRY: geo_BD, - _osm.KEY_OSMNX_LENGTH: length_BD, - #_osm.KEY_OSMNX_NAME: ( # no name for BD - # right_address if AB_right_BC_wrong else - # wrong_address) - } - - if BD_with_name: - - BD_dict[_osm.KEY_OSMNX_NAME] = ( - right_address if BD_right_address else wrong_address - ) - - network.add_edge(node_key_B, - node_key_D, - **BD_dict) - - #************************************************************************** - - return network, node_keys, node_key_to_gdf_index_dict - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def get_network_C(gdf: GeoDataFrame, - right_address: str = 'right', - wrong_address: str = 'wrong', - country_code: str = _osm.KEY_COUNTRY_DK, - CE_wrong: bool = False, - use_multiple_addresses: bool = False): - - #************************************************************************** - - # create network - - network, node_keys, node_key_to_gdf_index_dict = get_network_B( - gdf=gdf, - right_address=wrong_address, - wrong_address=wrong_address, - country_code=country_code, - AB_right_BC_wrong=True) - - # add a CE arc with the right name - - node_key_C = 'C' - node_key_E = 'E' - - # arc AB - - geo_CE = LineString( - [(network.nodes[node_key_C][_osm.KEY_OSMNX_X], - network.nodes[node_key_C][_osm.KEY_OSMNX_Y]), - (network.nodes[node_key_E][_osm.KEY_OSMNX_X], - network.nodes[node_key_E][_osm.KEY_OSMNX_Y])] - ) - - length_CE = network.nodes[node_key_C][_osm.KEY_OSMNX_GEOMETRY].distance( - network.nodes[node_key_E][_osm.KEY_OSMNX_GEOMETRY] - ) - - network.add_edge(node_key_C, - node_key_E, - **{_osm.KEY_OSMNX_GEOMETRY: geo_CE, - _osm.KEY_OSMNX_LENGTH: length_CE, - _osm.KEY_OSMNX_NAME: ( - wrong_address if CE_wrong else right_address - ) - }) - - #************************************************************************** - - return network, node_keys, node_key_to_gdf_index_dict - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def example_identify_entrances_simple_special( - create_reversed_arcs: bool = False, - revert_to_original_crs: bool = False, - focus_on_node_P_only: bool = False, - CE_wrong: bool = False): - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_C(country_code=country_code) - - network, node_keys, node_key_to_gdf_index_dict = get_network_C( - gdf=gdf, - country_code=country_code, - CE_wrong=CE_wrong) - - # create reverse arcs - - if create_reversed_arcs: - - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) - ) - - for arc_key in previous_arc_keys: - - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) - - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) - - # find out which is the closest arc - - if focus_on_node_P_only: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict={ - 'P': node_key_to_gdf_index_dict['P'] - }, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - else: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict=node_key_to_gdf_index_dict, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - # validate the outcome - - if CE_wrong: - - # no arcs with the right address, the closest arc should be selected - - assert (('B','D', 0) == nearest_arc_keys['P'] or - ('D','B', 0) == nearest_arc_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']) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def example_identify_entrances_simple_driveway( - AB_right_BC_wrong: bool = True, - create_reversed_arcs: bool = False, - revert_to_original_crs: bool = False, - focus_on_node_P_only: bool = False, - BD_with_name: bool = False, - BD_right_address: bool = False, - use_multiple_addresses: bool = False): - - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_B(country_code=country_code) - - network, node_keys, node_key_to_gdf_index_dict = get_network_B( - gdf=gdf, - country_code=country_code, - BD_with_name=BD_with_name, - BD_right_address=BD_right_address, - AB_right_BC_wrong=AB_right_BC_wrong, - use_multiple_addresses=use_multiple_addresses) - - # create reverse arcs - - if create_reversed_arcs: - - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) - ) - - for arc_key in previous_arc_keys: - - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) - - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) - - # find out which is the closest arc - - if focus_on_node_P_only: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict={ - 'P': node_key_to_gdf_index_dict['P'] - }, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - else: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict=node_key_to_gdf_index_dict, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - # validate the outcome - - if BD_with_name and not BD_right_address: - - if AB_right_BC_wrong: - - assert (('A','B', 0) == nearest_arc_keys['P'] or - ('B','A', 0) == nearest_arc_keys['P']) - - else: - - assert (('B','C', 0) == nearest_arc_keys['P'] or - ('C','B', 0) == nearest_arc_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']) - - else: - - assert (('B','D', 0) == nearest_arc_keys['P'] or - ('D','B', 0) == nearest_arc_keys['P']) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def example_identify_entrances_simple_no_driveway( - AB_right_BC_wrong: bool = True, - create_reversed_arcs: bool = False, - focus_on_node_P_only: bool = False, - revert_to_original_crs: bool = False, - use_multiple_addresses: bool = False): - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_A(country_code=country_code) - - network, node_keys, node_key_to_gdf_index_dict = get_network_A( - gdf=gdf, - country_code=country_code, - AB_right_BC_wrong=AB_right_BC_wrong, - use_multiple_addresses=use_multiple_addresses) - - # create reverse arcs - - if create_reversed_arcs: - - previous_arc_keys = list( - arc_key for arc_key in network.edges(keys=True) - ) - - for arc_key in previous_arc_keys: - - arc_dict = network.get_edge_data(u=arc_key[0], - v=arc_key[1], - key=arc_key[2]) - - network.add_edge(u_for_edge=arc_key[1], - v_for_edge=arc_key[0], - **arc_dict) - - # find out which is the closest arc - - if focus_on_node_P_only: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict={ - 'P': node_key_to_gdf_index_dict['P'] - }, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - else: - - nearest_arc_keys, _, _ = gis_utils.identify_building_entrance_arcs( - gdf=gdf, - gdf_street_column=_osm.KEY_OSM_STREET, - network=network, - node_key_to_gdf_index_dict=node_key_to_gdf_index_dict, - crs=None, - revert_to_original_crs=revert_to_original_crs) - - # validate the outcome - - if AB_right_BC_wrong: - - # the closest arc should be AB - - assert ('A','B', 0) == nearest_arc_keys['P'] - - else: - - # the closest arc should be BC - - assert ('B','C', 0) == nearest_arc_keys['P'] - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def example_identify_entrances_simple_no_driveway_closest(): - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_A(country_code=country_code) - - network, node_keys, node_key_to_gdf_index_dict = get_network_A( - gdf=gdf, - country_code=country_code) - - # find out which is the closest arc - - nearest_arc_keys, network = gis_iden.identify_edge_closest_to_node( - network, - node_keys=['P']) - - # the closest arc should be AB - - assert ('A','B', 0) in nearest_arc_keys - - assert len(nearest_arc_keys) == 1 - -#****************************************************************************** -#****************************************************************************** - -# test generating a node data container (to create nodes in a network object) - -def example_generate_node_container(include_geometry: bool = False, - include_street_column: bool = False, - use_id_as_node_key: bool = False): - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_A(country_code=country_code) - - # prepare node data - - (node_keys, - node_data_container, - _) = gis_utils.prepare_node_data_from_geodataframe( - gdf=gdf, - node_key_column=(_osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code] - if use_id_as_node_key else None), - include_columns=([_osm.KEY_OSM_STREET] - if include_street_column else None), - include_geometry=include_geometry) - - # node key to gdf index - - node_key_to_gdf_index_dict = { - node_key: ( - 'node', gdf[ - gdf[_osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code]]==node_key - ].index[0][1] if use_id_as_node_key else node_key - ) - for node_key in node_keys} - - # add nodes to new network - - network = nx.MultiDiGraph() - - network.graph['crs'] = "EPSG:4326" - - network.add_nodes_from(node_data_container) - - # verify the data - - for node_key in node_keys: - - assert network.has_node(node_key) - - gdf_index = node_key_to_gdf_index_dict[node_key] - - assert (network.nodes[node_key][_osm.KEY_OSMNX_X] == - gdf.loc[gdf_index][gis_utils.KEY_GPD_GEOMETRY].x) - - assert (network.nodes[node_key][_osm.KEY_OSMNX_Y] == - gdf.loc[gdf_index][gis_utils.KEY_GPD_GEOMETRY].y) - - if include_geometry: - - assert (network.nodes[node_key][_osm.KEY_OSMNX_GEOMETRY] == - gdf.loc[gdf_index][gis_utils.KEY_GPD_GEOMETRY]) - - if include_street_column: - - assert (network.nodes[node_key][_osm.KEY_OSM_STREET] == - gdf.loc[gdf_index][_osm.KEY_OSM_STREET]) - -#****************************************************************************** -#****************************************************************************** - -# trigger ValueError by using an index that differs from the osmnx-provided one - -def example_node_container_error(country_code: str = 'dk'): - - # get problem details - - country_code = _osm.KEY_COUNTRY_DK - - gdf = get_node_gdf_A(country_code=country_code) - - # modify index to trigger error - - gdf.set_index(_osm.KEY_OSM_BUILDING_ENTRANCE_ID[country_code], - inplace=True) - - # trigger the error - - error_triggered = False - try: - (node_keys, - node_data_container, - _) = gis_utils.prepare_node_data_from_geodataframe( - gdf=gdf) - except ValueError: - error_triggered = True - assert error_triggered - -#****************************************************************************** -#****************************************************************************** - -# test the counting of occurrences in a geodataframe - -def example_occurrences(): - - gdf = GeoDataFrame( - data={'column_A': [1, 2, 2, 3, 4], - 'column_B': [5.46, 5.46, 7, 7, 7.3], - 'column_C': ['a','a','a','a','a'], - 'column_D': ['a','b','c','d','e'], - 'column_E': ['hello','goodbye',None,'hello',None]}, - geometry=[Point(0,1), Point(4,5), Point(2,3), Point(4,6), Point(7,2)] - ) - - solution = { - 'column_A': {1: 1, 2: 2, 3: 1, 4: 1}, - 'column_B': {5.46: 2, 7: 2, 7.3: 1}, - 'column_C': {'a': 5}, - 'column_D': {'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1}, - 'column_E': {'hello': 2, 'goodbye': 1, None: 2} - } - - # all elements - - for column in solution: - - assert gis_utils.count_ocurrences(gdf, column) == solution[column] - - # specific ones - - assert gis_utils.count_ocurrences(gdf, 'column_A', [1, 2]) == {1: 1, 2: 2} - - assert gis_utils.count_ocurrences(gdf, 'column_A', [10]) == {10: 0} - - assert ( - gis_utils.count_ocurrences(gdf, 'column_B', [7, 7.3]) == {7: 2, 7.3: 1} - ) - - assert gis_utils.count_ocurrences(gdf, 'column_C', ['a']) == {'a': 5} - - assert gis_utils.count_ocurrences(gdf, 'column_C', ['b']) == {'b': 0} - - assert gis_utils.count_ocurrences(gdf, 'column_D', ['b']) == {'b': 1} - - assert ( - gis_utils.count_ocurrences(gdf, 'column_E', ['hello']) == {'hello': 2} - ) - - assert ( - gis_utils.count_ocurrences(gdf, 'column_E', [None]) == {None: 2} - ) - -#****************************************************************************** -#****************************************************************************** - -# test creating osmnx-like geodataframes for nodes - -def example_create_osmnx_gdf(): - - # method for basic gdf compliance verification - - def verify_osmnx_gdf(gdf: GeoDataFrame, - extra_column_names: list = None): - - # index format - - assert type(gdf.index) == MultiIndex - - assert len(gdf.index.names) == 2 - - assert _osm.KEY_OSMNX_OSMID in gdf.index.names - - assert _osm.KEY_OSMNX_ELEMENT_TYPE in gdf.index.names - - # geometry column - - assert _osm.KEY_OSMNX_GEOMETRY in gdf.columns - - # extra columns - - if type(extra_column_names) != type(None): - - for extra_column_name in extra_column_names: - - assert extra_column_name in gdf.columns - - # the elements - - for index in gdf.index: - - # must be a node - - assert 'node' in index[0] # first position of multi-index - - # must have point geometry - - assert type(gdf.loc[index][_osm.KEY_OSMNX_GEOMETRY]) == Point - - # test gdf - - gdf_example = gis_utils.GeoDataFrame( - { - _osm.KEY_OSMNX_GEOMETRY: [Point(152, 546)], - }, - index=MultiIndex.from_tuples([('node', 'badfnbjiadbnd')], - names=[_osm.KEY_OSMNX_ELEMENT_TYPE, - _osm.KEY_OSMNX_OSMID]) - ) - - verify_osmnx_gdf(gdf_example) - - # single node - - _latitude = 23 - _longitude = -12 - - gdf_single = gis_utils.create_node_geodataframe( - longitudes=(_longitude,), - latitudes=(_latitude,) - ) - - verify_osmnx_gdf(gdf_single) - - # single node, using a specific key - - mynodekey = 'mynodekeyishere' - - gdf_single = gis_utils.create_node_geodataframe( - longitudes=(_longitude,), - latitudes=(_latitude,), - osmids=(mynodekey,) - ) - - verify_osmnx_gdf(gdf_single) - - assert gdf_single.index[0][1] == mynodekey - - # single node, with extra columns - - gdf_single = gis_utils.create_node_geodataframe( - longitudes=(_longitude,), - latitudes=(_latitude,), - osmids=(mynodekey,), - long=(_longitude,), - lat=(_latitude,) - ) - - verify_osmnx_gdf(gdf_single, - extra_column_names=('long','lat')) - - assert gdf_single.index[0][1] == mynodekey - assert gdf_single.iloc[0]['long'] == _longitude - assert gdf_single.iloc[0]['lat'] == _latitude - - #************************************************************************** - - # multiple nodes - - _latitudes = (23,45,73) - _longitudes = (-12,33,24) - - gdf_multi = gis_utils.create_node_geodataframe( - longitudes=_longitudes, - latitudes=_latitudes - ) - - verify_osmnx_gdf(gdf_multi) - - # multiple nodes and specific keys - - _osmids = (54,'a4h4',44.323) - - gdf_multi = gis_utils.create_node_geodataframe( - longitudes=_longitudes, - latitudes=_latitudes, - osmids=_osmids - ) - - verify_osmnx_gdf(gdf_multi) - - for i in range(len(gdf_multi)): - - assert gdf_multi.index[i][1] == _osmids[i] - - # multiple nodes and extra columns - - gdf_multi = gis_utils.create_node_geodataframe( - longitudes=_longitudes, - latitudes=_latitudes, - osmids=_osmids, - long=_longitudes, - lat=_latitudes - ) - - verify_osmnx_gdf(gdf_multi, - extra_column_names=('long','lat')) - - for i in range(len(gdf_multi)): - - assert gdf_multi.index[i][1] == _osmids[i] - assert gdf_multi.iloc[i]['long'] == _longitudes[i] - assert gdf_multi.iloc[i]['lat'] == _latitudes[i] - - #************************************************************************** - - # trigger errors - - # mismatched longitudes and latitudes - - error_triggered = False - try: - _ = gis_utils.create_node_geodataframe( - longitudes=(_longitude,528), - latitudes=(_latitude,) - ) - except ValueError: - error_triggered = True - assert error_triggered - - # mismatched longitudes/latitudes and osmids - - error_triggered = False - try: - _ = gis_utils.create_node_geodataframe( - longitudes=(_longitude,528), - latitudes=(_latitude,92), - osmids=(59,482,135) - ) - except ValueError: - error_triggered = True - assert error_triggered - -#****************************************************************************** -#****************************************************************************** - -# TODO: test plotting using cached data - -#****************************************************************************** -#****************************************************************************** - -# test writing a GeoDataFrame with containers - -def example_io_geodataframe(preserve_original_gdf: bool = True, - identify_columns: bool = False, - file_extension: str = '.gpkg'): - - #************************************************************************** - #************************************************************************** - - filename = 'tests/mygdffile'+file_extension - - # print('bing') - # print('preserve_original_gdf:'+str(preserve_original_gdf)) - # print('identify_columns:'+str(identify_columns)) - # print('file_extension:'+str(file_extension)) - - def verify_gdf_conformity(gdf, new_gdf, preserve_original_gdf): - - # verify conformity - # print(gdf) - # print(new_gdf) - - # for each column in the original gdf - - for column in gdf.columns: - - # assert that the column exists or that it is a merged 1 (no need) - - assert (column in new_gdf.columns or gis_utils.RKW_GPKG == column) - - # packed column - - if gis_utils.RKW_GPKG == column: - - # duplicates - - # if the original was preserved, there should be no packed col. - # hence, it cannot have been preserved - - assert not preserve_original_gdf - - # for each key in the packed column - # print(gdf.columns) - # print(gdf[column]) - - for index in gdf.index: - - contents_dict = literal_eval(gdf.loc[(index,column)]) - - for new_gdf_column in contents_dict.keys(): - - assert new_gdf_column in new_gdf.columns - # print(new_gdf_column) - # print("......................................................") - # #print(index) - # print(gdf[column].dtype) - # print(new_gdf[new_gdf_column].dtype) - # print(contents_dict[new_gdf_column]) - # print(new_gdf.loc[(index, new_gdf_column)]) - # print(type(contents_dict[new_gdf_column])) - # print(type(new_gdf.loc[(index, new_gdf_column)])) - # print(repr(contents_dict[new_gdf_column])) - # print(repr(new_gdf.loc[(index, new_gdf_column)])) - - if new_gdf_column in special_columns: - - # the contents are containers: use literal_eval - - assert repr( - literal_eval( - contents_dict[new_gdf_column] - ) - ) == repr( - new_gdf.loc[(index, new_gdf_column)] - ) - - else: # the contents are not containers - - # direct comparison - # TODO: reach this statement - assert repr(contents_dict[new_gdf_column] - ) == repr( - new_gdf.loc[(index, new_gdf_column)] - ) - - continue - - #****************************************************************** - #****************************************************************** - - # non-packed column - - for index in gdf.index: - - if preserve_original_gdf: - - # the original gdf has been preserved - - # print("......................................................") - # print(gdf[column].dtype) - # print(new_gdf[column].dtype) - # print(gdf.loc[(index, column)]) - # print(new_gdf.loc[(index, column)]) - # print(type(gdf.loc[(index, column)])) - # print(type(new_gdf.loc[(index, column)])) - # print(repr(gdf.loc[(index, column)])) - # print(repr(new_gdf.loc[(index, column)])) - - # the types should match - - assert type( - gdf.loc[(index, column)] - ) == type( - new_gdf.loc[(index, column)] - ) - - # sets require special behaviour - - if (type(gdf.loc[(index, column)]) == set or - column == gis_utils.KEY_GPD_GEOMETRY): - - # sets are non-ordered: - # repr() may reveal different results - - assert ( - gdf.loc[(index, column)] == - new_gdf.loc[(index, column)] - ) - - else: # standard - - assert repr( - gdf.loc[(index, column)] - ) == repr( - new_gdf.loc[(index, column)] - ) - - else: # the original gdf has not been preserved - - # print("......................................................") - # print(gdf.columns) - # print(gdf[column].dtype) - # print(new_gdf[column].dtype) - # print(gdf[column].loc[index]) - # print(new_gdf[column].loc[index]) - # print(type(gdf[column].loc[index])) - # print(type(new_gdf[column].loc[index])) - # print(repr(gdf[column].loc[index])) - # print(repr(new_gdf[column].loc[index])) - - if column == gis_utils.KEY_GPD_GEOMETRY: - - # assert ( - # gdf[column].loc[index] == - # new_gdf[column].loc[index] - # ) - - assert ( - gdf.loc[(index, column)] == - new_gdf.loc[(index, column)] - ) - - elif column in special_columns: - - assert repr( - literal_eval(gdf.loc[(index, column)]) - ) == repr( - new_gdf.loc[(index, column)] - ) - - else: - - assert repr( - gdf.loc[(index, column)] - ) == repr( - new_gdf.loc[(index, column)] - ) - - #************************************************************************** - #************************************************************************** - - # TODO: test methods without specifying the columns - - #************************************************************************** - #************************************************************************** - - # gdf object with simple index, undeclared - - gdf = GeoDataFrame( - {'id': [1, 2, 3], - 'mymymy': [None,None,1.23], - 'another_id': [53.4,54.4,55.4], - 'not_another_id': ['you','they','us'], - 'column_of_lists': [list([0,1,2]), - list([3,4,5]), - list([6,7,8])], - 'column_of_tuples': [tuple([-1,-2,-3]), - tuple([-4,-5,-6]), - tuple([-7,-8,-9])], - 'column_of_sets': [set([-1,-2,-3]), - set([-4,-5,-6]), - set([-7,-8,-9])], - 'column_of_dicts': [{1:34,6:'a',5:46.32}, - {'a':575,4:[],3:(2,3)}, - {(4,5):3,4:{2:5},3:4}], - 'column_of_strs': ["set([-1,-2,-3])", - "set([-4,-5,-6])", - "set([-7,-8,-9])"], - 'another_id2': ['hello',53.4,None], # requires special handling - 'another_id3': [53.4,'hello',None], # requires special handling - 'yet_another_id': [None,None,None], # requires special handling - }, - geometry=[LineString([(3, 2), (7, 7)]), - LineString([(3, 7), (7, 2)]), - LineString([(6, 2), (6, 6)])] - ) - - # identify the columns that require special treatment - - if identify_columns: - - # find the columns automatically - - special_columns = None # TODO: reach this statement - - else: - - special_columns = ( - 'column_of_lists', - 'column_of_tuples', - 'column_of_sets', - 'column_of_dicts', - #'column_of_strs' # can be omitted - 'another_id2', - 'another_id3', - 'yet_another_id' - ) - - # find the columns automatically - - set_packable_columns = gis_utils.find_gpkg_packable_columns(gdf) - - # make sure the columns can be identified - - for packable_column in set_packable_columns: - - assert packable_column in special_columns - - # write file - - gis_utils.write_gdf_file( - gdf=gdf, - filename=filename, - columns_to_pack=special_columns, - preserve_original=preserve_original_gdf - ) - - new_gdf = gis_utils.read_gdf_file( - filename=filename, - packed_columns=special_columns) - - # verify conformity - - verify_gdf_conformity(gdf, new_gdf, preserve_original_gdf) - - #************************************************************************** - #************************************************************************** - - # gdf object with simple index, declared - - gdf = GeoDataFrame( - {'id': [1, 2, 3], - 'column_of_lists': [list([0,1,2]), - list([3,4,5]), - list([6,7,8])], - 'column_of_tuples': [tuple([-1,-2,-3]), - tuple([-4,-5,-6]), - tuple([-7,-8,-9])], - 'column_of_sets': [set([-1,-2,-3]), - set([-4,-5,-6]), - set([-7,-8,-9])], - 'column_of_dicts': [{1:34,6:'a',5:46.32}, - {'a':575,4:[],3:(2,3)}, - {(4,5):3,4:{2:5},3:4}], - 'column_of_strs': ["set([-1,-2,-3])", - "set([-4,-5,-6])", - "set([-7,-8,-9])"] - }, - geometry=[LineString([(3, 2), (7, 7)]), - LineString([(3, 7), (7, 2)]), - LineString([(6, 2), (6, 6)])], - index=['a','b','c'], # index is declared - ) - - # identify the columns that require special treatment - - if identify_columns: - - # find the columns automatically - - special_columns = None # TODO: reach this statement - - else: - - special_columns = ( - 'column_of_lists', - 'column_of_tuples', - 'column_of_sets', - 'column_of_dicts', - 'column_of_strs' - ) - - # find the columns automatically - - set_packable_columns = gis_utils.find_gpkg_packable_columns(gdf) - - # make sure the columns can be identified - - for packable_column in set_packable_columns: - - assert packable_column in special_columns - - # write file - - gis_utils.write_gdf_file( - gdf=gdf, - filename=filename, - columns_to_pack=special_columns, - preserve_original=preserve_original_gdf - ) - - new_gdf = gis_utils.read_gdf_file( - filename=filename, - packed_columns=special_columns, - index='index') # index has to be specified - - # verify conformity - - verify_gdf_conformity(gdf, new_gdf, preserve_original_gdf) - - #************************************************************************** - #************************************************************************** - - # gdf object with multi-index, declared - - gdf = gis_utils.GeoDataFrame( - { - 'other_column': [1,2,3], - 'column_a': ['nadbnppadfb','agasdgnp','adfgdn'], - 'column_b': [12517.4247,0.54673,0.3723], - 'column_c': [(1,2,3,4),(5,6,7,8),(44,1247)], - 'column_d': [{'beans':'cheese','lollipops':'dentist'},{},{1:3}], - 'column_e': [[1,2,3],[4.5,4.6,4.7],[9.0,10.0,11.0]], - 'column_f': [{4,5,6},{5.64435,0.7545,1.4634},{'a','b','c'}], - 'geometry': [Point(12, 55), Point(2,4), Point(3,6)], - }, - index=MultiIndex.from_tuples([('a', 124), - ('b', 754), - ('c', 234)], - names=['index1', 'index2']) - ) - - # identify the columns that require special treatment - - if identify_columns: - - # find the columns automatically - - special_columns = None # TODO: reach this statement - - else: - - special_columns = ( - 'column_c', - 'column_d', - 'column_e', - 'column_f' - ) - - # find the columns automatically - - set_packable_columns = gis_utils.find_gpkg_packable_columns(gdf) - - # make sure the columns can be identified - - for packable_column in set_packable_columns: - - assert packable_column in special_columns - - # write file - - gis_utils.write_gdf_file( - gdf=gdf, - filename=filename, - columns_to_pack=special_columns, - preserve_original=preserve_original_gdf - ) - - new_gdf = gis_utils.read_gdf_file( - filename=filename, - packed_columns=special_columns, - index=['index1', 'index2']) - - # verify conformity - - verify_gdf_conformity(gdf, new_gdf, preserve_original_gdf) - - #************************************************************************** - #************************************************************************** - - # gdf with column names matching in lower case (not good for .gpkg files) - - gdf = GeoDataFrame( - {'id': [1, 2, 3], - 'mymymy': [901.1,53.4,None], - 'another_id': [53.4,54.4,55.4], - 'not_another_id': ['you','they','us'], - 'abc': [list([0,1,2]), - list([3,4,5]), - list([6,7,8])], - 'ABC': [tuple([-1,-2,-3]), - tuple([-4,-5,-6]), - tuple([-7,-8,-9])], - 'Abc': ['here', - 'there', - 'nowhere'], - 'aBc': [(1,2,3,4), - [5,6,7,8], - {9,10,11,12}], - 'aBC': [53.643, - {3:6,7:'goodbye'}, - None], - 'ABc': [None, - None, - None], - 'mymymy2': ['hello',53.4,None], # requires special handling - 'yet_another_id': [None,None,None], # requires special handling - }, - geometry=[LineString([(3, 2), (7, 7)]), - LineString([(3, 7), (7, 2)]), - LineString([(6, 2), (6, 6)])] - ) - - # identify the columns that require special treatment - - if identify_columns: - - # find the columns automatically - - special_columns = None # TODO: reach this statement - - else: - - special_columns = ( - 'abc', - 'ABC', - 'Abc', # no containers but has the same lowercase name as others - 'aBc', - 'aBC', - 'ABc', - # special cases - 'mymymy2', - 'yet_another_id' - ) - - # find the columns automatically - - set_packable_columns = gis_utils.find_gpkg_packable_columns(gdf) - - # make sure the columns can be identified - - for packable_column in set_packable_columns: - - assert packable_column in special_columns - - # write file - - gis_utils.write_gdf_file( - gdf=gdf, - filename=filename, - columns_to_pack=special_columns, - preserve_original=preserve_original_gdf - ) - - new_gdf = gis_utils.read_gdf_file( - filename=filename, - packed_columns=special_columns) - - # verify conformity - - verify_gdf_conformity(gdf, new_gdf, preserve_original_gdf) - - #************************************************************************** - #************************************************************************** - - # TODO: force the methods to throw errors with non-primitive types - - #************************************************************************** - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def examples_gpkg_write_errors(filename_gpkg: str = 'test.gpkg'): - - #************************************************************************** - #************************************************************************** - - type_status = { - int: True, - str: True, - float: True, - bytes: False, - dict: True, # works but comes out incorrectly - set: False, - tuple : False, - list : False, - type(None): True # works but comes out incorrectly - } - - for a_type, a_status in type_status.items(): - - if a_type == int: - - data = [1,2] - - elif a_type == str: - - data = ['hello','goodbye'] - - elif a_type == float: - - data = [3.4,6.7] - - elif a_type == bytes: - - data = [b'\x04\x00',b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'] - - elif a_type == dict: - - data = [{0:1},{'a':53.46}] - - elif a_type == set: - - data = [{0,1},{'a',53.46}] - - elif a_type == tuple: - - data = [(0,1),('a',53.46)] - - elif a_type == list: - - data = [list((0,1)),list(('a',53.46))] - - elif a_type == type(None): - - data = [None, None] - - #********************************************************************** - #********************************************************************** - - # create gdf - - gdf = GeoDataFrame( - { - 'data': data, - 'geometry': [Point(1, 2), Point(3,4)], - }, - index=['a','b'] - ) - - #********************************************************************** - #********************************************************************** - - # verify the status - - if a_status: - - # compatible: no errors are expected - - gdf.to_file(filename_gpkg) - - else: # incompatible: errors are expected - - error_triggered = False - try: - gdf.to_file(filename_gpkg) - except Exception: - error_triggered = True - assert error_triggered - - #************************************************************************** - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def example_discrete_plot_gdf(): - - #************************************************************************** - - G = ox.graph_from_point( - (55.71654,9.11728), - network_type='drive', - 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 - - column = 'discrete_category_column' - - offset = random.randint(0,int(1e3)) - - number_options = 10 - - category = { - idx: random.randint(0,number_options-1)+offset - for idx in gdf.index - } - - set_categories = set(category.values()) - - category_to_label = { - cat: 'label for '+str(cat) - for cat in set_categories - } - - # create column - - gdf[column] = Series(data=category, index=gdf.index) - - #************************************************************************** - - gis_utils.plot_discrete_attributes( - gdf, - column=column, - category_to_label=category_to_label - ) - - #************************************************************************** - -#****************************************************************************** -#****************************************************************************** - -def examples_convert_edge_paths(): - - # 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 - - 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) - -#****************************************************************************** -#****************************************************************************** - -def example_get_directed(network: nx.MultiDiGraph): - - # 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 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.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: - # the data is different - continue - 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) - -#****************************************************************************** -#****************************************************************************** diff --git a/tests/test_all.py b/tests/test_all.py index 7e75605bed5750cc796793ee7402c3b70c6163fe..1a028d48ab290b807c39c04046b706646a926af9 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -12,7 +12,6 @@ from examples_esipp_network import examples as examples_esipp_network from examples_esipp_problem import examples as examples_esipp_problem from examples_esipp_resource import examples as examples_esipp_resource from examples_esipp import examples as examples_esipp -from examples_gis import examples as examples_gis from examples_signal import examples as examples_signal #****************************************************************************** @@ -40,9 +39,6 @@ def test_suite(): test_examples_esipp = True # test_examples_esipp = False - # test_examples_gis = True - test_examples_gis = False - test_examples_signal = True # test_examples_signal = False @@ -230,18 +226,6 @@ def test_suite(): #************************************************************************** - # gis - - if test_examples_gis: - - print('\'gis\': testing about to start...') - - examples_gis(seed_number=None) - - print('\'gis\': testing complete.') - - #************************************************************************** - # signal if test_examples_signal: 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 90b5637dd82ee3d7cfba1f3a71b3768606b84c57..04862bac0abd7ed1996568af3ba5106c0c952de0 100644 --- a/tests/test_gis_identify.py +++ b/tests/test_gis_identify.py @@ -24,12 +24,13 @@ class TestGisIdentify: # ************************************************************************* # ************************************************************************* - def path_validator( + def straight_path_validator( self, network: nx.MultiDiGraph, path: list, excluded_nodes: list, - consider_reversed_edges: bool): + consider_reversed_edges: bool, + ignore_self_loops: bool): # find out the unique nodes set_nodes = set(path) # at least three nodes @@ -48,12 +49,181 @@ class TestGisIdentify: # no excluded nodes in the intermediate positions for node in excluded_nodes: assert node not in path[1:-1] + # intermediate nodes can only have two neighbours + for node_key in path[1:-1]: + assert len( + set( + gis_iden.neighbours( + network, + node_key, + ignore_self_loops=True + ) + ) + ) == 2 + # end nodes need to have at least one neighbour, except in loops, + # wherein they need to have two neighbours + if path[0] == path[-1]: + # end nodes in loops need to have at least two neighbours + assert len( + set( + gis_iden.neighbours( + network, + path[0], + ignore_self_loops=True + ) + ) + ) >= 2 + else: + # end nodes need to have at least one neighbour + assert len( + set( + gis_iden.neighbours( + network, + path[0], + ignore_self_loops=True + ) + ) + ) >= 1 + assert len( + set( + gis_iden.neighbours( + network, + path[-1], + ignore_self_loops=True + ) + ) + ) >= 1 + # if ignore_self_loops=False, intermediate nodes cannot have self-loops + if not ignore_self_loops: + for node in path[1:-1]: + assert not network.has_edge(node, node) + # if path[0] == path[-1]: + # # loop, applies to all nodes + # for node in path: + # assert not network.has_edge(node, node) + # else: + # # not a loop, applies only to intermediate nodes + # for node in path[1:-1]: + # assert not network.has_edge(node, node) # make sure it qualifies as a path assert gis_iden.is_node_path( network, path, consider_reversed_edges=consider_reversed_edges ) + # make sure it is a straight path + assert gis_iden.is_path_straight( + network, + path, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + + + # ************************************************************************* + # ************************************************************************* + + def test_finding_simplifiable_paths_osmnx(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 + ) + + # ********************************************************************* + # ********************************************************************* + + consider_reversed_edges = False + ignore_self_loops = False + + # paths + paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + # verify the paths + for path in paths: + self.straight_path_validator( + network, + path, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + + # ********************************************************************* + # ********************************************************************* + + consider_reversed_edges = False + ignore_self_loops = True + + # paths + paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + # verify the paths + for path in paths: + self.straight_path_validator( + network, + path, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops) + + # ********************************************************************* + # ********************************************************************* + + consider_reversed_edges = True + ignore_self_loops = False + + # paths + paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + # verify the paths + for path in paths: + self.straight_path_validator( + network, + path, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops) + + # ********************************************************************* + # ********************************************************************* + + consider_reversed_edges = True + ignore_self_loops = True + + # paths + paths = gis_iden.find_simplifiable_paths( + network, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + # verify the paths + for path in paths: + self.straight_path_validator( + network, + path, + excluded_nodes=[], + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops) + + # ********************************************************************* + # ********************************************************************* # ************************************************************************* # ************************************************************************* @@ -79,11 +249,12 @@ class TestGisIdentify: # test path validator with non-path error_raised = False try: - assert not self.path_validator( + assert not self.straight_path_validator( network, [1, 1, 1], excluded_nodes, - False + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) except AssertionError: error_raised = True @@ -166,8 +337,7 @@ class TestGisIdentify: # add single node network.add_node(0) - - path = gis_iden._find_path_direction_insensitive(network, [], 0) + path = gis_iden._find_path_direction_insensitive(network, [], 0, False) assert type(path) == list assert len(path) == 1 assert repr(path) == repr([0]) @@ -587,11 +757,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -611,11 +782,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, - excluded_nodes, - consider_reversed_edges=consider_reversed_edges + excluded_nodes, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -651,11 +823,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -675,11 +848,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -705,11 +879,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -729,11 +904,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -769,11 +945,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -793,11 +970,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -840,11 +1018,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -864,11 +1043,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -904,11 +1084,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -928,11 +1109,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -958,11 +1140,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -982,11 +1165,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1022,11 +1206,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1046,11 +1231,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1093,11 +1279,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1117,11 +1304,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1141,11 +1329,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1165,11 +1354,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1189,11 +1379,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1219,11 +1410,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1240,14 +1432,15 @@ class TestGisIdentify: 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 + 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1263,15 +1456,16 @@ class TestGisIdentify: consider_reversed_edges=consider_reversed_edges, ignore_self_loops=ignore_self_loops ) - true_straight_paths = [[1, 2, 0, 1], [1, 0, 2, 1]] + 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1291,11 +1485,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1315,11 +1510,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1448,11 +1644,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1472,11 +1669,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1512,11 +1710,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1536,11 +1735,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1669,11 +1869,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1693,11 +1894,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1733,11 +1935,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1757,11 +1960,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1804,11 +2008,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1834,11 +2039,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1859,11 +2065,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1906,11 +2113,12 @@ class TestGisIdentify: assert len(straight_paths) == len(true_straight_paths) for straight_path in straight_paths: assert straight_path in true_straight_paths - self.path_validator( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -1938,11 +2146,12 @@ class TestGisIdentify: 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( + self.straight_path_validator( network, straight_path, excluded_nodes, - consider_reversed_edges=consider_reversed_edges + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops ) # ********************************************************************* @@ -2473,91 +2682,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): - - 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): # ********************************************************************* @@ -2575,7 +2699,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( @@ -2585,6 +2743,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 @@ -2595,7 +2787,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) + # ********************************************************************* # ********************************************************************* @@ -2608,6 +2831,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) # ********************************************************************* # ********************************************************************* @@ -2621,6 +2875,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 @@ -2631,6 +2919,37 @@ class TestGisIdentify: 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) # ********************************************************************* # ********************************************************************* @@ -2645,6 +2964,37 @@ class TestGisIdentify: 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) # ********************************************************************* # ********************************************************************* @@ -2661,9 +3011,71 @@ class TestGisIdentify: 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 + + 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)], + use_start_point_equidistant=True + ) + + assert repr(close_to_start) == repr([0]) + assert repr(close_to_end) == repr([]) + + # ********************************************************************* + # ********************************************************************* + + # 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)], + use_start_point_equidistant=False + ) + + assert repr(close_to_start) == repr([]) + assert repr(close_to_end) == repr([0]) # ************************************************************************* # ************************************************************************* @@ -3989,6 +4401,51 @@ class TestGisIdentify: # ********************************************************************* # ********************************************************************* + + # ************************************************************************* + # ************************************************************************* + + 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, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops) + + # ********************************************************************* + # ********************************************************************* # ************************************************************************* # ************************************************************************* @@ -3998,51 +4455,31 @@ class TestGisIdentify: # ********************************************************************* # create a network - network = nx.MultiDiGraph() - network.graph['crs'] = "EPSG:4326" - # network.graph['crs'] = 'init' - # add edges and nodes - number_edges = random.randint(3,10) - edge_keys = [ (random.randint(0,number_edges),random.randint(0,number_edges)) for edge_index in range(number_edges)] - network.add_edges_from(edge_keys) - # add attributes to the nodes used in the edges - for node_key in network.nodes(): - _xy = (random.random(), random.random()) - - network.add_node(node_key, x=_xy[0], y=_xy[0]) - + network.add_node(node_key, x=_xy[0], y=_xy[0]) # add new (unconnected) nodes - number_new_nodes = random.randint(3,5) - unconnected_node_keys = [] - for node_index in range(number_new_nodes): - new_node_key = uuid.uuid4() - _xy = (random.random(), random.random()) - network.add_node(new_node_key, x=_xy[0], y=_xy[0]) - unconnected_node_keys.append(new_node_key) - #************************************************************************** + # ********************************************************************* # find the nearest nodes using the osmnx method - nearest_node_keys = gis_iden.nearest_nodes( network, [network.nodes[node_key]['x'] @@ -4052,60 +4489,40 @@ class TestGisIdentify: ) # assert that the test is meaningful - assert len(nearest_node_keys) != 0 - assert len(nearest_node_keys) == len(unconnected_node_keys) # assert that the nodes are the same - for i, node_key in enumerate(unconnected_node_keys): - assert node_key == nearest_node_keys[i] - #************************************************************************** + # ********************************************************************* # find the nodes nearest to select nodes excluding themselves - nearest_node_keys = gis_iden.nearest_nodes_other_than_themselves( network, unconnected_node_keys) # assert that the test is meaningful - assert len(nearest_node_keys) != 0 - assert len(nearest_node_keys) == len(unconnected_node_keys) - all_node_keys = list(network.nodes()) - list_all_geos = [] - for node_key in all_node_keys: - list_all_geos.append(Point( - (network.nodes[node_key]['x'], - network.nodes[node_key]['y']) + (network.nodes[node_key]['x'],network.nodes[node_key]['y']) ) ) - all_node_geos = { node_key: list_all_geos[i] for i, node_key in enumerate(all_node_keys) } - # for each node - for i, node_key in enumerate(unconnected_node_keys): - # assert that they are not the same - assert node_key != nearest_node_keys[i] - # verify that the distance between is the lowest among all - unconnected_node_geo = all_node_geos[node_key] - all_distances = [ unconnected_node_geo.distance( all_node_geos[other_node_key] @@ -4113,15 +4530,71 @@ class TestGisIdentify: for other_node_key in all_node_keys if other_node_key != node_key ] - actual_distance = unconnected_node_geo.distance( all_node_geos[nearest_node_keys[i]] ) - assert isclose( 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 diff --git a/tests/test_gis_modify.py b/tests/test_gis_modify.py index 90a7f5b94b0567c7ca1ff413f7697ecccd21a233..a6b718194da09e93fd4ff11bd820ca9977bc38ee 100644 --- a/tests/test_gis_modify.py +++ b/tests/test_gis_modify.py @@ -146,113 +146,80 @@ 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) + # define the settings + ignore_self_loops = False + consider_reversed_edges = True + + # find paths + paths = gis_iden.find_simplifiable_paths( + _net, + excluded_nodes=[], + ignore_self_loops=ignore_self_loops, + consider_reversed_edges=consider_reversed_edges + ) + + # verify the paths + for path in paths: + gis_iden.is_path_straight( + _net, + path, + consider_reversed_edges=consider_reversed_edges, + ignore_self_loops=ignore_self_loops + ) + + # 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 +250,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 +268,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 +323,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 +331,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 +342,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 +393,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 +415,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 +462,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 +486,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 +536,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 +560,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 +615,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 +632,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 +643,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]) # ********************************************************************* # ********************************************************************* @@ -834,7 +801,7 @@ class TestGisModify: # ************************************************************************* # ************************************************************************* - def test_remove_longer_arcs(self): + def test_remove_longer_edges(self): # simple example network = nx.MultiDiGraph() @@ -847,13 +814,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 +833,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 +856,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 +880,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 +905,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 +922,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 +938,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 +1030,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 +1083,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 +1094,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 +1125,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 + abs_tol=0.925 # 0.29327665321937957, 0.9249539991553775 ) - # 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 +1183,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 +1194,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 +1215,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 +1226,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 +1247,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 +1258,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 +1269,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 +1280,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 +1294,50 @@ 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]: + # exclude new paths with self-loops + no_self_loops = True + for edge_in_path in new_path: + if edge_in_path[0] == edge_in_path[1]: + no_self_loops = False + break + if not no_self_loops: + # there are self loops, skip path + continue + 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 +1348,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 +1386,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 +1500,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 +1558,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 +1618,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 +1679,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 +1693,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 +1882,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 +2233,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 +2276,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 +2331,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 +2401,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 +2477,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 +2488,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 +2533,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 +2564,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 +2636,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 +2646,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 +3195,83 @@ 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 + + # ************************************************************************* + # ************************************************************************* + def test_modify_roundabouts_unprojected(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) + # insert fake roundabout + roundabouts.append([0, 1, 2]) + # modify the roundabouts + node_replacements = gis_mod.transform_roundabouts_into_crossroads( + network, + roundabouts + ) + # make sure the fake roundabout was detected + assert type(node_replacements[-1]) == type(None) + # TODO: test the conversion itself + + # ************************************************************************* + # ************************************************************************* + + def test_modify_roundabouts_projected(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 + ) + # project the network + network = ox.project_graph(G=network) + # find all roundabouts + roundabouts = gis_iden.find_roundabouts(network) + # confirm they are roundabouts + for roundabout in roundabouts: + assert gis_iden.is_roundabout(network, roundabout) + # insert fake roundabout + roundabouts.append([0, 1, 2]) + # modify the roundabouts + node_replacements = gis_mod.transform_roundabouts_into_crossroads( + network, + roundabouts + ) + # make sure the fake roundabout was detected + assert type(node_replacements[-1]) == type(None) + # TODO: test the conversion itself + # ***************************************************************************** -# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file diff --git a/tests/test_gis_utils.py b/tests/test_gis_utils.py index 961100c3e134649b061a4413d100f30272111290..d1d276192ccffe6929e2c25eac3bb20263be4780 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'] # ************************************************************************* # ************************************************************************* @@ -942,18 +942,16 @@ class TestGisUtils: network, node_keys, node_key_to_gdf_index_dict = self.get_network_A( gdf=gdf, country_code=country_code) - - # find out which is the closest arc - - nearest_arc_keys, network = gis_iden.identify_edge_closest_to_node( + # find out which is the closest edge + 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 +1057,8 @@ class TestGisUtils: error_triggered = True assert error_triggered - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** # test the counting of occurrences in a geodataframe @@ -1113,8 +1111,8 @@ class TestGisUtils: gis_utils.count_ocurrences(gdf, 'column_E', [None]) == {None: 2} ) - #****************************************************************************** - #****************************************************************************** + # ***************************************************************************** + # ***************************************************************************** # test creating osmnx-like geodataframes for nodes @@ -1893,8 +1891,9 @@ class TestGisUtils: # ************************************************************************* def test_discrete_plot_gdf(self): - - #************************************************************************** + + # ********************************************************************* + # ********************************************************************* G = ox.graph_from_point( (55.71654,9.11728), @@ -1902,12 +1901,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 +1934,8 @@ class TestGisUtils: gdf[column] = Series(data=category, index=gdf.index) - #************************************************************************** + # ********************************************************************* + # ********************************************************************* gis_utils.plot_discrete_attributes( gdf, @@ -1943,66 +1945,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 +2071,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.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 +2103,41 @@ 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) # ************************************************************************* # ************************************************************************* + + def test_simplifying_graph(self): + # get a network + network = ox.graph_from_point( + (55.71654,9.11728), + network_type='drive', + custom_filter='["highway"~"residential|tertiary|unclassified|service"]', + truncate_by_edge=True + ) + # protect some nodes + number_nodes_protected = 4 + node_keys = tuple(network.nodes()) + protected_nodes = [ + node_keys[random.randint(0,len(node_keys)-1)] + for i in range(number_nodes_protected) + ] + # try simplifying it + gis_utils.simplify_network(network, protected_nodes) + # TODO: verify the changes + # confirm that the protected nodes still exist and have the same attr. + # for node_key in protected_nodes: + # assert network.has_node(node_key) + # TODO: check if [335762579, 335762585, 1785975921, 360252989, 335762632, 335762579] is a path + + # ************************************************************************* + # ************************************************************************* -# ************************************************************************* -# ************************************************************************* +# ***************************************************************************** +# ***************************************************************************** \ No newline at end of file