From 5e050fedbcaec4ccac834ae23ef3345f23796c8c Mon Sep 17 00:00:00 2001 From: Fred Thomas Date: Mon, 4 Jul 2022 17:29:30 +0100 Subject: [PATCH 1/3] add test case for split_multilinestrings --- tests/test_init.py | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index 955e3d0..0e61f34 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -9,7 +9,7 @@ from pandas.testing import assert_frame_equal from pytest import fixture, mark -from shapely.geometry import Point, LineString, MultiPoint +from shapely.geometry import Point, LineString, MultiPoint, MultiLineString try: import networkx as nx @@ -229,6 +229,52 @@ def test_split_at_nodes(unsplit, split): assert_frame_equal(split.edges, actual.edges) +def test_split_multilinestrings(): + """Explode multilinestrings into linestrings""" + + # point coordinates comprising three linestrings + mls_coords = [ + ( + (0, 0), + (0, 1), + ), + ( + (1, 1), + (2, 2), + (2, 1), + ), + ( + (0, 1), + (-1, 1), + (-1, 2), + (-1, 0), + ), + ] + # point coordsinate comprising a single linestring + ls_coords = [(4, 0), (4, 1), (4, 2)] + + # make input network edges + edges = GeoDataFrame( + { + "data": ["MLS", "LS"], + "geometry": [MultiLineString(mls_coords), LineString(ls_coords)], + } + ) + multi_network = snkit.network.Network(edges=edges) + # check we have two rows (multilinestring and linestring) in the input network + assert len(multi_network.edges) == 2 + + # split and check we have four rows (the resulting linestrings) in the output network + split_network = snkit.network.split_multilinestrings(multi_network) + assert len(split_network.edges) == 4 + + # check data is replicated from multilinestring to child linestrings + assert list(split_network.edges["data"].values) == ["MLS"] * 3 + ["LS"] + + # check everything is reindexed + assert list(split_network.edges.index.values) == list(range(4)) + + def test_split_line(): """Definitively split line at a point""" line = LineString( From 2beb7361517a6bc6f7e55a835dbbee0f7355a1e3 Mon Sep 17 00:00:00 2001 From: Fred Thomas Date: Mon, 4 Jul 2022 17:41:43 +0100 Subject: [PATCH 2/3] delegate split_multilinestrings to geopandas explode --- src/snkit/network.py | 52 +++++++++++--------------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/src/snkit/network.py b/src/snkit/network.py index 109b3ed..ecc2903 100644 --- a/src/snkit/network.py +++ b/src/snkit/network.py @@ -186,50 +186,24 @@ def _set_precision(geom): def split_multilinestrings(network): - """Create multiple edges from any MultiLineString edge - - Ensures that edge geometries are all LineStrings, duplicates attributes over any - created multi-edges. """ - simple_edge_attrs = [] - simple_edge_geoms = [] - edges = network.edges - - # # Commented out idea about dealing with multi-part MultiLineStrings separately - # is_multi_single_part = edges.geometry.apply(lambda geom: geom.geom_type == 'MultiLineString' and len(geom) == 1) - # is_multi_multi_part = edges.geometry.apply(lambda geom: geom.geom_type == 'MultiLineString' and len(geom) > 1) - # is_not_multi = edges.geometry.apply(lambda geom: geom.geom_type != 'MultiLineString') - - # not_multi_edges = edges[is_not_multi].copy() - # multi_single_part_edges = edges[is_multi_single_part].copy() - # multi_single_part_edges.geometry = multi_single_part_edges.geometry.apply(lambda geom: next(geom)) - # edges = edges[is_multi_multi_part].copy() - - for edge in tqdm( - edges.itertuples(index=False), desc="split_multi", total=len(edges) - ): - if edge.geometry.geom_type == "MultiLineString": - edge_parts = list(edge.geometry) - else: - edge_parts = [edge.geometry] - - for part in edge_parts: - simple_edge_geoms.append(part) + Create multiple edges from any MultiLineString edge - attrs = GeoDataFrame([edge] * len(edge_parts)) - simple_edge_attrs.append(attrs) + Ensures that edge geometries are all LineStrings, duplicates attributes + over any created multi-edges. + """ - simple_edge_geoms = GeoDataFrame(simple_edge_geoms, columns=["geometry"]) - edges = ( - pandas.concat(simple_edge_attrs, axis=0) - .reset_index(drop=True) - .drop("geometry", axis=1) - ) - edges = pandas.concat([edges, simple_edge_geoms], axis=1) + edges = network.edges + geom_col: str = geometry_column_name(edges) + split_edges = edges.explode(column=geom_col, ignore_index=True) - # edges = pandas.concat([edges, multi_single_part_edges, not_multi_edges], axis=0).reset_index(drop=True) + geo_types = set(split_edges.geom_type) + if geo_types != {'LineString'}: + raise ValueError( + f"exploded edges -> not only LineStrings {geo_types=}" + ) - return Network(nodes=network.nodes, edges=edges) + return Network(nodes=network.nodes, edges=split_edges) def merge_multilinestring(geom): From 6e1e637c2739ba71455ff4e4a9f611dbb7482577 Mon Sep 17 00:00:00 2001 From: Fred Thomas Date: Wed, 6 Jul 2022 11:39:32 +0100 Subject: [PATCH 3/3] remove use of f-string equals expression for debugging this was introduced in 3.8: https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging but still project aims to support 3.7 --- src/snkit/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snkit/network.py b/src/snkit/network.py index ecc2903..50ec2fb 100644 --- a/src/snkit/network.py +++ b/src/snkit/network.py @@ -200,7 +200,7 @@ def split_multilinestrings(network): geo_types = set(split_edges.geom_type) if geo_types != {'LineString'}: raise ValueError( - f"exploded edges -> not only LineStrings {geo_types=}" + f"exploded edges are of type(s) {geo_types} but should only be LineString" ) return Network(nodes=network.nodes, edges=split_edges)