286 lines
11 KiB
Python
286 lines
11 KiB
Python
import unittest
|
|
|
|
import pytest
|
|
|
|
from shapely.errors import GeometryTypeError
|
|
from shapely.geometry import (
|
|
LineString,
|
|
MultiLineString,
|
|
MultiPoint,
|
|
MultiPolygon,
|
|
Point,
|
|
Polygon,
|
|
)
|
|
from shapely.ops import linemerge, split, unary_union
|
|
|
|
|
|
class TestSplitGeometry(unittest.TestCase):
|
|
# helper class for testing below
|
|
def helper(self, geom, splitter, expected_chunks):
|
|
s = split(geom, splitter)
|
|
assert s.geom_type == "GeometryCollection"
|
|
assert len(s.geoms) == expected_chunks
|
|
if expected_chunks > 1:
|
|
# split --> expected collection that when merged is again equal to original
|
|
# geometry
|
|
if s.geoms[0].geom_type == "LineString":
|
|
self.assertTrue(linemerge(s).simplify(0.000001).equals(geom))
|
|
elif s.geoms[0].geom_type == "Polygon":
|
|
union = unary_union(s).simplify(0.000001)
|
|
assert union.equals(geom)
|
|
assert union.area == geom.area
|
|
else:
|
|
raise ValueError
|
|
elif expected_chunks == 1:
|
|
# not split --> expected equal to line
|
|
assert s.geoms[0].equals(geom)
|
|
|
|
def test_split_closed_line_with_point(self):
|
|
# point at start/end of closed ring -> return equal
|
|
# see GH #524
|
|
ls = LineString([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
|
|
splitter = Point(0, 0)
|
|
self.helper(ls, splitter, 1)
|
|
|
|
|
|
class TestSplitPolygon(TestSplitGeometry):
|
|
poly_simple = Polygon([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
|
|
poly_hole = Polygon(
|
|
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)],
|
|
[[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]],
|
|
)
|
|
|
|
def test_split_poly_with_line(self):
|
|
# crossing at 2 points --> return 2 polygons
|
|
splitter = LineString([(1, 3), (1, -3)])
|
|
self.helper(self.poly_simple, splitter, 2)
|
|
self.helper(self.poly_hole, splitter, 2)
|
|
|
|
# crossing twice with one linestring --> return 3 polygons
|
|
splitter = LineString([(1, 3), (1, -3), (1.7, -3), (1.7, 3)])
|
|
self.helper(self.poly_simple, splitter, 3)
|
|
self.helper(self.poly_hole, splitter, 3)
|
|
|
|
# touching the boundary --> return equal
|
|
splitter = LineString([(0, 2), (5, 2)])
|
|
self.helper(self.poly_simple, splitter, 1)
|
|
self.helper(self.poly_hole, splitter, 1)
|
|
|
|
# inside the polygon --> return equal
|
|
splitter = LineString([(0.2, 0.2), (1.7, 1.7), (3, 2)])
|
|
self.helper(self.poly_simple, splitter, 1)
|
|
self.helper(self.poly_hole, splitter, 1)
|
|
|
|
# outside the polygon --> return equal
|
|
splitter = LineString([(0, 3), (3, 3), (3, 0)])
|
|
self.helper(self.poly_simple, splitter, 1)
|
|
self.helper(self.poly_hole, splitter, 1)
|
|
|
|
def test_split_poly_with_multiline(self):
|
|
# crossing twice with a multilinestring --> return 3 polygons
|
|
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(1.7, -3), (1.7, 3)]])
|
|
self.helper(self.poly_simple, splitter, 3)
|
|
self.helper(self.poly_hole, splitter, 3)
|
|
|
|
# crossing twice with a cross multilinestring --> return 4 polygons
|
|
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(-3, 1), (3, 1)]])
|
|
self.helper(self.poly_simple, splitter, 4)
|
|
self.helper(self.poly_hole, splitter, 4)
|
|
|
|
# cross once, touch the boundary once --> return 2 polygons
|
|
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(0, 2), (5, 2)]])
|
|
self.helper(self.poly_simple, splitter, 2)
|
|
self.helper(self.poly_hole, splitter, 2)
|
|
|
|
# cross once, inside the polygon once --> return 2 polygons
|
|
splitter = MultiLineString(
|
|
[[(0.2, 3), (0.2, -3)], [(1.2, 1.2), (1.7, 1.7), (3, 2)]]
|
|
)
|
|
self.helper(self.poly_simple, splitter, 2)
|
|
self.helper(self.poly_hole, splitter, 2)
|
|
|
|
# cross once, outside the polygon once --> return 2 polygons
|
|
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(0, 3), (3, 3), (3, 0)]])
|
|
self.helper(self.poly_simple, splitter, 2)
|
|
self.helper(self.poly_hole, splitter, 2)
|
|
|
|
def test_split_poly_with_other(self):
|
|
with pytest.raises(GeometryTypeError):
|
|
split(self.poly_simple, Point(1, 1))
|
|
with pytest.raises(GeometryTypeError):
|
|
split(self.poly_simple, MultiPoint([(1, 1), (3, 4)]))
|
|
with pytest.raises(GeometryTypeError):
|
|
split(self.poly_simple, self.poly_hole)
|
|
|
|
|
|
class TestSplitLine(TestSplitGeometry):
|
|
ls = LineString([(0, 0), (1.5, 1.5), (3.0, 4.0)])
|
|
|
|
def test_split_line_with_point(self):
|
|
# point on line interior --> return 2 segments
|
|
splitter = Point(1, 1)
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# point on line point --> return 2 segments
|
|
splitter = Point(1.5, 1.5)
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# point on boundary --> return equal
|
|
splitter = Point(3, 4)
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
# point on exterior of line --> return equal
|
|
splitter = Point(2, 2)
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
def test_split_line_with_multipoint(self):
|
|
# points on line interior --> return 4 segments
|
|
splitter = MultiPoint([(1, 1), (1.5, 1.5), (0.5, 0.5)])
|
|
self.helper(self.ls, splitter, 4)
|
|
|
|
# points on line interior and boundary -> return 2 segments
|
|
splitter = MultiPoint([(1, 1), (3, 4)])
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# point on linear interior but twice --> return 2 segments
|
|
splitter = MultiPoint([(1, 1), (1.5, 1.5), (1, 1)])
|
|
self.helper(self.ls, splitter, 3)
|
|
|
|
def test_split_line_with_line(self):
|
|
# crosses at one point --> return 2 segments
|
|
splitter = LineString([(0, 1), (1, 0)])
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# crosses at two points --> return 3 segments
|
|
splitter = LineString([(0, 1), (1, 0), (1, 2)])
|
|
self.helper(self.ls, splitter, 3)
|
|
|
|
# overlaps --> raise
|
|
splitter = LineString([(0, 0), (15, 15)])
|
|
with pytest.raises(ValueError):
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
# does not cross --> return equal
|
|
splitter = LineString([(0, 1), (0, 2)])
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
# is touching the boundary --> return equal
|
|
splitter = LineString([(-1, 1), (1, -1)])
|
|
assert splitter.touches(self.ls)
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
# splitter boundary touches interior of line --> return 2 segments
|
|
splitter = LineString([(0, 1), (1, 1)]) # touches at (1, 1)
|
|
assert splitter.touches(self.ls)
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
def test_split_line_with_multiline(self):
|
|
# crosses at one point --> return 2 segments
|
|
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 0), (2, -2)]])
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# crosses at two points --> return 3 segments
|
|
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 2), (2, 0)]])
|
|
self.helper(self.ls, splitter, 3)
|
|
|
|
# crosses at three points --> return 4 segments
|
|
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 2), (2, 0), (2.2, 3.2)]])
|
|
self.helper(self.ls, splitter, 4)
|
|
|
|
# overlaps --> raise
|
|
splitter = MultiLineString([[(0, 0), (1.5, 1.5)], [(1.5, 1.5), (3, 4)]])
|
|
with pytest.raises(ValueError):
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
# does not cross --> return equal
|
|
splitter = MultiLineString([[(0, 1), (0, 2)], [(1, 0), (2, 0)]])
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
def test_split_line_with_polygon(self):
|
|
# crosses at two points --> return 3 segments
|
|
splitter = Polygon([(1, 0), (1, 2), (2, 2), (2, 0), (1, 0)])
|
|
self.helper(self.ls, splitter, 3)
|
|
|
|
# crosses at one point and touches boundary --> return 2 segments
|
|
splitter = Polygon([(0, 0), (1, 2), (2, 2), (1, 0), (0, 0)])
|
|
self.helper(self.ls, splitter, 2)
|
|
|
|
# exterior crosses at one point and touches at (0, 0)
|
|
# interior crosses at two points
|
|
splitter = Polygon(
|
|
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)],
|
|
[[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]],
|
|
)
|
|
self.helper(self.ls, splitter, 4)
|
|
|
|
def test_split_line_with_multipolygon(self):
|
|
poly1 = Polygon(
|
|
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)]
|
|
) # crosses at one point and touches at (0, 0)
|
|
poly2 = Polygon(
|
|
[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]
|
|
) # crosses at two points
|
|
poly3 = Polygon([(0, 0), (0, -2), (-2, -2), (-2, 0), (0, 0)]) # not crossing
|
|
splitter = MultiPolygon([poly1, poly2, poly3])
|
|
self.helper(self.ls, splitter, 4)
|
|
|
|
|
|
class TestSplitClosedRing(TestSplitGeometry):
|
|
ls = LineString([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]])
|
|
|
|
def test_split_closed_ring_with_point(self):
|
|
splitter = Point([0.0, 0.0])
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
splitter = Point([0.0, 0.5])
|
|
self.helper(self.ls, splitter, 2)
|
|
result = split(self.ls, splitter)
|
|
assert result.geoms[0].coords[:] == [(0, 0), (0.0, 0.5)]
|
|
assert result.geoms[1].coords[:] == [(0.0, 0.5), (0, 1), (1, 1), (1, 0), (0, 0)]
|
|
|
|
# previously failed, see GH#585
|
|
splitter = Point([0.5, 0.0])
|
|
self.helper(self.ls, splitter, 2)
|
|
result = split(self.ls, splitter)
|
|
assert result.geoms[0].coords[:] == [(0, 0), (0, 1), (1, 1), (1, 0), (0.5, 0)]
|
|
assert result.geoms[1].coords[:] == [(0.5, 0), (0, 0)]
|
|
|
|
splitter = Point([2.0, 2.0])
|
|
self.helper(self.ls, splitter, 1)
|
|
|
|
|
|
class TestSplitMulti(TestSplitGeometry):
|
|
def test_split_multiline_with_point(self):
|
|
# a cross-like multilinestring with a point in the middle --> return 4 line
|
|
# segments
|
|
l1 = LineString([(0, 1), (2, 1)])
|
|
l2 = LineString([(1, 0), (1, 2)])
|
|
ml = MultiLineString([l1, l2])
|
|
splitter = Point((1, 1))
|
|
self.helper(ml, splitter, 4)
|
|
|
|
def test_split_multiline_with_multipoint(self):
|
|
# a cross-like multilinestring with a point in middle, a point on one of the
|
|
# lines and a point in the exterior
|
|
# --> return 4+1 line segments
|
|
l1 = LineString([(0, 1), (3, 1)])
|
|
l2 = LineString([(1, 0), (1, 2)])
|
|
ml = MultiLineString([l1, l2])
|
|
splitter = MultiPoint([(1, 1), (2, 1), (4, 2)])
|
|
self.helper(ml, splitter, 5)
|
|
|
|
def test_split_multipolygon_with_line(self):
|
|
# two polygons with a crossing line --> return 4 triangles
|
|
poly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
|
|
poly2 = Polygon([(1, 1), (1, 2), (2, 2), (2, 1), (1, 1)])
|
|
mpoly = MultiPolygon([poly1, poly2])
|
|
ls = LineString([(-1, -1), (3, 3)])
|
|
self.helper(mpoly, ls, 4)
|
|
|
|
# two polygons away from the crossing line --> return identity
|
|
poly1 = Polygon([(10, 10), (10, 11), (11, 11), (11, 10), (10, 10)])
|
|
poly2 = Polygon([(-10, -10), (-10, -11), (-11, -11), (-11, -10), (-10, -10)])
|
|
mpoly = MultiPolygon([poly1, poly2])
|
|
ls = LineString([(-1, -1), (3, 3)])
|
|
self.helper(mpoly, ls, 2)
|