Source code for cityImage.clean

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=RuntimeWarning)

import networkx as nx
import pandas as pd
import numpy as np
import geopandas as gpd

from shapely.geometry import Point, LineString, MultiPoint, MultiLineString
from shapely.ops import unary_union, linemerge
pd.set_option("display.precision", 3)

from .graph import graph_fromGDF, nodes_degree
from .load import obtain_nodes_gdf, join_nodes_edges_by_coordinates, reset_index_graph_gdfs
from .utilities import center_line, split_line_at_MultiPoint

[docs]def duplicate_nodes(nodes_gdf, edges_gdf, nodeID = 'nodeID'): """ The function checks the existencce of duplicate nodes through the network, on the basis of geometry. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ nodes_gdf = nodes_gdf.copy() nodes_gdf.index = nodes_gdf[nodeID] # detecting duplicate geometries nodes_gdf['wkt'] = nodes_gdf['geometry'].apply(lambda row: row.wkt) if 'z' in nodes_gdf.columns: new_nodes = nodes_gdf.drop_duplicates(subset = ['wkt', 'z']).copy() else: new_nodes = nodes_gdf.drop_duplicates(subset = ['wkt']).copy() # assign univocal nodeID to edges which have 'u' or 'v' referring to duplicate nodes to_edit = list(set(nodes_gdf.index.values.tolist()) - set((new_nodes.index.values.tolist()))) if not to_edit: return(nodes_gdf, edges_gdf) # readjusting edges' nodes too, accordingly for node in to_edit: geo = nodes_gdf.loc[node].geometry tmp = new_nodes[new_nodes.geometry == geo] index = tmp.iloc[0][nodeID] # assigning the unique index to edges edges_gdf.loc[edges_gdf.u == node,'u'] = index edges_gdf.loc[edges_gdf.v == node,'v'] = index nodes_gdf = new_nodes.drop('wkt', axis =1).copy() return nodes_gdf, edges_gdf
[docs]def fix_dead_ends(nodes_gdf, edges_gdf): """ The function removes dead-ends. In other words, it eliminates nodes from where only one segment originates, and the relative segment. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ nodes_gdf = nodes_gdf.copy() edges_gdf = edges_gdf.copy() # find the nodes that are dead-ends dead_end_nodes = [node for node, degree in nodes_degree(edges_gdf).items() if degree == 1] if not dead_end_nodes: # if there are no dead-end nodes, return the original GeoDataFrames return nodes_gdf, edges_gdf # drop the dead-end nodes and edges from the GeoDataFrames nodes_gdf = nodes_gdf[~nodes_gdf.index.isin(dead_end_nodes)] edges_gdf = edges_gdf[~edges_gdf['u'].isin(dead_end_nodes) & ~edges_gdf['v'].isin(dead_end_nodes)] return nodes_gdf, edges_gdf
[docs]def is_nodes_simplified(nodes_gdf, edges_gdf, nodes_to_keep_regardless = []): """ The function checks the presence of pseudo-junctions, by using the edges_gdf GeoDataFrame. Parameters ---------- edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- simplified: bool Whether the nodes of the network are simplified or not. """ simplified = True to_edit = {k: v for k, v in nodes_degree(edges_gdf).items() if v == 2} if nodes_to_keep_regardless: to_edit_list = list(to_edit.keys()) tmp_nodes = nodes_gdf[(nodes_gdf.nodeID.isin(to_edit_list)) & (~nodes_gdf.nodeID.isin(nodes_to_keep_regardless))].copy() to_edit = list(tmp_nodes.nodeID) if len(to_edit) == 0: return simplified return False
[docs]def is_edges_simplified(edges_gdf, preserve_direction): """ The function checks the presence of possible duplicate geometries in the edges_gdf GeoDataFrame. Parameters ---------- edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- simplified: bool Whether the edges of the network are simplified or not. """ if not preserve_direction: edges_gdf["code"] = np.where(edges_gdf['v'] >= edges_gdf['u'], edges_gdf.u.astype(str)+"-"+edges_gdf.v.astype(str), edges_gdf.v.astype(str)+"-"+edges_gdf.u.astype(str)) else: edges_gdf["code"] = edges_gdf.u.astype(str)+"-"+edges_gdf.v.astype(str) if edges_gdf.duplicated('code').any(): max_lengths = edges_gdf.groupby("code").agg({'length': 'max'}).to_dict()['length'] for code, group in edges_gdf.groupby("code"): if group[group.length < max_lengths[code] * 0.9].shape[0]>0: return False return True
[docs]def simplify_graph(nodes_gdf, edges_gdf, nodes_to_keep_regardless = []): """ The function identify pseudo-nodes, namely nodes that represent intersection between only 2 segments. The segments geometries are merged and the node is removed from the nodes_gdf GeoDataFrame. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. nodes_to_keep_regardless: list List of nodeIDs representing nodes to keep, even when pseudo-nodes (e.g. stations, when modelling transport networks). Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ nodes_gdf = nodes_gdf.copy() edges_gdf = edges_gdf.copy() to_edit = list(set(n for n, d in nodes_degree(edges_gdf).items() if d == 2)) if len(to_edit) == 0: return(nodes_gdf, edges_gdf) if nodes_to_keep_regardless: to_edit_list = list(to_edit.keys()) tmp_nodes = nodes_gdf[(nodes_gdf.nodeID.isin(to_edit_list)) & (~nodes_gdf.nodeID.isin(nodes_to_keep_regardless))].copy() to_edit = list(tmp_nodes.nodeID) for nodeID in to_edit: tmp = edges_gdf[(edges_gdf.u == nodeID) | (edges_gdf.v == nodeID)].copy() if len(tmp) == 0: nodes_gdf.drop(nodeID, axis = 0, inplace = True) continue if len(tmp) == 1: continue # possible dead end # pseudo junction identified first_edge, second_edge = tmp.iloc[0], tmp.iloc[1] nodes_gdf, edges_gdf = merge_pseudo_edges(first_edge, second_edge, nodeID, nodes_gdf, edges_gdf) edges_gdf = edges_gdf[edges_gdf['u'] != edges_gdf['v']] #eliminate node-lines return nodes_gdf, edges_gdf
[docs]def merge_pseudo_edges(first_edge, second_edge, nodeID, nodes_gdf, edges_gdf): """ Merge pseudo-edges by updating node and edge information in the corresponding GeoDataFrames. Parameters ---------- first_edge: Series The first pseudo-edge to be merged. second_edge: Series The second pseudo-edge to be merged. nodeID: int The nodeID of the node where the pseudo-edges meet. nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The updated junctions and street segments GeoDataFrames. """ index_first, index_second = first_edge.edgeID, second_edge.edgeID first_coords = first_edge['geometry'].coords second_coords = second_edge['geometry'].coords # meeting at u if (first_edge['u'] == second_edge['u']): edges_gdf.at[index_first,'u'] = first_edge['v'] edges_gdf.at[index_first,'v'] = second_edge['v'] line_coordsA, line_coordsB = list(first_coords), list(second_coords) line_coordsA.reverse() # meeting at u and v elif (first_edge['u'] == second_edge['v']): edges_gdf.at[index_first,'u'] = second_edge['u'] line_coordsA, line_coordsB = list(second_coords), list(first_coords) # meeting at v and u elif (first_edge['v'] == second_edge['u']): edges_gdf.at[index_first,'v'] = second_edge['v'] line_coordsA, line_coordsB = list(first_coords), list(second_coords) # meeting at v and v else: # (first_edge['v'] == second_edge['v']) edges_gdf.at[index_first,'v'] = second_edge['u'] line_coordsA, line_coordsB = list(first_coords), list(second_coords) line_coordsB.reverse() # checking that no edges with node_u == node_v has been created, if yes: drop it if edges_gdf.loc[index_first].u == edges_gdf.loc[index_first].v: edges_gdf = edges_gdf.drop([index_first, index_second], axis = 0) nodes_gdf = nodes_gdf.drop(nodeID, axis = 0) return nodes_gdf, edges_gdf # obtaining coordinates-list in consistent order and merging merged_line = line_coordsA + line_coordsB edges_gdf.at[index_first, 'geometry'] = LineString([coor for coor in merged_line]) # dropping the second segment, as the new geometry was assigned to the first edge edges_gdf = edges_gdf.drop(index_second, axis = 0) nodes_gdf = nodes_gdf.drop(nodeID, axis = 0) return nodes_gdf, edges_gdf
def prepare_dataframes(nodes_gdf, edges_gdf): """ Prepare nodes and edges dataframes for further analysis by extracting the x,y coordinates of the nodes and adding new columns to the edges dataframe. Parameters ---------- nodes_gdf: Point GeoDataFrame nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns: ---------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ nodes_gdf = nodes_gdf.copy().set_index('nodeID', drop=False) edges_gdf = edges_gdf.copy().set_index('edgeID', drop=False) nodes_gdf.index.name, edges_gdf.index.name = None, None nodes_gdf['x'], nodes_gdf['y'] = list(zip(*[(r.coords[0][0], r.coords[0][1]) for r in nodes_gdf.geometry])) edges_gdf.sort_index(inplace = True) if 'highway' in edges_gdf.columns: to_remove = ['elevator'] edges_gdf = edges_gdf[~edges_gdf.highway.isin(to_remove)] return nodes_gdf, edges_gdf
[docs]def simplify_same_vertexes_edges(edges_gdf, preserve_direction): """ This function is used to simplify edges that have the same start and end point (i.e. 'u' and 'v' values) in the edges_gdf GeoDataFrame. It removes duplicate edges that have similar geometry, keeping only the one with the longest length if one of them is 10% longer of the other. Otherwise generates a center line Parameters ---------- edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- edges_gdf: LineString GeoDataFrame The clean street segments GeoDataFrames. """ to_drop = set() if not edges_gdf.duplicated('code').any(): return edges_gdf if not preserve_direction: edges_gdf["code"] = np.where(edges_gdf['v'] >= edges_gdf['u'], edges_gdf.u.astype(str)+"-"+edges_gdf.v.astype(str), edges_gdf.v.astype(str)+"-"+edges_gdf.u.astype(str)) else: edges_gdf["code"] = edges_gdf.u.astype(str)+"-"+edges_gdf.v.astype(str) groups = edges_gdf.groupby("code").filter(lambda x: len(x) > 1)[['code','length', 'edgeID']].sort_values(by=['code','length']) max_lengths = edges_gdf.groupby("code").agg({'length': 'max'}).to_dict()['length'] for code, g in edges_gdf.groupby("code"): if g[g.length < max_lengths[code] * 0.9].shape[0]>0: to_drop.update(list(g[g.length < max_lengths[code] * 0.9]['edgeID'])) groups = groups.drop(list(to_drop), axis = 0) groups_filtered = groups.groupby('code').filter(lambda x: len(x) > 1)[['code','length','edgeID']].sort_values(by=['code','length']) first_indexes = list(groups_filtered.groupby("code")[['edgeID']].first().edgeID) others = set(groups_filtered.edgeID.to_list())- set(first_indexes) to_drop.update(others) # Update the geometry of the first edge in each group to the center line of the edge to update for index in first_indexes: code = edges_gdf.loc[index]['code'] geometryA = edges_gdf.loc[index].geometry geometryB = edges_gdf.query("code == @code").iloc[1].geometry cl = center_line(geometryA, geometryB) edges_gdf.at[index, 'geometry'] = cl sub_group = edges_gdf.query("code == @code").copy() edges_gdf = edges_gdf.drop(list(to_drop), axis = 0) return edges_gdf
[docs]def clean_network(nodes_gdf, edges_gdf, dead_ends = False, remove_islands = True, same_vertexes_edges = True, self_loops = False, fix_topology = False, preserve_direction = False, nodes_to_keep_regardless = []): """ It calls a series of functions to clean nodes and edges GeoDataFrames. It handles: - pseudo-nodes; - duplicate-geometries (nodes and edges); - disconnected islands - optional; - edges with different geometry but same nodes - optional; - dead-ends - optional; - self-loops - optional; - toplogy issues - optional. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. dead_ends: bool When true remove dead ends. remove_islands: bool When true checks the existence of disconnected islands within the network and deletes corresponding graph components. same_vertexes_edges: bool When true, it considers as duplicates couple of edges with same vertexes but different geometry. If one of the edges is 1% longer than the other, the longer is deleted; otherwise a center line is built to replace the same vertexes edges. self_loops: bool When true, removes genuine self-loops. fix_topology: bool When true, it breaks lines at intersections with other lines in the streets GeoDataFrame. preserve_direction: bool When true, it does not consider segments with same coordinates list, but different directions, as identical. When false, it does and therefore considers them as duplicated geometries. nodes_to_keep_regardless: list List of nodeIDs representing nodes to keep, even when pseudo-nodes (e.g. stations, when modelling transport networks). Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ nodes_gdf, edges_gdf = prepare_dataframes(nodes_gdf, edges_gdf) # removes fake self-loops wrongly coded by the data source nodes_gdf, edges_gdf = fix_self_loops(nodes_gdf, edges_gdf) if dead_ends: nodes_gdf, edges_gdf = fix_dead_ends(nodes_gdf, edges_gdf) if remove_islands: nodes_gdf, edges_gdf = remove_disconnected_islands(nodes_gdf, edges_gdf, 'nodeID') if fix_topology: nodes_gdf, edges_gdf = fix_network_topology(nodes_gdf, edges_gdf) cycle = 0 while ((not is_edges_simplified(edges_gdf, preserve_direction) and same_vertexes_edges) | (not is_nodes_simplified(nodes_gdf, edges_gdf, nodes_to_keep_regardless)) | (cycle == 0)): edges_gdf['length'] = edges_gdf['geometry'].length # recomputing length, to account for small changes cycle += 1 nodes_gdf, edges_gdf = duplicate_nodes(nodes_gdf, edges_gdf) #eliminate loops if self_loops: edges_gdf = edges_gdf[edges_gdf['u'] != edges_gdf['v']] if dead_ends: nodes_gdf, edges_gdf = fix_dead_ends(nodes_gdf, edges_gdf) #eliminate node-lines edges_gdf = edges_gdf[edges_gdf['u'] != edges_gdf['v']] # dropping duplicate-geometries edges geometries = edges_gdf['geometry'].apply(lambda geom: geom.wkb) edges_gdf = edges_gdf.loc[geometries.drop_duplicates().index] # dropping edges with same geometry but with coords in different orders (depending on their directions) # Reordering coordinates to allow for comparison between edges edges_gdf['coords'] = [list(c.coords) for c in edges_gdf.geometry] if not preserve_direction: condition = ((edges_gdf.u.astype(str)+"-"+edges_gdf.v.astype(str)) != edges_gdf.code) edges_gdf.loc[condition, 'coords'] = pd.Series([x[::-1] for x in edges_gdf.loc[condition]['coords']], index = edges_gdf.loc[condition].index) edges_gdf['tmp'] = edges_gdf['coords'].apply(tuple, 1) edges_gdf.drop_duplicates(['tmp'], keep = 'first', inplace = True) # edges with different geometries but same u-v nodes pairs if same_vertexes_edges: edges_gdf = simplify_same_vertexes_edges(edges_gdf, preserve_direction) # only keep nodes which are actually used by the edges in the GeoDataFrame to_keep = list(set(list(edges_gdf['u'].unique()) + list(edges_gdf['v'].unique()))) nodes_gdf = nodes_gdf[nodes_gdf['nodeID'].isin(to_keep)] # simplify the graph nodes_gdf, edges_gdf = simplify_graph(nodes_gdf, edges_gdf, nodes_to_keep_regardless) if remove_islands: nodes_gdf, edges_gdf = remove_disconnected_islands(nodes_gdf, edges_gdf, 'nodeID') nodes_gdf['x'], nodes_gdf['y'] = list(zip(*[(r.coords[0][0], r.coords[0][1]) for r in nodes_gdf.geometry])) nodes_gdf['nodeID'] = nodes_gdf.nodeID.astype(int) edges_gdf = correct_edges(nodes_gdf, edges_gdf) # correct edges coordinates edges_gdf.drop(['coords', 'tmp', 'code', 'wkt', 'fixing', 'to_fix'], axis = 1, inplace = True, errors = 'ignore') # remove temporary columns edges_gdf['length'] = edges_gdf['geometry'].length edges_gdf.set_index('edgeID', drop = False, inplace = True, append = False) nodes_gdf, edges_gdf = reset_index_graph_gdfs(nodes_gdf, edges_gdf, nodeID = "nodeID") edges_gdf.index.name = None return nodes_gdf, edges_gdf
def add_fixed_edges(edges_gdf, to_fix_gdf): """ Add fixed edges to the edges GeoDataFrame. Parameters ---------- edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. to_fix_gdf: GeoDataFrame The GeoDataFrame containing the edges to be fixed. Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The cleaned junctions and street segments GeoDataFrames. """ dfs = [] new_geometries = to_fix_gdf.apply(lambda row: split_line_at_MultiPoint(row.geometry, [Point(coord) for coord in row.to_fix]), axis=1) new_geometries = pd.DataFrame(new_geometries, columns = ['lines']) def append_new_geometries(row): for n, line in enumerate(row): # assigning the resulting geometries ix = row.name if n == 0: index = ix else: index = max(edges_gdf.index)+1 # copy attributes row = to_fix_gdf.loc[ix].copy() # and assign geometry an new edgeID row['edgeID'] = index row['geometry'] = line dfs.append(row.to_frame().T) new_geometries.apply(lambda row: append_new_geometries(row), axis = 1) rows = pd.concat(dfs, ignore_index = True) rows = rows.explode(column = 'geometry') # concatenate the dataframes and assign to edges_gdf edges_gdf = pd.concat([edges_gdf, rows], ignore_index=True) edges_gdf.drop(['u', 'v', 'to_fix', 'fixing', 'coords'], inplace=True, axis=1) edges_gdf['length'] = edges_gdf.geometry.length edges_gdf['edgeID'] = edges_gdf.index nodes_gdf = obtain_nodes_gdf(edges_gdf, edges_gdf.crs) nodes_gdf, edges_gdf = join_nodes_edges_by_coordinates(nodes_gdf, edges_gdf) return nodes_gdf, edges_gdf
[docs]def fix_network_topology(nodes_gdf, edges_gdf): """ Fix the network topology by splitting intersecting edges and adding fixed edges to the network. This function considers as segments to be fixed only segments that are actually fully intersecting, thus sharing coordinates, excluding their from and to vertices coordinates, but withouth actually generating, in the given GeoDataFrame, the right number of features. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- LineString GeoDataFrame The updated edges GeoDataFrame. """ edges_gdf.copy() edges_gdf['coords'] = [list(geometry.coords) for geometry in edges_gdf.geometry] # spatial index sindex = edges_gdf.sindex def find_intersections(ix, line_geometry, coords): """ Find intersection points between a line and other intersecting edges. Parameters ---------- ix : int The index of the line to check for intersections. line_geometry : LineString The LineString geometry of the line to check for intersections. coords : list The list of coordinates of the line's geometry. Returns ------- list A list of actual intersection points between the line and other intersecting edges. """ possible_matches_index = list(sindex.intersection(line_geometry.buffer(5).bounds)) possible_matches = edges_gdf.iloc[possible_matches_index].copy() # lines intersecting the given line tmp = possible_matches[possible_matches.intersects(line_geometry)] tmp = tmp.drop(ix, axis = 0) union = tmp.unary_union # find actual intersections actual_intersections = [] intersections = line_geometry.intersection(union) if intersections is None: return actual_intersections if intersections.geom_type == 'LineString': # probably overlapping (to resolve) return actual_intersections if intersections.geom_type == 'Point': intersections = [intersections] else: intersections = intersections.geoms # from and to vertices of the given line segment_vertices = [coords[0], coords[-1]] # obtaining all the intersecting Points intersection_points = [intersection for intersection in intersections if intersection.geom_type == 'Point'] # keeping intersections that are in the coordinate list of the given line, without actually coinciding with the from and to vertices for point in intersection_points: if (point.coords[0] not in coords): pass if (point.coords[0] in segment_vertices): pass else: actual_intersections.append(point) return actual_intersections # verify which street segment needs to be fixed edges_gdf['to_fix'] = edges_gdf.apply(lambda row: find_intersections(row.name, row.geometry, row.coords), axis=1) # verify which street segment needs to be fixed edges_gdf['fixing'] = [True if len(to_fix) > 0 else False for to_fix in edges_gdf['to_fix']] to_fix = edges_gdf[edges_gdf['fixing'] == True].copy() edges_gdf = edges_gdf[edges_gdf['fixing'] == False] if len(to_fix) == 0: return edges_gdf return add_fixed_edges(edges_gdf, to_fix)
[docs]def fix_self_loops(nodes_gdf, edges_gdf): """ Fix the network topology by removing (fake) self-loops and adding fixed edges. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- LineString GeoDataFrame The updated edges GeoDataFrame. """ edges_gdf = edges_gdf.copy() edges_gdf['coords'] = [list(geometry.coords) for geometry in edges_gdf.geometry] # all the coordinates but the from and to vertices' ones. edges_gdf['coords'] = [coords[1:-1] for coords in edges_gdf.coords] # convert nodes_gdf['x'] and nodes_gdf['y'] to numpy arrays for faster computation x = list(nodes_gdf['x']) y = list(nodes_gdf['y']) # create a set of all coordinates in nodes. This essentially correspond to the from and to nodes of the edges currently in the edges_gdf nodes_set = set(zip(x, y)) to_fix = [] # loop through the coordinates in edges_gdf.coords and check if they are in the nodes_set. This means that one of the edges coords (not from and to), # coincide with some other edge from or to vertex (indicating some sort of loop) for coords in edges_gdf.coords: fix_coords = [] for coord in coords: if coord in nodes_set: fix_coords.append(coord) to_fix.append(fix_coords) # assign the results to self_loops['to_fix'] edges_gdf['to_fix'] = to_fix edges_gdf['fixing'] = [True if len(to_fix) > 0 else False for to_fix in edges_gdf['to_fix']] to_fix = edges_gdf[edges_gdf['fixing'] == True].copy() edges_gdf = edges_gdf[edges_gdf['fixing'] == False] if len(to_fix) == 0: return nodes_gdf, edges_gdf return add_fixed_edges(edges_gdf, to_fix)
[docs]def remove_disconnected_islands(nodes_gdf, edges_gdf, nodeID): """ Remove disconnected islands from a graph. Parameters: ----------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. nodeID: str The name of the field containing the nodeIDs. Returns ------- nodes_gdf, edges_gdf: tuple of GeoDataFrames The updated junctions and street segments GeoDataFrame. """ Ng = graph_fromGDF(nodes_gdf, edges_gdf, nodeID) if not nx.is_connected(Ng): largest_component = max(nx.connected_components(Ng), key=len) # Create a subgraph of Ng consisting only of this component: G = Ng.subgraph(largest_component) to_keep = list(G.nodes()) nodes_gdf = nodes_gdf[nodes_gdf[nodeID].isin(to_keep)] edges_gdf = edges_gdf[(edges_gdf.u.isin(nodes_gdf[nodeID])) & (edges_gdf.v.isin(nodes_gdf[nodeID]))] return nodes_gdf, edges_gdf
def assign_group_membership_to_islands(graph, edges_gdf): """ Assign group membership to islands in the network by updating the 'group' attribute in the edges GeoDataFrame. Parameters ---------- graph: NetworkX Graph The graph representing the network. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- edges_gdf: LineString GeoDataFrame The updated street segments GeoDataFrame. """ components = nx.connected_components(graph) for n, c in enumerate(islands): nodes_group = list(c) edges_gdf.loc[(edges_gdf.u.isin(nodes_group) & (edges_gdf.v.isin(nodes_group))),'group'] = n return edges_gdf
[docs]def correct_edges(nodes_gdf, edges_gdf): """ The function adjusts the edges LineString coordinates consistently with their relative u and v nodes' coordinates. It might be necessary to run the function after having cleaned the network. Parameters ---------- nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame. edges_gdf: LineString GeoDataFrame The street segments GeoDataFrame. Returns ------- edges_gdf: LineString GeoDataFrame The updated street segments GeoDataFrame. """ edges_gdf['geometry'] = edges_gdf.apply(lambda row: _update_line_geometry_coords(row['u'], row['v'], nodes_gdf, row['geometry']), axis=1) return edges_gdf
def _update_line_geometry_coords(u, v, nodes_gdf, line_geometry): """ It supports the correct_edges function checks that the edges coordinates are consistent with their relative u and v nodes'coordinates. It can be necessary to run the function after having cleaned the network. Parameters ---------- u: int The nodeID of the from node of the geometry. v: int The nodeID of the to node of the geometry. nodes_gdf: Point GeoDataFrame The nodes (junctions) GeoDataFrame . line_geometry: LineString A street segment geometry. Returns ------- new_line_geometry: LineString The readjusted LineString, on the basis of the given u and v nodes. """ line_coords = list(line_geometry.coords) line_coords[0] = (nodes_gdf.loc[u]['x'], nodes_gdf.loc[u]['y']) line_coords[-1] = (nodes_gdf.loc[v]['x'], nodes_gdf.loc[v]['y']) new_line_geometry = LineString([coor for coor in line_coords]) return new_line_geometry