Source code for sgis.networkanalysis.nodes

"""Create nodes (points) from a GeoDataFrame of lines.

The functions are used inside inside other functions, and aren't needed
to use explicitly.
"""

import geopandas as gpd
import pandas as pd
from geopandas import GeoDataFrame
from geopandas import GeoSeries
from shapely.geometry import Point

from ..geopandas_tools.general import _push_geom_col
from ..geopandas_tools.general import make_edge_coords_cols
from ..geopandas_tools.general import make_edge_wkt_cols
from ..geopandas_tools.geometry_types import make_all_singlepart


[docs] def make_node_ids( gdf: GeoDataFrame, wkt: bool = True, ) -> tuple[GeoDataFrame, GeoDataFrame]: """Gives the lines unique node ids and returns lines (edges) and nodes. Takes the first and last point of each line and creates a GeoDataFrame of nodes (points) with a column 'node_id'. The node ids are then assigned to the input GeoDataFrame of lines as the columns 'source' and 'target'. Args: gdf: GeoDataFrame with line geometries wkt: If True (default), the resulting nodes will include the column 'wkt', containing the well-known text representation of the geometry. If False, it will include the column 'coords', a tuple with x and y geometries. Returns: A tuple of two GeoDataFrames, one with the lines and one with the nodes. Note: The lines must be singlepart linestrings. """ gdf = make_all_singlepart(gdf, ignore_index=True) if wkt: gdf = make_edge_wkt_cols(gdf) geomcol1, geomcol2, geomcol_final = "source_wkt", "target_wkt", "wkt" else: gdf = make_edge_coords_cols(gdf) geomcol1, geomcol2, geomcol_final = "source_coords", "target_coords", "coords" # remove identical lines in opposite directions gdf["meters_"] = gdf.length.astype(str) sources = gdf[[geomcol1, geomcol2, "meters_"]].rename( columns={geomcol1: geomcol_final, geomcol2: "temp"} ) targets = gdf[[geomcol1, geomcol2, "meters_"]].rename( columns={geomcol2: geomcol_final, geomcol1: "temp"} ) nodes = ( pd.concat([sources, targets], axis=0, ignore_index=True) .drop_duplicates([geomcol_final, "temp", "meters_"]) .drop(["meters_", "temp"], axis=1) ) gdf = gdf.drop("meters_", axis=1) nodes["n"] = nodes.assign(n=1).groupby(geomcol_final)["n"].transform("sum") nodes = nodes.drop_duplicates(subset=[geomcol_final]).reset_index(drop=True) nodes["node_id"] = nodes.index nodes["node_id"] = nodes["node_id"].astype(str) id_dict = { geom: node_id for geom, node_id in zip(nodes[geomcol_final], nodes["node_id"], strict=True) } gdf["source"] = gdf[geomcol1].map(id_dict) gdf["target"] = gdf[geomcol2].map(id_dict) n_dict = {geom: n for geom, n in zip(nodes[geomcol_final], nodes["n"], strict=True)} gdf["n_source"] = gdf[geomcol1].map(n_dict) gdf["n_target"] = gdf[geomcol2].map(n_dict) if wkt: nodes["geometry"] = gpd.GeoSeries.from_wkt(nodes[geomcol_final], crs=gdf.crs) else: nodes["geometry"] = GeoSeries( [Point(geom) for geom in nodes[geomcol_final]], crs=gdf.crs ) nodes = gpd.GeoDataFrame(nodes, geometry="geometry", crs=gdf.crs) nodes = nodes.reset_index(drop=True) gdf = _push_geom_col(gdf) return gdf, nodes