| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- 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)
|