From 0c18b9d633dfd68278f7bb17ad8c07272eb0cd44 Mon Sep 17 00:00:00 2001 From: Gareth Simons Date: Sat, 18 Nov 2023 14:23:33 +0000 Subject: [PATCH] generic_edges_geopandas_from_nx --- pysrc/cityseer/tools/graphs.py | 2 +- pysrc/cityseer/tools/io.py | 45 ++++++++++++++++++++++++++++++++++ tests/tools/test_io.py | 11 +++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pysrc/cityseer/tools/graphs.py b/pysrc/cityseer/tools/graphs.py index 67ff0a9f..90a67abc 100644 --- a/pysrc/cityseer/tools/graphs.py +++ b/pysrc/cityseer/tools/graphs.py @@ -670,7 +670,7 @@ def nx_consolidate_nodes( The buffer distance to be used for consolidating nearby nodes. Defaults to 5. neighbour_policy: str Whether all nodes within the buffer distance are merged, or only "direct" or "indirect" neighbours. Defaults to - None. + None which will consider all nodes. crawl: bool Whether the algorithm will recursively explore neighbours of neighbours if those neighbours are within the buffer distance from the prior node. Defaults to False. diff --git a/pysrc/cityseer/tools/io.py b/pysrc/cityseer/tools/io.py index 0180fa9c..61e963f6 100644 --- a/pysrc/cityseer/tools/io.py +++ b/pysrc/cityseer/tools/io.py @@ -955,6 +955,51 @@ def nx_from_cityseer_geopandas( return g_multi_copy +def generic_edges_geopandas_from_nx( + nx_multigraph: nx.MultiGraph, + crs: str | int, +) -> gpd.GeoDataFrame: + """ + Transpose a `cityseer` `networkX` `MultiGraph` into a `gpd.GeoDataFrame` representing the network edges. + + Converts the `geom` attribute attached to each edge into a GeoPandas GeoDataFrame. This is useful when + inspecting or cleaning the network in QGIS. It can then be reimported with + [`nx_from_generic_geopandas`](#nx-from-generic-geopandas) + + Parameters + ---------- + nx_multigraph: nx.MultiGraph + A `networkX` `MultiGraph` in a projected coordinate system, containing `x` and `y` node attributes, and `geom` + edge attributes containing `LineString` geoms. + crs: str | int + CRS for initialising the returned structures. This is used for initialising the GeoPandas + [`GeoDataFrame`](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.html#geopandas-geodataframe). # pylint: disable=line-too-long + + Returns + ------- + gpd.GeoDataFrame + A `gpd.GeoDataFrame` with `edge_idx` and `geom` attributes. + + """ + if not isinstance(nx_multigraph, nx.MultiGraph): + raise TypeError("This method requires an undirected networkX MultiGraph.") + logger.info("Preparing node and edge arrays from networkX graph.") + agg_edge_data = [] + # set edges + for start_nd_key, end_nd_key, edge_idx, edge_data in nx_multigraph.edges(keys=True, data=True): + agg_edge_data.append( + { + "start_nd_key": start_nd_key, + "end_nd_key": end_nd_key, + "edge_idx": edge_idx, + "geom": edge_data["geom"], + } + ) + edges_gdf = gpd.GeoDataFrame(agg_edge_data, crs=crs, geometry="geom") + + return edges_gdf + + def nx_from_generic_geopandas( gdf_network: gpd.GeoDataFrame, # type: ignore ) -> nx.MultiGraph: diff --git a/tests/tools/test_io.py b/tests/tools/test_io.py index 9ffa281f..27fb3bec 100644 --- a/tests/tools/test_io.py +++ b/tests/tools/test_io.py @@ -705,6 +705,17 @@ def test_nx_from_cityseer_geopandas(primal_graph): assert G_round_trip_decomp.edges == G_decomposed.edges +def test_generic_edges_geopandas_from_nx(primal_graph): + """ """ + edges_gdf = io.generic_edges_geopandas_from_nx(primal_graph, 3395) + assert len(edges_gdf) == len(primal_graph.edges) + for _idx, row_data in edges_gdf.iterrows(): + assert ( + row_data["geom"] + == primal_graph[row_data["start_nd_key"]][row_data["end_nd_key"]][row_data["edge_idx"]]["geom"] + ) + + def test_nx_from_generic_geopandas(primal_graph): """ """ # generate a GDF for testing with