Skip to content

Commit

Permalink
Merge pull request #54 from tomalrussell/feature/improve_split_multil…
Browse files Browse the repository at this point in the history
…inestrings

Improve split_multilinestrings
  • Loading branch information
tomalrussell authored Jul 6, 2022
2 parents 24fac5e + 6e1e637 commit 51dae0b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 40 deletions.
52 changes: 13 additions & 39 deletions src/snkit/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 are of type(s) {geo_types} but should only be LineString"
)

return Network(nodes=network.nodes, edges=edges)
return Network(nodes=network.nodes, edges=split_edges)


def merge_multilinestring(geom):
Expand Down
48 changes: 47 additions & 1 deletion tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 51dae0b

Please sign in to comment.