import numpy as np import pytest import shapely from shapely import ( Geometry, GeometryCollection, GEOSException, LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, geos_version, ) from shapely.errors import UnsupportedGEOSVersionError from shapely.testing import assert_geometries_equal from shapely.tests.common import ( ArrayLike, all_types, empty, empty_line_string, empty_point, empty_polygon, ignore_invalid, line_string, multi_point, point, point_z, ) geos39 = geos_version[0:2] == (3, 9) geos310 = geos_version[0:2] == (3, 10) geos311 = geos_version[0:2] == (3, 11) CONSTRUCTIVE_NO_ARGS = ( shapely.boundary, shapely.centroid, shapely.convex_hull, pytest.param( shapely.concave_hull, marks=pytest.mark.skipif( shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11" ), ), shapely.envelope, shapely.extract_unique_points, shapely.minimum_clearance_line, shapely.node, shapely.normalize, shapely.point_on_surface, pytest.param( shapely.constrained_delaunay_triangles, marks=pytest.mark.skipif( shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10" ), ), ) CONSTRUCTIVE_FLOAT_ARG = ( shapely.buffer, shapely.offset_curve, shapely.delaunay_triangles, shapely.simplify, shapely.voronoi_polygons, ) @pytest.mark.parametrize("geometry", all_types) @pytest.mark.parametrize("func", CONSTRUCTIVE_NO_ARGS) def test_no_args_array(geometry, func): if ( geometry.is_empty and shapely.get_num_geometries(geometry) > 0 and func is shapely.node and ( (geos39 and geos_version < (3, 9, 3)) or (geos310 and geos_version < (3, 10, 3)) ) ): # GEOS GH-601 pytest.xfail("GEOS < 3.9.3 or GEOS < 3.10.3 crashes with empty geometries") actual = func([geometry, geometry]) assert actual.shape == (2,) assert actual[0] is None or isinstance(actual[0], Geometry) @pytest.mark.parametrize("geometry", all_types) @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG) def test_float_arg_array(geometry, func): if ( func is shapely.offset_curve and shapely.get_type_id(geometry) not in [1, 2] and shapely.geos_version < (3, 11, 0) ): with pytest.raises(GEOSException, match="only accept linestrings"): func([geometry, geometry], 0.0) return # voronoi_polygons emits an "invalid" warning when supplied with an empty # point (see https://github.com/libgeos/geos/issues/515) with ignore_invalid( func is shapely.voronoi_polygons and shapely.get_type_id(geometry) == 0 and shapely.geos_version < (3, 12, 0) ): actual = func([geometry, geometry], 0.0) assert actual.shape == (2,) assert isinstance(actual[0], Geometry) @pytest.mark.parametrize("geometry", all_types) @pytest.mark.parametrize("reference", all_types) def test_snap_array(geometry, reference): actual = shapely.snap([geometry, geometry], [reference, reference], tolerance=1.0) assert actual.shape == (2,) assert isinstance(actual[0], Geometry) @pytest.mark.parametrize("func", CONSTRUCTIVE_NO_ARGS) def test_no_args_missing(func): actual = func(None) assert actual is None @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG) def test_float_arg_missing(func): actual = func(None, 1.0) assert actual is None @pytest.mark.parametrize("geometry", all_types) @pytest.mark.parametrize("func", CONSTRUCTIVE_FLOAT_ARG) def test_float_arg_nan(geometry, func): actual = func(geometry, float("nan")) assert actual is None def test_buffer_cap_style_invalid(): with pytest.raises(ValueError, match="'invalid' is not a valid option"): shapely.buffer(point, 1, cap_style="invalid") def test_buffer_join_style_invalid(): with pytest.raises(ValueError, match="'invalid' is not a valid option"): shapely.buffer(point, 1, join_style="invalid") def test_snap_none(): actual = shapely.snap(None, point, tolerance=1.0) assert actual is None @pytest.mark.parametrize("geometry", all_types) def test_snap_nan_float(geometry): actual = shapely.snap(geometry, point, tolerance=np.nan) assert actual is None def test_build_area_none(): actual = shapely.build_area(None) assert actual is None @pytest.mark.parametrize( "geom,expected", [ (point, empty), # a point has no area (line_string, empty), # a line string has no area # geometry collection of two polygons are combined into one ( GeometryCollection( [ Polygon([(0, 0), (0, 3), (3, 3), (3, 0), (0, 0)]), Polygon([(1, 1), (2, 2), (1, 2), (1, 1)]), ] ), Polygon( [(0, 0), (0, 3), (3, 3), (3, 0), (0, 0)], holes=[[(1, 1), (2, 2), (1, 2), (1, 1)]], ), ), (empty, empty), ([empty], [empty]), ], ) def test_build_area(geom, expected): actual = shapely.build_area(geom) assert actual is not expected assert actual == expected def test_make_valid_none(): actual = shapely.make_valid(None) assert actual is None @pytest.mark.parametrize( "geom,expected", [ (point, point), # a valid geometry stays the same (but is copied) # an L shaped polygon without area is converted to a multilinestring ( Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]), MultiLineString([((1, 1), (1, 2)), ((0, 0), (1, 1))]), ), # a polygon with self-intersection (bowtie) is converted into polygons ( Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]), MultiPolygon( [ Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]), Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]), ] ), ), (empty, empty), ([empty], [empty]), ], ) def test_make_valid(geom, expected): actual = shapely.make_valid(geom) assert actual is not expected # normalize needed to handle variation in output across GEOS versions assert shapely.normalize(actual) == expected @pytest.mark.parametrize( "geom,expected", [ (all_types, all_types), # first polygon is valid, second polygon has self-intersection ( [ Polygon([(0, 0), (2, 2), (0, 2), (0, 0)]), Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]), ], [ Polygon([(0, 0), (2, 2), (0, 2), (0, 0)]), MultiPolygon( [ Polygon([(1, 1), (0, 0), (0, 2), (1, 1)]), Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]), ] ), ], ), ([point, None, empty], [point, None, empty]), ], ) def test_make_valid_1d(geom, expected): actual = shapely.make_valid(geom) # normalize needed to handle variation in output across GEOS versions assert np.all(shapely.normalize(actual) == shapely.normalize(expected)) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "geom,expected", [ (point, point), # a valid geometry stays the same (but is copied) # an L shaped polygon without area is converted to a linestring ( Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]), LineString([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]), ), # a polygon with self-intersection (bowtie) is converted into polygons ( Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]), MultiPolygon( [ Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]), Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]), ] ), ), (empty, empty), ([empty], [empty]), ], ) def test_make_valid_structure(geom, expected): actual = shapely.make_valid(geom, method="structure") assert actual is not expected # normalize needed to handle variation in output across GEOS versions assert shapely.normalize(actual) == expected @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "geom,expected", [ (point, point), # a valid geometry stays the same (but is copied) # an L shaped polygon without area is converted to Empty Polygon ( Polygon([(0, 0), (1, 1), (1, 2), (1, 1), (0, 0)]), Polygon(), ), # a polygon with self-intersection (bowtie) is converted into polygons ( Polygon([(0, 0), (2, 2), (2, 0), (0, 2), (0, 0)]), MultiPolygon( [ Polygon([(1, 1), (2, 2), (2, 0), (1, 1)]), Polygon([(0, 0), (0, 2), (1, 1), (0, 0)]), ] ), ), (empty, empty), ([empty], [empty]), ], ) def test_make_valid_structure_keep_collapsed_false(geom, expected): actual = shapely.make_valid(geom, method="structure", keep_collapsed=False) assert actual is not expected # normalize needed to handle variation in output across GEOS versions assert shapely.normalize(actual) == expected @pytest.mark.skipif(shapely.geos_version >= (3, 10, 0), reason="GEOS >= 3.10") def test_make_valid_structure_unsupported_geos(): with pytest.raises( ValueError, match="The 'structure' method is only available in GEOS >= 3.10.0" ): _ = shapely.make_valid(Point(), method="structure") @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "method, keep_collapsed, error_type, error", [ ( np.array(["linework", "structure"]), True, TypeError, "method only accepts scalar values", ), ( "linework", [True, False], TypeError, "keep_collapsed only accepts scalar values", ), ("unknown", True, ValueError, "Unknown method: unknown"), ( "linework", False, ValueError, "The 'linework' method does not support 'keep_collapsed=False'", ), ], ) def test_make_valid_invalid_params(method, keep_collapsed, error_type, error): with pytest.raises(error_type, match=error): _ = shapely.make_valid(Point(), method=method, keep_collapsed=keep_collapsed) @pytest.mark.parametrize( "geom,expected", [ (point, point), # a point is always in normalized form # order coordinates of linestrings and parts of multi-linestring ( MultiLineString([((1, 1), (0, 0)), ((1, 1), (1, 2))]), MultiLineString([((1, 1), (1, 2)), ((0, 0), (1, 1))]), ), ], ) def test_normalize(geom, expected): actual = shapely.normalize(geom) assert actual == expected def test_offset_curve_empty(): with ignore_invalid(shapely.geos_version < (3, 12, 0)): # Empty geometries emit an "invalid" warning # (see https://github.com/libgeos/geos/issues/515) actual = shapely.offset_curve(empty_line_string, 2.0) assert shapely.is_empty(actual) def test_offset_curve_distance_array(): # check that kwargs are passed through result = shapely.offset_curve([line_string, line_string], [-2.0, -3.0]) assert result[0] == shapely.offset_curve(line_string, -2.0) assert result[1] == shapely.offset_curve(line_string, -3.0) def test_offset_curve_kwargs(): # check that kwargs are passed through result1 = shapely.offset_curve( line_string, -2.0, quad_segs=2, join_style="mitre", mitre_limit=2.0 ) result2 = shapely.offset_curve(line_string, -2.0) assert result1 != result2 def test_offset_curve_non_scalar_kwargs(): msg = "only accepts scalar values" with pytest.raises(TypeError, match=msg): shapely.offset_curve([line_string, line_string], 1, quad_segs=np.array([8, 9])) with pytest.raises(TypeError, match=msg): shapely.offset_curve( [line_string, line_string], 1, join_style=["round", "bevel"] ) with pytest.raises(TypeError, match=msg): shapely.offset_curve([line_string, line_string], 1, mitre_limit=[5.0, 6.0]) def test_offset_curve_join_style_invalid(): with pytest.raises(ValueError, match="'invalid' is not a valid option"): shapely.offset_curve(line_string, 1.0, join_style="invalid") @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11") @pytest.mark.parametrize( "geom,expected", [ (LineString([(0, 0), (0, 0), (1, 0)]), LineString([(0, 0), (1, 0)])), ( LinearRing([(0, 0), (1, 2), (1, 2), (1, 3), (0, 0)]), LinearRing([(0, 0), (1, 2), (1, 3), (0, 0)]), ), ( Polygon([(0, 0), (0, 0), (1, 0), (1, 1), (1, 0), (0, 0)]), Polygon([(0, 0), (1, 0), (1, 1), (1, 0), (0, 0)]), ), ( Polygon( [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)], holes=[[(2, 2), (2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ), Polygon( [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)], holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ), ), ( MultiPolygon( [ Polygon([(0, 0), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), Polygon([(2, 2), (2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]), ] ), MultiPolygon( [ Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), Polygon([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]), ] ), ), # points are unchanged (point, point), (point_z, point_z), (multi_point, multi_point), # empty geometries are unchanged (empty_point, empty_point), (empty_line_string, empty_line_string), (empty, empty), (empty_polygon, empty_polygon), ], ) def test_remove_repeated_points(geom, expected): assert_geometries_equal(shapely.remove_repeated_points(geom, 0), expected) @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12") @pytest.mark.parametrize( "geom, tolerance", [[Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), 2]] ) def test_remove_repeated_points_invalid_result(geom, tolerance): # Requiring GEOS 3.12 instead of 3.11 # (GEOS 3.11 had a bug causing this to intermittently not fail) with pytest.raises(shapely.GEOSException, match="Invalid number of points"): shapely.remove_repeated_points(geom, tolerance) @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11") def test_remove_repeated_points_none(): assert shapely.remove_repeated_points(None, 1) is None assert shapely.remove_repeated_points([None], 1).tolist() == [None] geometry = LineString([(0, 0), (0, 0), (1, 1)]) expected = LineString([(0, 0), (1, 1)]) result = shapely.remove_repeated_points([None, geometry], 1) assert result[0] is None assert_geometries_equal(result[1], expected) @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11") @pytest.mark.parametrize("geom, tolerance", [("Not a geometry", 1), (1, 1)]) def test_remove_repeated_points_invalid_type(geom, tolerance): with pytest.raises(TypeError, match="One of the arguments is of incorrect type"): shapely.remove_repeated_points(geom, tolerance) @pytest.mark.parametrize( "geom,expected", [ (LineString([(0, 0), (1, 2)]), LineString([(1, 2), (0, 0)])), ( LinearRing([(0, 0), (1, 2), (1, 3), (0, 0)]), LinearRing([(0, 0), (1, 3), (1, 2), (0, 0)]), ), ( Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]), ), ( Polygon( [(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)], holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ), Polygon( [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)], holes=[[(2, 2), (4, 2), (4, 4), (2, 4), (2, 2)]], ), ), ( MultiLineString([[(0, 0), (1, 2)], [(3, 3), (4, 4)]]), MultiLineString([[(1, 2), (0, 0)], [(4, 4), (3, 3)]]), ), ( MultiPolygon( [ Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]), Polygon([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]), ] ), MultiPolygon( [ Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]), Polygon([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)]), ] ), ), # points are unchanged (point, point), (point_z, point_z), (multi_point, multi_point), # empty geometries are unchanged (empty_point, empty_point), (empty_line_string, empty_line_string), (empty, empty), (empty_polygon, empty_polygon), ], ) def test_reverse(geom, expected): assert_geometries_equal(shapely.reverse(geom), expected) def test_reverse_none(): assert shapely.reverse(None) is None assert shapely.reverse([None]).tolist() == [None] geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]) expected = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) result = shapely.reverse([None, geometry]) assert result[0] is None assert_geometries_equal(result[1], expected) @pytest.mark.parametrize("geom", ["Not a geometry", 1]) def test_reverse_invalid_type(geom): with pytest.raises(TypeError, match="One of the arguments is of incorrect type"): shapely.reverse(geom) @pytest.mark.parametrize( "geom,expected", [ # Point outside (Point(0, 0), GeometryCollection()), # Point inside (Point(15, 15), Point(15, 15)), # Point on boundary (Point(15, 10), GeometryCollection()), # Line outside (LineString([(0, 0), (-5, 5)]), GeometryCollection()), # Line inside (LineString([(15, 15), (16, 15)]), LineString([(15, 15), (16, 15)])), # Line on boundary (LineString([(10, 15), (10, 10), (15, 10)]), GeometryCollection()), # Line splitting rectangle (LineString([(10, 5), (25, 20)]), LineString([(15, 10), (20, 15)])), ], ) def test_clip_by_rect(geom, expected): actual = shapely.clip_by_rect(geom, 10, 10, 20, 20) assert_geometries_equal(actual, expected) @pytest.mark.parametrize( "geom, rect, expected", [ # Polygon hole (CCW) fully on rectangle boundary""" ( Polygon( ((0, 0), (0, 30), (30, 30), (30, 0), (0, 0)), holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))], ), (10, 10, 20, 20), GeometryCollection(), ), # Polygon hole (CW) fully on rectangle boundary""" ( Polygon( ((0, 0), (0, 30), (30, 30), (30, 0), (0, 0)), holes=[((10, 10), (10, 20), (20, 20), (20, 10), (10, 10))], ), (10, 10, 20, 20), GeometryCollection(), ), # Polygon fully within rectangle""" ( Polygon( ((1, 1), (1, 30), (30, 30), (30, 1), (1, 1)), holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))], ), (0, 0, 40, 40), Polygon( ((1, 1), (1, 30), (30, 30), (30, 1), (1, 1)), holes=[((10, 10), (20, 10), (20, 20), (10, 20), (10, 10))], ), ), # Polygon overlapping rectanglez ( Polygon( [(0, 0), (0, 30), (30, 30), (30, 0), (0, 0)], holes=[[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]], ), (5, 5, 15, 15), Polygon([(5, 5), (5, 15), (10, 15), (10, 10), (15, 10), (15, 5), (5, 5)]), ), ], ) def test_clip_by_rect_polygon(geom, rect, expected): actual = shapely.clip_by_rect(geom, *rect) assert_geometries_equal(actual, expected) @pytest.mark.parametrize("geometry", all_types) def test_clip_by_rect_array(geometry): if ( geometry.is_empty and shapely.get_type_id(geometry) == shapely.GeometryType.POINT and ( (geos39 and geos_version < (3, 9, 5)) or (geos310 and geos_version < (3, 10, 6)) or (geos311 and geos_version < (3, 11, 3)) ) ): # GEOS GH-913 with pytest.raises(GEOSException): shapely.clip_by_rect([geometry, geometry], 0.0, 0.0, 1.0, 1.0) return actual = shapely.clip_by_rect([geometry, geometry], 0.0, 0.0, 1.0, 1.0) assert actual.shape == (2,) assert actual[0] is None or isinstance(actual[0], Geometry) def test_clip_by_rect_missing(): actual = shapely.clip_by_rect(None, 0, 0, 1, 1) assert actual is None @pytest.mark.parametrize("geom", [empty, empty_line_string, empty_polygon]) def test_clip_by_rect_empty(geom): # TODO empty point actual = shapely.clip_by_rect(geom, 0, 0, 1, 1) assert actual == GeometryCollection() def test_clip_by_rect_non_scalar_kwargs(): msg = "only accepts scalar values" with pytest.raises(TypeError, match=msg): shapely.clip_by_rect([line_string, line_string], 0, 0, 1, np.array([0, 1])) def test_polygonize(): lines = [ LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), LineString([(1, 1), (1, 0)]), LineString([(1, 0), (0, 0)]), LineString([(5, 5), (6, 6)]), Point(0, 0), None, ] result = shapely.polygonize(lines) assert shapely.get_type_id(result) == 7 # GeometryCollection expected = GeometryCollection( [ Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]), Polygon([(1, 1), (0, 0), (0, 1), (1, 1)]), ] ) assert result == expected def test_polygonize_array(): lines = [ LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), ] expected = GeometryCollection([Polygon([(1, 1), (0, 0), (0, 1), (1, 1)])]) result = shapely.polygonize(np.array(lines)) assert isinstance(result, shapely.Geometry) assert result == expected result = shapely.polygonize(np.array([lines])) assert isinstance(result, np.ndarray) assert result.shape == (1,) assert result[0] == expected arr = np.array([lines, lines]) assert arr.shape == (2, 3) result = shapely.polygonize(arr) assert isinstance(result, np.ndarray) assert result.shape == (2,) assert result[0] == expected assert result[1] == expected arr = np.array([[lines, lines], [lines, lines], [lines, lines]]) assert arr.shape == (3, 2, 3) result = shapely.polygonize(arr) assert isinstance(result, np.ndarray) assert result.shape == (3, 2) for res in result.flatten(): assert res == expected def test_polygonize_array_axis(): lines = [ LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), ] arr = np.array([lines, lines]) # shape (2, 3) result = shapely.polygonize(arr, axis=1) assert result.shape == (2,) result = shapely.polygonize(arr, axis=0) assert result.shape == (3,) def test_polygonize_missing(): # set of geometries that is all missing result = shapely.polygonize([None, None]) assert result == GeometryCollection() def test_polygonize_full(): lines = [ None, LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), LineString([(1, 1), (1, 0)]), None, LineString([(1, 0), (0, 0)]), LineString([(5, 5), (6, 6)]), LineString([(1, 1), (100, 100)]), Point(0, 0), None, ] result = shapely.polygonize_full(lines) assert len(result) == 4 assert all(shapely.get_type_id(geom) == 7 for geom in result) # GeometryCollection polygons, cuts, dangles, invalid = result expected_polygons = GeometryCollection( [ Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]), Polygon([(1, 1), (0, 0), (0, 1), (1, 1)]), ] ) assert polygons == expected_polygons assert cuts == GeometryCollection() expected_dangles = GeometryCollection( [LineString([(1, 1), (100, 100)]), LineString([(5, 5), (6, 6)])] ) assert dangles == expected_dangles assert invalid == GeometryCollection() def test_polygonize_full_array(): lines = [ LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), ] expected = GeometryCollection([Polygon([(1, 1), (0, 0), (0, 1), (1, 1)])]) result = shapely.polygonize_full(np.array(lines)) assert len(result) == 4 assert all(isinstance(geom, shapely.Geometry) for geom in result) assert result[0] == expected assert all(geom == GeometryCollection() for geom in result[1:]) result = shapely.polygonize_full(np.array([lines])) assert len(result) == 4 assert all(isinstance(geom, np.ndarray) for geom in result) assert all(geom.shape == (1,) for geom in result) assert result[0][0] == expected assert all(geom[0] == GeometryCollection() for geom in result[1:]) arr = np.array([lines, lines]) assert arr.shape == (2, 3) result = shapely.polygonize_full(arr) assert len(result) == 4 assert all(isinstance(arr, np.ndarray) for arr in result) assert all(arr.shape == (2,) for arr in result) assert result[0][0] == expected assert result[0][1] == expected assert all(g == GeometryCollection() for geom in result[1:] for g in geom) arr = np.array([[lines, lines], [lines, lines], [lines, lines]]) assert arr.shape == (3, 2, 3) result = shapely.polygonize_full(arr) assert len(result) == 4 assert all(isinstance(arr, np.ndarray) for arr in result) assert all(arr.shape == (3, 2) for arr in result) for res in result[0].flatten(): assert res == expected for arr in result[1:]: for res in arr.flatten(): assert res == GeometryCollection() def test_polygonize_full_array_axis(): lines = [ LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0, 1)]), LineString([(0, 1), (1, 1)]), ] arr = np.array([lines, lines]) # shape (2, 3) result = shapely.polygonize_full(arr, axis=1) assert len(result) == 4 assert all(arr.shape == (2,) for arr in result) result = shapely.polygonize_full(arr, axis=0) assert len(result) == 4 assert all(arr.shape == (3,) for arr in result) def test_polygonize_full_missing(): # set of geometries that is all missing result = shapely.polygonize_full([None, None]) assert len(result) == 4 assert all(geom == GeometryCollection() for geom in result) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize("geometry", all_types) @pytest.mark.parametrize("max_segment_length", [-1, 0]) def test_segmentize_invalid_max_segment_length(geometry, max_segment_length): with pytest.raises(GEOSException, match="IllegalArgumentException"): shapely.segmentize(geometry, max_segment_length=max_segment_length) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize("geometry", all_types) def test_segmentize_max_segment_length_nan(geometry): actual = shapely.segmentize(geometry, max_segment_length=np.nan) assert actual is None @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "geometry", [empty, empty_point, empty_line_string, empty_polygon] ) def test_segmentize_empty(geometry): actual = shapely.segmentize(geometry, max_segment_length=5) assert_geometries_equal(actual, geometry) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize("geometry", [point, point_z, multi_point]) def test_segmentize_no_change(geometry): actual = shapely.segmentize(geometry, max_segment_length=5) assert_geometries_equal(actual, geometry) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") def test_segmentize_none(): assert shapely.segmentize(None, max_segment_length=5) is None @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") @pytest.mark.parametrize( "geometry,tolerance, expected", [ # tolerance greater than max edge length, no change ( LineString([(0, 0), (0, 10)]), 20, LineString([(0, 0), (0, 10)]), ), ( Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), 20, Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), ), # tolerance causes one vertex per segment ( LineString([(0, 0), (0, 10)]), 5, LineString([(0, 0), (0, 5), (0, 10)]), ), ( Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), 5, Polygon( [ (0, 0), (5, 0), (10, 0), (10, 5), (10, 10), (5, 10), (0, 10), (0, 5), (0, 0), ] ), ), # ensure input arrays are broadcast correctly ( [ LineString([(0, 0), (0, 10)]), LineString([(0, 0), (0, 2)]), ], 5, [ LineString([(0, 0), (0, 5), (0, 10)]), LineString([(0, 0), (0, 2)]), ], ), ( [ LineString([(0, 0), (0, 10)]), LineString([(0, 0), (0, 2)]), ], [5], [ LineString([(0, 0), (0, 5), (0, 10)]), LineString([(0, 0), (0, 2)]), ], ), ( [ LineString([(0, 0), (0, 10)]), LineString([(0, 0), (0, 2)]), ], [5, 1.5], [ LineString([(0, 0), (0, 5), (0, 10)]), LineString([(0, 0), (0, 1), (0, 2)]), ], ), ], ) def test_segmentize(geometry, tolerance, expected): actual = shapely.segmentize(geometry, tolerance) assert_geometries_equal(actual, expected) @pytest.mark.parametrize("geometry", all_types) def test_minimum_bounding_circle_all_types(geometry): actual = shapely.minimum_bounding_circle([geometry, geometry]) assert actual.shape == (2,) assert actual[0] is None or isinstance(actual[0], Geometry) actual = shapely.minimum_bounding_circle(None) assert actual is None @pytest.mark.parametrize( "geometry, expected", [ ( Polygon([(0, 5), (5, 10), (10, 5), (5, 0), (0, 5)]), shapely.buffer(Point(5, 5), 5), ), ( LineString([(1, 0), (1, 10)]), shapely.buffer(Point(1, 5), 5), ), ( MultiPoint([(2, 2), (4, 2)]), shapely.buffer(Point(3, 2), 1), ), ( Point(2, 2), Point(2, 2), ), ( GeometryCollection(), Polygon(), ), ], ) def test_minimum_bounding_circle(geometry, expected): actual = shapely.minimum_bounding_circle(geometry) assert_geometries_equal(actual, expected) @pytest.mark.parametrize("geometry", all_types) def test_oriented_envelope_all_types(geometry): actual = shapely.oriented_envelope([geometry, geometry]) assert actual.shape == (2,) assert actual[0] is None or isinstance(actual[0], Geometry) actual = shapely.oriented_envelope(None) assert actual is None @pytest.mark.parametrize( "func", [shapely.oriented_envelope, shapely.minimum_rotated_rectangle] ) @pytest.mark.parametrize( "geometry, expected", [ ( MultiPoint([(1.0, 1.0), (1.0, 5.0), (3.0, 6.0), (4.0, 2.0), (5.0, 5.0)]), Polygon([(1.0, 1.0), (1.0, 6.0), (5.0, 6.0), (5.0, 1.0), (1.0, 1.0)]), ), ( LineString([(1, 1), (5, 1), (10, 10)]), Polygon([(1, 1), (3, -1), (12, 8), (10, 10), (1, 1)]), ), ( Polygon([(1, 1), (15, 1), (5, 9), (1, 1)]), Polygon([(1.0, 1.0), (5.0, 9.0), (16.2, 3.4), (12.2, -4.6), (1.0, 1.0)]), ), ( LineString([(1, 1), (10, 1)]), LineString([(1, 1), (10, 1)]), ), ( Point(2, 2), Point(2, 2), ), ( GeometryCollection(), Polygon(), ), ], ) def test_oriented_envelope(geometry, expected, func): actual = func(geometry) assert_geometries_equal(actual, expected, normalize=True, tolerance=1e-3) @pytest.mark.skipif(shapely.geos_version >= (3, 12, 0), reason="GEOS >= 3.12") @pytest.mark.parametrize( "geometry, expected", [ ( MultiPoint([(1.0, 1.0), (1.0, 5.0), (3.0, 6.0), (4.0, 2.0), (5.0, 5.0)]), Polygon([(-0.2, 1.4), (1.5, 6.5), (5.1, 5.3), (3.4, 0.2), (-0.2, 1.4)]), ), ( LineString([(1, 1), (5, 1), (10, 10)]), Polygon([(1, 1), (3, -1), (12, 8), (10, 10), (1, 1)]), ), ( Polygon([(1, 1), (15, 1), (5, 9), (1, 1)]), Polygon([(1.0, 1.0), (1.0, 9.0), (15.0, 9.0), (15.0, 1.0), (1.0, 1.0)]), ), ( LineString([(1, 1), (10, 1)]), LineString([(1, 1), (10, 1)]), ), ( Point(2, 2), Point(2, 2), ), ( GeometryCollection(), Polygon(), ), ], ) def test_oriented_envelope_pre_geos_312(geometry, expected): # use private method (similar as direct shapely.lib.oriented_envelope) # to cover the C code for older GEOS versions actual = shapely.constructive._oriented_envelope_geos(geometry) assert_geometries_equal(actual, expected, normalize=True, tolerance=1e-3) def test_oriented_evelope_array_like(): # https://github.com/shapely/shapely/issues/1929 # because we have a custom python implementation, need to ensure this has # the same capabilities as numpy ufuncs to work with array-likes geometries = [Point(1, 1).buffer(1), Point(2, 2).buffer(1)] actual = shapely.oriented_envelope(ArrayLike(geometries)) assert isinstance(actual, ArrayLike) expected = shapely.oriented_envelope(geometries) assert_geometries_equal(np.asarray(actual), expected) @pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11") def test_concave_hull_kwargs(): p = Point(10, 10) mp = MultiPoint(p.buffer(5).exterior.coords[:] + p.buffer(4).exterior.coords[:]) result1 = shapely.concave_hull(mp, ratio=0.5) assert len(result1.interiors) == 0 result2 = shapely.concave_hull(mp, ratio=0.5, allow_holes=True) assert len(result2.interiors) == 1 result3 = shapely.concave_hull(mp, ratio=0) result4 = shapely.concave_hull(mp, ratio=1) assert shapely.get_num_coordinates(result4) < shapely.get_num_coordinates(result3) @pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10") class TestConstrainedDelaunayTriangulation: """ Only testing the number of triangles and their type here. This doesn't actually test the points in the resulting geometries. """ def test_poly(self): polys = shapely.constrained_delaunay_triangles( Polygon([(10, 10), (20, 40), (90, 90), (90, 10), (10, 10)]) ) assert len(polys.geoms) == 2 for p in polys.geoms: assert isinstance(p, Polygon) def test_multi_polygon(self): multipoly = MultiPolygon( [ Polygon(((50, 30), (60, 30), (100, 100), (50, 30))), Polygon(((10, 10), (20, 40), (90, 90), (90, 10), (10, 10))), ] ) polys = shapely.constrained_delaunay_triangles(multipoly) assert len(polys.geoms) == 3 for p in polys.geoms: assert isinstance(p, Polygon) def test_point(self): p = Point(1, 1) polys = shapely.constrained_delaunay_triangles(p) assert len(polys.geoms) == 0 def test_empty_poly(self): polys = shapely.constrained_delaunay_triangles(Polygon()) assert len(polys.geoms) == 0 @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12") def test_voronoi_polygons_ordered(): mp = MultiPoint([(3.0, 1.0), (3.0, 2.0), (1.0, 2.0), (1.0, 1.0)]) result = shapely.voronoi_polygons(mp, ordered=False) assert result.geoms[0].equals( Polygon([(-1, -1), (-1, 1.5), (2, 1.5), (2, -1), (-1, -1)]) ) result_ordered = shapely.voronoi_polygons(mp, ordered=True) assert result_ordered.geoms[0].equals( Polygon([(5, -1), (2, -1), (2, 1.5), (5, 1.5), (5, -1)]) ) @pytest.mark.skipif(shapely.geos_version >= (3, 12, 0), reason="GEOS >= 3.12") def test_voronoi_polygons_ordered_raise(): mp = MultiPoint([(3.0, 1.0), (3.0, 2.0), (1.0, 2.0), (1.0, 1.0)]) with pytest.raises( UnsupportedGEOSVersionError, match="Ordered Voronoi polygons require GEOS" ): shapely.voronoi_polygons(mp, ordered=True) @pytest.mark.parametrize("geometry", all_types) def test_maximum_inscribed_circle_all_types(geometry): if shapely.get_type_id(geometry) not in [3, 6]: # Maximum Inscribed Circle is only supported for (Multi)Polygon input with pytest.raises( GEOSException, match=( "Argument must be Polygonal or LinearRing|" # GEOS < 3.10.4 "Input geometry must be a Polygon or MultiPolygon|" "Operation not supported by GeometryCollection" ), ): shapely.maximum_inscribed_circle(geometry) return if geometry.is_empty: with pytest.raises( GEOSException, match="Empty input geometry is not supported" ): shapely.maximum_inscribed_circle(geometry) return actual = shapely.maximum_inscribed_circle([geometry, geometry]) assert actual.shape == (2,) assert actual[0] is None or isinstance(actual[0], Geometry) actual = shapely.maximum_inscribed_circle(None) assert actual is None @pytest.mark.parametrize( "geometry, expected", [ ( "POLYGON ((0 5, 5 10, 10 5, 5 0, 0 5))", "LINESTRING (5 5, 2.5 7.5)", ), ], ) def test_maximum_inscribed_circle(geometry, expected): geometry, expected = shapely.from_wkt(geometry), shapely.from_wkt(expected) actual = shapely.maximum_inscribed_circle(geometry) assert_geometries_equal(actual, expected) def test_maximum_inscribed_circle_empty(): geometry = shapely.from_wkt("POINT EMPTY") with pytest.raises( GEOSException, match=( "Argument must be Polygonal or LinearRing|" # GEOS < 3.10.4 "Input geometry must be a Polygon or MultiPolygon" ), ): shapely.maximum_inscribed_circle(geometry) geometry = shapely.from_wkt("POLYGON EMPTY") with pytest.raises(GEOSException, match="Empty input geometry is not supported"): shapely.maximum_inscribed_circle(geometry) def test_maximum_inscribed_circle_invalid_tolerance(): geometry = shapely.from_wkt("POLYGON ((0 5, 5 10, 10 5, 5 0, 0 5))") with pytest.raises(ValueError, match="'tolerance' should be positive"): shapely.maximum_inscribed_circle(geometry, tolerance=-1) @pytest.mark.parametrize("geometry", all_types) def test_orient_polygons_all_types(geometry): actual = shapely.orient_polygons([geometry, geometry]) assert actual.shape == (2,) assert isinstance(actual[0], Geometry) actual = shapely.orient_polygons(None) assert actual is None def test_orient_polygons(): # polygon with both shell and hole having clockwise orientation polygon = Polygon( [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)], holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ) result = shapely.orient_polygons(polygon) assert result.exterior.is_ccw assert not result.interiors[0].is_ccw result = shapely.orient_polygons(polygon, exterior_cw=True) assert not result.exterior.is_ccw assert result.interiors[0].is_ccw # in a MultiPolygon mp = MultiPolygon([polygon, polygon]) result = shapely.orient_polygons(mp) assert len(result.geoms) == 2 for geom in result.geoms: assert geom.exterior.is_ccw assert not geom.interiors[0].is_ccw result = shapely.orient_polygons([mp], exterior_cw=True)[0] assert len(result.geoms) == 2 for geom in result.geoms: assert not geom.exterior.is_ccw assert geom.interiors[0].is_ccw # in a GeometryCollection gc = GeometryCollection([Point(1, 1), polygon, mp]) result = shapely.orient_polygons(gc) assert len(result.geoms) == 3 assert result.geoms[0] == Point(1, 1) assert result.geoms[1] == shapely.orient_polygons(polygon) assert result.geoms[2] == shapely.orient_polygons(mp) def test_orient_polygons_non_polygonal_input(): arr = np.array([Point(0, 0), LineString([(0, 0), (1, 1)]), None]) result = shapely.orient_polygons(arr) assert_geometries_equal(result, arr) def test_orient_polygons_array(): # because we have a custom python implementation for older GEOS, need to # ensure this has the same capabilities as numpy ufuncs to work with array-likes polygon = Polygon( [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)], holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ) geometries = np.array([[polygon] * 3] * 2) actual = shapely.orient_polygons(geometries) assert isinstance(actual, np.ndarray) assert actual.shape == (2, 3) expected = shapely.orient_polygons(polygon) assert (actual == expected).all() def test_orient_polygons_array_like(): # because we have a custom python implementation for older GEOS, need to # ensure this has the same capabilities as numpy ufuncs to work with array-likes polygon = Polygon( [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)], holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]], ) geometries = [polygon, Point(2, 2).buffer(1)] actual = shapely.orient_polygons(ArrayLike(geometries)) assert isinstance(actual, ArrayLike) expected = shapely.orient_polygons(geometries) assert_geometries_equal(np.asarray(actual), expected) def test_buffer_deprecate_positional(): with pytest.deprecated_call( match="positional argument `quad_segs` for `buffer` is deprecated" ): shapely.buffer(point, 1.0, 8) with pytest.deprecated_call( match="positional arguments `quad_segs` and `cap_style` " "for `buffer` are deprecated" ): shapely.buffer(point, 1.0, 8, "round") with pytest.deprecated_call( match="positional arguments `quad_segs`, `cap_style`, and `join_style` " "for `buffer` are deprecated" ): shapely.buffer(point, 1.0, 8, "round", "round") with pytest.deprecated_call(): shapely.buffer(point, 1.0, 8, "round", "round", 5.0) with pytest.deprecated_call(): shapely.buffer(point, 1.0, 8, "round", "round", 5.0, False) def test_offset_curve_deprecate_positional(): with pytest.deprecated_call( match="positional argument `quad_segs` for `offset_curve` is deprecated" ): shapely.offset_curve(line_string, 1.0, 8) with pytest.deprecated_call( match="positional arguments `quad_segs` and `join_style` " "for `offset_curve` are deprecated" ): shapely.offset_curve(line_string, 1.0, 8, "round") with pytest.deprecated_call( match="positional arguments `quad_segs`, `join_style`, and `mitre_limit` " "for `offset_curve` are deprecated" ): shapely.offset_curve(line_string, 1.0, 8, "round", 5.0) def test_simplify_deprecate_positional(): with pytest.deprecated_call( match="positional argument `preserve_topology` for `simplify` is deprecated" ): shapely.simplify(line_string, 1.0, True) def test_voronoi_polygons_deprecate_positional(): with pytest.deprecated_call( match="positional argument `extend_to` for `voronoi_polygons` is deprecated" ): shapely.voronoi_polygons(multi_point, 0.0, None) with pytest.deprecated_call( match="positional arguments `extend_to` and `only_edges` " "for `voronoi_polygons` are deprecated" ): shapely.voronoi_polygons(multi_point, 0.0, None, False) with pytest.deprecated_call( match="positional arguments `extend_to`, `only_edges`, and `ordered` " "for `voronoi_polygons` are deprecated" ): shapely.voronoi_polygons(multi_point, 0.0, None, False, False)
Memory