refactor: excel parse

This commit is contained in:
Blizzard
2026-04-16 10:01:11 +08:00
parent 680ecc320f
commit f62f95ec02
7941 changed files with 2899112 additions and 0 deletions
@@ -0,0 +1,125 @@
from contextlib import contextmanager
import numpy as np
import pytest
import shapely
shapely20_todo = pytest.mark.xfail(
strict=False, reason="Not yet implemented for Shapely 2.0"
)
point_polygon_testdata = (
shapely.points(np.arange(6), np.arange(6)),
shapely.box(2, 2, 4, 4),
)
point = shapely.Point(2, 3)
line_string = shapely.LineString([(0, 0), (1, 0), (1, 1)])
linear_ring = shapely.LinearRing([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
polygon = shapely.Polygon([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
multi_point = shapely.MultiPoint([(0, 0), (1, 2)])
multi_line_string = shapely.MultiLineString([[(0, 0), (1, 2)]])
multi_polygon = shapely.multipolygons(
[
[(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)],
[(2.1, 2.1), (2.2, 2.1), (2.2, 2.2), (2.1, 2.2), (2.1, 2.1)],
]
)
geometry_collection = shapely.GeometryCollection(
[shapely.Point(51, -1), shapely.LineString([(52, -1), (49, 2)])]
)
point_z = shapely.Point(2, 3, 4)
line_string_z = shapely.LineString([(0, 0, 4), (1, 0, 4), (1, 1, 4)])
polygon_z = shapely.Polygon([(0, 0, 4), (2, 0, 4), (2, 2, 4), (0, 2, 4), (0, 0, 4)])
geometry_collection_z = shapely.GeometryCollection([point_z, line_string_z])
polygon_with_hole = shapely.Polygon(
[(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
holes=[[(2, 2), (2, 4), (4, 4), (4, 2), (2, 2)]],
)
empty_point = shapely.from_wkt("POINT EMPTY")
empty_point_z = shapely.from_wkt("POINT Z EMPTY")
empty_line_string = shapely.from_wkt("LINESTRING EMPTY")
empty_line_string_z = shapely.from_wkt("LINESTRING Z EMPTY")
empty_polygon = shapely.from_wkt("POLYGON EMPTY")
empty = shapely.from_wkt("GEOMETRYCOLLECTION EMPTY")
multi_point_z = shapely.MultiPoint([(0, 0, 4), (1, 2, 4)])
multi_line_string_z = shapely.MultiLineString([[(0, 0, 4), (1, 2, 4)]])
multi_polygon_z = shapely.multipolygons(
[
[(0, 0, 4), (1, 0, 4), (1, 1, 4), (0, 1, 4), (0, 0, 4)],
[(2.1, 2.1, 4), (2.2, 2.1, 4), (2.2, 2.2, 4), (2.1, 2.2, 4), (2.1, 2.1, 4)],
]
)
polygon_with_hole_z = shapely.Polygon(
[(0, 0, 4), (0, 10, 4), (10, 10, 4), (10, 0, 4), (0, 0, 4)],
holes=[[(2, 2, 4), (2, 4, 4), (4, 4, 4), (4, 2, 4), (2, 2, 4)]],
)
all_types = (
point,
line_string,
linear_ring,
polygon,
multi_point,
multi_line_string,
multi_polygon,
geometry_collection,
empty,
)
all_types_z = (
point_z,
line_string_z,
polygon_z,
multi_point_z,
multi_line_string_z,
multi_polygon_z,
polygon_with_hole_z,
geometry_collection_z,
empty_point_z,
empty_line_string_z,
)
@contextmanager
def ignore_invalid(condition=True):
if condition:
with np.errstate(invalid="ignore"):
yield
else:
yield
with ignore_invalid():
line_string_nan = shapely.LineString([(np.nan, np.nan), (np.nan, np.nan)])
class ArrayLike:
"""
Simple numpy Array like class that implements the
ufunc protocol.
"""
def __init__(self, array):
self._array = np.asarray(array)
def __len__(self):
return len(self._array)
def __getitem(self, key):
return self._array[key]
def __iter__(self):
return self._array.__iter__()
def __array__(self):
return np.asarray(self._array)
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
if method == "__call__":
inputs = [
arg._array if isinstance(arg, self.__class__) else arg for arg in inputs
]
return self.__class__(ufunc(*inputs, **kwargs))
else:
return NotImplemented
@@ -0,0 +1,82 @@
import numpy as np
import pytest
from shapely import GeometryCollection, LineString, Point, wkt
from shapely.geometry import shape
@pytest.fixture()
def geometrycollection_geojson():
return {
"type": "GeometryCollection",
"geometries": [
{"type": "Point", "coordinates": (0, 3, 0)},
{"type": "LineString", "coordinates": ((2, 0), (1, 0))},
],
}
@pytest.mark.parametrize(
"geom",
[
GeometryCollection(),
shape({"type": "GeometryCollection", "geometries": []}),
wkt.loads("GEOMETRYCOLLECTION EMPTY"),
],
)
def test_empty(geom):
assert geom.geom_type == "GeometryCollection"
assert geom.is_empty
assert len(geom.geoms) == 0
assert list(geom.geoms) == []
def test_empty_subgeoms():
geom = GeometryCollection([Point(), LineString()])
assert geom.geom_type == "GeometryCollection"
assert geom.is_empty
assert len(geom.geoms) == 2
assert list(geom.geoms) == [Point(), LineString()]
def test_child_with_deleted_parent():
# test that we can remove a collection while keeping
# children around
a = LineString([(0, 0), (1, 1), (1, 2), (2, 2)])
b = LineString([(0, 0), (1, 1), (2, 1), (2, 2)])
collection = a.intersection(b)
child = collection.geoms[0]
# delete parent of child
del collection
# access geometry, this should not seg fault as 1.2.15 did
assert child.wkt is not None
def test_from_geojson(geometrycollection_geojson):
geom = shape(geometrycollection_geojson)
assert geom.geom_type == "GeometryCollection"
assert len(geom.geoms) == 2
geom_types = [g.geom_type for g in geom.geoms]
assert "Point" in geom_types
assert "LineString" in geom_types
def test_geointerface(geometrycollection_geojson):
geom = shape(geometrycollection_geojson)
assert geom.__geo_interface__ == geometrycollection_geojson
def test_len_raises(geometrycollection_geojson):
geom = shape(geometrycollection_geojson)
with pytest.raises(TypeError):
len(geom)
def test_numpy_object_array():
geom = GeometryCollection([LineString([(0, 0), (1, 1)])])
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
@@ -0,0 +1,102 @@
import numpy as np
import pytest
from shapely import LineString
from shapely.tests.common import line_string, line_string_z, point, point_z
class TestCoords:
"""
Shapely assumes contiguous C-order float64 data for internal ops.
Data should be converted to contiguous float64 if numpy exists.
c9a0707 broke this a little bit.
"""
def test_data_promotion(self):
coords = np.array([[12, 34], [56, 78]], dtype=np.float32)
processed_coords = np.array(LineString(coords).coords)
assert coords.tolist() == processed_coords.tolist()
def test_data_destriding(self):
coords = np.array([[12, 34], [56, 78]], dtype=np.float32)
# Easy way to introduce striding: reverse list order
processed_coords = np.array(LineString(coords[::-1]).coords)
assert coords[::-1].tolist() == processed_coords.tolist()
class TestCoordsGetItem:
def test_index_2d_coords(self):
c = [(float(x), float(-x)) for x in range(4)]
g = LineString(c)
for i in range(-4, 4):
assert g.coords[i] == c[i]
with pytest.raises(IndexError):
g.coords[4]
with pytest.raises(IndexError):
g.coords[-5]
def test_index_3d_coords(self):
c = [(float(x), float(-x), float(x * 2)) for x in range(4)]
g = LineString(c)
for i in range(-4, 4):
assert g.coords[i] == c[i]
with pytest.raises(IndexError):
g.coords[4]
with pytest.raises(IndexError):
g.coords[-5]
def test_index_coords_misc(self):
g = LineString() # empty
with pytest.raises(IndexError):
g.coords[0]
with pytest.raises(TypeError):
g.coords[0.0]
def test_slice_2d_coords(self):
c = [(float(x), float(-x)) for x in range(4)]
g = LineString(c)
assert g.coords[1:] == c[1:]
assert g.coords[:-1] == c[:-1]
assert g.coords[::-1] == c[::-1]
assert g.coords[::2] == c[::2]
assert g.coords[:4] == c[:4]
assert g.coords[4:] == c[4:] == []
def test_slice_3d_coords(self):
c = [(float(x), float(-x), float(x * 2)) for x in range(4)]
g = LineString(c)
assert g.coords[1:] == c[1:]
assert g.coords[:-1] == c[:-1]
assert g.coords[::-1] == c[::-1]
assert g.coords[::2] == c[::2]
assert g.coords[:4] == c[:4]
assert g.coords[4:] == c[4:] == []
class TestXY:
"""New geometry/coordseq method 'xy' makes numpy interop easier"""
def test_arrays(self):
x, y = LineString([(0, 0), (1, 1)]).xy
assert len(x) == 2
assert list(x) == [0.0, 1.0]
assert len(y) == 2
assert list(y) == [0.0, 1.0]
@pytest.mark.parametrize("geom", [point, point_z, line_string, line_string_z])
def test_coords_array_copy(geom):
"""Test CoordinateSequence.__array__ method."""
coord_seq = geom.coords
assert np.array(coord_seq) is not np.array(coord_seq)
assert np.array(coord_seq, copy=True) is not np.array(coord_seq, copy=True)
# Behaviour of copy=False is different between NumPy 1.x and 2.x
if int(np.version.short_version.split(".", 1)[0]) >= 2:
with pytest.raises(ValueError, match="A copy is always created"):
np.array(coord_seq, copy=False)
else:
assert np.array(coord_seq, copy=False) is np.array(coord_seq, copy=False)
@@ -0,0 +1,117 @@
from decimal import Decimal
import pytest
from shapely import (
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
items2d = [
[(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)],
[(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)],
]
items2d_mixed = [
[
(Decimal(0.0), Decimal(0.0)),
(Decimal(70.0), 120.0),
(140.0, Decimal(0.0)),
(0.0, 0.0),
],
[
(Decimal(60.0), Decimal(80.0)),
(Decimal(80.0), 80.0),
(70.0, Decimal(60.0)),
(60.0, 80.0),
],
]
items2d_decimal = [
[
(Decimal(0.0), Decimal(0.0)),
(Decimal(70.0), Decimal(120.0)),
(Decimal(140.0), Decimal(0.0)),
(Decimal(0.0), Decimal(0.0)),
],
[
(Decimal(60.0), Decimal(80.0)),
(Decimal(80.0), Decimal(80.0)),
(Decimal(70.0), Decimal(60.0)),
(Decimal(60.0), Decimal(80.0)),
],
]
items3d = [
[(0.0, 0.0, 1), (70.0, 120.0, 2), (140.0, 0.0, 3), (0.0, 0.0, 1)],
[(60.0, 80.0, 1), (80.0, 80.0, 2), (70.0, 60.0, 3), (60.0, 80.0, 1)],
]
items3d_mixed = [
[
(Decimal(0.0), Decimal(0.0), Decimal(1)),
(Decimal(70.0), 120.0, Decimal(2)),
(140.0, Decimal(0.0), 3),
(0.0, 0.0, 1),
],
[
(Decimal(60.0), Decimal(80.0), Decimal(1)),
(Decimal(80.0), 80.0, 2),
(70.0, Decimal(60.0), Decimal(3)),
(60.0, 80.0, 1),
],
]
items3d_decimal = [
[
(Decimal(0.0), Decimal(0.0), Decimal(1)),
(Decimal(70.0), Decimal(120.0), Decimal(2)),
(Decimal(140.0), Decimal(0.0), Decimal(3)),
(Decimal(0.0), Decimal(0.0), Decimal(1)),
],
[
(Decimal(60.0), Decimal(80.0), Decimal(1)),
(Decimal(80.0), Decimal(80.0), Decimal(2)),
(Decimal(70.0), Decimal(60.0), Decimal(3)),
(Decimal(60.0), Decimal(80.0), Decimal(1)),
],
]
all_geoms = [
[
Point(items[0][0]),
Point(*items[0][0]),
MultiPoint(items[0]),
LinearRing(items[0]),
LineString(items[0]),
MultiLineString(items),
Polygon(items[0]),
MultiPolygon(
[
Polygon(items[1]),
Polygon(items[0], holes=items[1:]),
]
),
GeometryCollection([Point(items[0][0]), Polygon(items[0])]),
]
for items in [
items2d,
items2d_mixed,
items2d_decimal,
items3d,
items3d_mixed,
items3d_decimal,
]
]
@pytest.mark.parametrize("geoms", list(zip(*all_geoms)))
def test_decimal(geoms):
assert geoms[0] == geoms[1] == geoms[2]
assert geoms[3] == geoms[4] == geoms[5]
@@ -0,0 +1,98 @@
import math
import numpy as np
import pytest
from shapely import (
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
from shapely.geometry import mapping, shape
from shapely.geometry.base import BaseGeometry, EmptyGeometry
def empty_generator():
return iter([])
class TestEmptiness:
def test_empty_class(self):
with pytest.warns(FutureWarning):
g = EmptyGeometry()
assert g.is_empty
def test_empty_base(self):
with pytest.warns(FutureWarning):
g = BaseGeometry()
assert g.is_empty
def test_empty_point(self):
assert Point().is_empty
def test_empty_multipoint(self):
assert MultiPoint().is_empty
def test_empty_geometry_collection(self):
assert GeometryCollection().is_empty
def test_empty_linestring(self):
assert LineString().is_empty
assert LineString(None).is_empty
assert LineString([]).is_empty
assert LineString(empty_generator()).is_empty
def test_empty_multilinestring(self):
assert MultiLineString([]).is_empty
def test_empty_polygon(self):
assert Polygon().is_empty
assert Polygon(None).is_empty
assert Polygon([]).is_empty
assert Polygon(empty_generator()).is_empty
def test_empty_multipolygon(self):
assert MultiPolygon([]).is_empty
def test_empty_linear_ring(self):
assert LinearRing().is_empty
assert LinearRing(None).is_empty
assert LinearRing([]).is_empty
assert LinearRing(empty_generator()).is_empty
def test_numpy_object_array():
geoms = [Point(), GeometryCollection()]
arr = np.empty(2, object)
arr[:] = geoms
def test_shape_empty():
empty_mp = MultiPolygon()
empty_json = mapping(empty_mp)
empty_shape = shape(empty_json)
assert empty_shape.is_empty
@pytest.mark.parametrize(
"geom",
[
Point(),
LineString(),
Polygon(),
MultiPoint(),
MultiLineString(),
MultiPolygon(),
GeometryCollection(),
LinearRing(),
],
)
def test_empty_geometry_bounds(geom):
"""The bounds of an empty geometry is a tuple of NaNs"""
assert len(geom.bounds) == 4
assert all(math.isnan(v) for v in geom.bounds)
@@ -0,0 +1,237 @@
import numpy as np
import pytest
import shapely
from shapely import LinearRing, LineString, MultiLineString, Point, Polygon
from shapely.tests.common import all_types, all_types_z, ignore_invalid
@pytest.mark.parametrize("geom", all_types + all_types_z)
def test_equality(geom):
assert geom == geom
transformed = shapely.transform(geom, lambda x: x, include_z=True)
assert geom == transformed
assert not (geom != transformed)
@pytest.mark.parametrize(
"left, right",
[
# (slightly) different coordinate values
(LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 2)])),
(LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 1 + 1e-12)])),
# different coordinate order
(LineString([(0, 0), (1, 1)]), LineString([(1, 1), (0, 0)])),
# different number of coordinates (but spatially equal)
(LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 1), (1, 1)])),
(LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0.5, 0.5), (1, 1)])),
# different order of sub-geometries
(
MultiLineString([[(1, 1), (2, 2)], [(2, 2), (3, 3)]]),
MultiLineString([[(2, 2), (3, 3)], [(1, 1), (2, 2)]]),
),
],
)
def test_equality_false(left, right):
assert left != right
with ignore_invalid():
cases1 = [
(LineString([(0, 1), (2, np.nan)]), LineString([(0, 1), (2, np.nan)])),
(
LineString([(0, 1), (np.nan, np.nan)]),
LineString([(0, 1), (np.nan, np.nan)]),
),
(LineString([(np.nan, 1), (2, 3)]), LineString([(np.nan, 1), (2, 3)])),
(LineString([(0, np.nan), (2, 3)]), LineString([(0, np.nan), (2, 3)])),
(
LineString([(np.nan, np.nan), (np.nan, np.nan)]),
LineString([(np.nan, np.nan), (np.nan, np.nan)]),
),
# NaN as explicit Z coordinate
# TODO: if first z is NaN -> considered as 2D -> tested below explicitly
# (
# LineString([(0, 1, np.nan), (2, 3, np.nan)]),
# LineString([(0, 1, np.nan), (2, 3, np.nan)]),
# ),
(
LineString([(0, 1, 2), (2, 3, np.nan)]),
LineString([(0, 1, 2), (2, 3, np.nan)]),
),
# (
# LineString([(0, 1, np.nan), (2, 3, 4)]),
# LineString([(0, 1, np.nan), (2, 3, 4)]),
# ),
]
@pytest.mark.parametrize("left, right", cases1)
def test_equality_with_nan(left, right):
# TODO currently those evaluate as not equal, but we are considering to change this
# assert left == right
assert not (left == right)
# assert not (left != right)
assert left != right
with ignore_invalid():
cases2 = [
(
LineString([(0, 1, np.nan), (2, 3, np.nan)]),
LineString([(0, 1, np.nan), (2, 3, np.nan)]),
),
(
LineString([(0, 1, np.nan), (2, 3, 4)]),
LineString([(0, 1, np.nan), (2, 3, 4)]),
),
]
@pytest.mark.parametrize("left, right", cases2)
def test_equality_with_nan_z(left, right):
# TODO: those are currently considered equal because z dimension is ignored
if shapely.geos_version < (3, 12, 0):
assert left == right
assert not (left != right)
else:
# on GEOS main z dimension is not ignored -> NaNs cause inequality
assert left != right
with ignore_invalid():
cases3 = [
(LineString([(0, np.nan), (2, 3)]), LineString([(0, 1), (2, 3)])),
(LineString([(0, 1), (2, np.nan)]), LineString([(0, 1), (2, 3)])),
(LineString([(0, 1, np.nan), (2, 3, 4)]), LineString([(0, 1, 2), (2, 3, 4)])),
(LineString([(0, 1, 2), (2, 3, np.nan)]), LineString([(0, 1, 2), (2, 3, 4)])),
]
@pytest.mark.parametrize("left, right", cases3)
def test_equality_with_nan_false(left, right):
assert left != right
def test_equality_with_nan_z_false():
with ignore_invalid():
left = LineString([(0, 1, np.nan), (2, 3, np.nan)])
right = LineString([(0, 1, np.nan), (2, 3, 4)])
if shapely.geos_version < (3, 10, 0):
# GEOS <= 3.9 fill the NaN with 0, so the z dimension is different
# assert left != right
# however, has_z still returns False, so z dimension is ignored in .coords
assert left == right
elif shapely.geos_version < (3, 12, 0):
# GEOS 3.10-3.11 ignore NaN for Z also when explicitly created with 3D
# and so the geometries are considered as 2D (and thus z dimension is ignored)
assert left == right
else:
assert left != right
def test_equality_z():
# different dimensionality
geom1 = Point(0, 1)
geom2 = Point(0, 1, 0)
assert geom1 != geom2
# different dimensionality with NaN z
geom2 = Point(0, 1, np.nan)
if shapely.geos_version < (3, 10, 0):
# GEOS < 3.8 fill the NaN with 0, so the z dimension is different
# assert geom1 != geom2
# however, has_z still returns False, so z dimension is ignored in .coords
assert geom1 == geom2
elif shapely.geos_version < (3, 12, 0):
# GEOS 3.10-3.11 ignore NaN for Z also when explicitly created with 3D
# and so the geometries are considered as 2D (and thus z dimension is ignored)
assert geom1 == geom2
else:
assert geom1 != geom2
def test_equality_exact_type():
# geometries with different type but same coord seq are not equal
geom1 = LineString([(0, 0), (1, 1), (0, 1), (0, 0)])
geom2 = LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)])
geom3 = Polygon([(0, 0), (1, 1), (0, 1), (0, 0)])
assert geom1 != geom2
assert geom1 != geom3
assert geom2 != geom3
# empty with different type
geom1 = shapely.from_wkt("POINT EMPTY")
geom2 = shapely.from_wkt("LINESTRING EMPTY")
assert geom1 != geom2
def test_equality_polygon():
# different exterior rings
geom1 = shapely.from_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")
geom2 = shapely.from_wkt("POLYGON ((0 0, 10 0, 10 10, 0 15, 0 0))")
assert geom1 != geom2
# different number of holes
geom1 = shapely.from_wkt(
"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1))"
)
geom2 = shapely.from_wkt(
"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1), (3 3, 4 3, 4 4, 3 3))"
)
assert geom1 != geom2
# different order of holes
geom1 = shapely.from_wkt(
"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (3 3, 4 3, 4 4, 3 3), (1 1, 2 1, 2 2, 1 1))"
)
geom2 = shapely.from_wkt(
"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1), (3 3, 4 3, 4 4, 3 3))"
)
assert geom1 != geom2
@pytest.mark.parametrize("geom", all_types)
def test_comparison_notimplemented(geom):
# comparing to a non-geometry class should return NotImplemented in __eq__
# to ensure proper delegation to other (eg to ensure comparison of scalar
# with array works)
# https://github.com/shapely/shapely/issues/1056
assert geom.__eq__(1) is NotImplemented
# with array
arr = np.array([geom, geom], dtype=object)
result = arr == geom
assert isinstance(result, np.ndarray)
assert result.all()
result = geom == arr
assert isinstance(result, np.ndarray)
assert result.all()
result = arr != geom
assert isinstance(result, np.ndarray)
assert not result.any()
result = geom != arr
assert isinstance(result, np.ndarray)
assert not result.any()
def test_comparison_not_supported():
geom1 = Point(1, 1)
geom2 = Point(2, 2)
with pytest.raises(TypeError, match="not supported between instances"):
geom1 > geom2
with pytest.raises(TypeError, match="not supported between instances"):
geom1 < geom2
with pytest.raises(TypeError, match="not supported between instances"):
geom1 >= geom2
with pytest.raises(TypeError, match="not supported between instances"):
geom1 <= geom2
@@ -0,0 +1,116 @@
import pytest
from shapely import Point, Polygon
from shapely.geos import geos_version
def test_format_invalid():
# check invalid spec formats
pt = Point(1, 2)
test_list = [
("5G", ValueError, "invalid format specifier"),
(".f", ValueError, "invalid format specifier"),
("0.2e", ValueError, "invalid format specifier"),
(".1x", ValueError, "hex representation does not specify precision"),
]
for format_spec, err, match in test_list:
with pytest.raises(err, match=match):
format(pt, format_spec)
def test_format_point():
# example coordinate data
xy1 = (0.12345678901234567, 1.2345678901234567e10)
xy2 = (-169.910918, -18.997564)
xyz3 = (630084, 4833438, 76)
# list of tuples to test; see structure at top of the for-loop
test_list = [
(".0f", xy1, "POINT (0 12345678901)", True),
(".1f", xy1, "POINT (0.1 12345678901.2)", True),
("0.2f", xy2, "POINT (-169.91 -19.00)", True),
(".3F", (float("inf"), -float("inf")), "POINT (INF -INF)", True),
]
if geos_version < (3, 10, 0):
# 'g' format varies depending on GEOS version
test_list += [
(".1g", xy1, "POINT (0.1 1e+10)", True),
(".6G", xy1, "POINT (0.123457 1.23457E+10)", True),
("0.12g", xy1, "POINT (0.123456789012 12345678901.2)", True),
("0.4g", xy2, "POINT (-169.9 -19)", True),
]
else:
test_list += [
(".1g", xy1, "POINT (0.1 12345678901.2)", False),
(".6G", xy1, "POINT (0.123457 12345678901.234568)", False),
("0.12g", xy1, "POINT (0.123456789012 12345678901.234568)", False),
("g", xy2, "POINT (-169.910918 -18.997564)", False),
("0.2g", xy2, "POINT (-169.91 -19)", False),
]
# without precisions test GEOS rounding_precision=-1; different than Python
test_list += [
("f", (1, 2), f"POINT ({1:.16f} {2:.16f})", False),
("F", xyz3, "POINT Z ({:.16f} {:.16f} {:.16f})".format(*xyz3), False),
("g", xyz3, "POINT Z (630084 4833438 76)", False),
]
for format_spec, coords, expt_wkt, same_python_float in test_list:
pt = Point(*coords)
# basic checks
assert f"{pt}" == pt.wkt
assert format(pt, "") == pt.wkt
assert format(pt, "x") == pt.wkb_hex.lower()
assert format(pt, "X") == pt.wkb_hex
# check formatted WKT to expected
assert format(pt, format_spec) == expt_wkt, format_spec
# check Python's format consistency
text_coords = expt_wkt[expt_wkt.index("(") + 1 : expt_wkt.index(")")]
is_same = []
for coord, expt_coord in zip(coords, text_coords.split()):
py_fmt_float = format(float(coord), format_spec)
if same_python_float:
assert py_fmt_float == expt_coord, format_spec
else:
is_same.append(py_fmt_float == expt_coord)
if not same_python_float:
assert not all(is_same), f"{format_spec!r} with {expt_wkt}"
def test_format_polygon():
# check basic cases
poly = Point(0, 0).buffer(10, 2)
assert f"{poly}" == poly.wkt
assert format(poly, "") == poly.wkt
assert format(poly, "x") == poly.wkb_hex.lower()
assert format(poly, "X") == poly.wkb_hex
# Use f-strings with extra characters and rounding precision
if geos_version < (3, 13, 0):
assert f"<{poly:.2f}>" == (
"<POLYGON ((10.00 0.00, 7.07 -7.07, 0.00 -10.00, -7.07 -7.07, "
"-10.00 -0.00, -7.07 7.07, -0.00 10.00, 7.07 7.07, 10.00 0.00))>"
)
else:
assert f"<{poly:.2f}>" == (
"<POLYGON ((10.00 0.00, 7.07 -7.07, 0.00 -10.00, -7.07 -7.07, "
"-10.00 0.00, -7.07 7.07, 0.00 10.00, 7.07 7.07, 10.00 0.00))>"
)
# 'g' format varies depending on GEOS version
if geos_version < (3, 10, 0):
assert f"{poly:.2G}" == (
"POLYGON ((10 0, 7.1 -7.1, 1.6E-14 -10, -7.1 -7.1, "
"-10 -3.2E-14, -7.1 7.1, -4.6E-14 10, 7.1 7.1, 10 0))"
)
else:
assert f"{poly:.2G}" == (
"POLYGON ((10 0, 7.07 -7.07, 0 -10, -7.07 -7.07, "
"-10 0, -7.07 7.07, 0 10, 7.07 7.07, 10 0))"
)
# check empty
empty = Polygon()
assert f"{empty}" == "POLYGON EMPTY"
assert format(empty, "") == empty.wkt
assert format(empty, ".2G") == empty.wkt
assert format(empty, "x") == empty.wkb_hex.lower()
assert format(empty, "X") == empty.wkb_hex
@@ -0,0 +1,281 @@
import platform
import weakref
import numpy as np
import pytest
import shapely
from shapely import (
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
from shapely.errors import ShapelyDeprecationWarning
from shapely.testing import assert_geometries_equal
def test_polygon():
assert bool(Polygon()) is False
def test_linestring():
assert bool(LineString()) is False
def test_point():
assert bool(Point()) is False
def test_geometry_collection():
assert bool(GeometryCollection()) is False
geometries_all_types = [
Point(1, 1),
LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)]),
LineString([(0, 0), (1, 1), (0, 1), (0, 0)]),
Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]),
MultiPoint([(1, 1)]),
MultiLineString([[(0, 0), (1, 1), (0, 1), (0, 0)]]),
MultiPolygon([Polygon([(0, 0), (1, 1), (0, 1), (0, 0)])]),
GeometryCollection([Point(1, 1)]),
]
@pytest.mark.skipif(
platform.python_implementation() == "PyPy",
reason="Setting custom attributes doesn't fail on PyPy",
)
@pytest.mark.parametrize("geom", geometries_all_types)
def test_setattr_disallowed(geom):
with pytest.raises(AttributeError):
geom.name = "test"
@pytest.mark.parametrize("geom", geometries_all_types)
def test_weakrefable(geom):
_ = weakref.ref(geom)
def test_base_class_not_callable():
with pytest.raises(TypeError):
shapely.Geometry("POINT (1 1)")
def test_GeometryType_deprecated():
geom = Point(1, 1)
with pytest.warns(ShapelyDeprecationWarning):
geom_type = geom.geometryType()
assert geom_type == geom.geom_type
def test_type_deprecated():
geom = Point(1, 1)
with pytest.warns(ShapelyDeprecationWarning):
geom_type = geom.type
assert geom_type == geom.geom_type
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
def test_segmentize():
line = LineString([(0, 0), (0, 10)])
result = line.segmentize(max_segment_length=5)
assert result.equals(LineString([(0, 0), (0, 5), (0, 10)]))
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_reverse():
coords = [(0, 0), (1, 2)]
line = LineString(coords)
result = line.reverse()
assert result.coords[:] == coords[::-1]
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize(
"op", ["union", "intersection", "difference", "symmetric_difference"]
)
@pytest.mark.parametrize("grid_size", [0, 1, 2])
def test_binary_op_grid_size(op, grid_size):
geom1 = shapely.box(0, 0, 2.5, 2.5)
geom2 = shapely.box(2, 2, 3, 3)
result = getattr(geom1, op)(geom2, grid_size=grid_size)
expected = getattr(shapely, op)(geom1, geom2, grid_size=grid_size)
assert result == expected
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
def test_dwithin():
point = Point(1, 1)
line = LineString([(0, 0), (0, 10)])
assert point.dwithin(line, 0.5) is False
assert point.dwithin(line, 1.5) is True
def test_contains_properly():
polygon = Polygon([(0, 0), (10, 10), (10, -10)])
line = LineString([(0, 0), (10, 0)])
assert polygon.contains_properly(line) is False
assert polygon.contains(line) is True
@pytest.mark.parametrize(
"op", ["convex_hull", "envelope", "oriented_envelope", "minimum_rotated_rectangle"]
)
def test_constructive_properties(op):
geom = LineString([(0, 0), (0, 10), (10, 10)])
result = getattr(geom, op)
expected = getattr(shapely, op)(geom)
assert result == expected
@pytest.mark.parametrize(
"op",
[
"crosses",
"contains",
"contains_properly",
"covered_by",
"covers",
"disjoint",
"equals",
"intersects",
"overlaps",
"touches",
"within",
],
)
def test_array_argument_binary_predicates(op):
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
points = shapely.points([(0, 0), (0.5, 0.5), (1, 1)])
result = getattr(polygon, op)(points)
assert isinstance(result, np.ndarray)
expected = np.array([getattr(polygon, op)(p) for p in points], dtype=bool)
np.testing.assert_array_equal(result, expected)
# check scalar
result = getattr(polygon, op)(points[0])
assert type(result) is bool
@pytest.mark.parametrize(
"op, kwargs",
[
pytest.param(
"dwithin",
dict(distance=0.5),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10"
),
),
("equals_exact", dict(tolerance=0.01)),
("relate_pattern", dict(pattern="T*F**F***")),
],
)
def test_array_argument_binary_predicates2(op, kwargs):
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
points = shapely.points([(0, 0), (0.5, 0.5), (1, 1)])
result = getattr(polygon, op)(points, **kwargs)
assert isinstance(result, np.ndarray)
expected = np.array([getattr(polygon, op)(p, **kwargs) for p in points], dtype=bool)
np.testing.assert_array_equal(result, expected)
# check scalar
result = getattr(polygon, op)(points[0], **kwargs)
assert type(result) is bool
@pytest.mark.parametrize(
"op",
[
"difference",
"intersection",
"symmetric_difference",
"union",
],
)
def test_array_argument_binary_geo(op):
box = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
polygons = shapely.buffer(shapely.points([(0, 0), (0.5, 0.5), (1, 1)]), 0.5)
result = getattr(box, op)(polygons)
assert isinstance(result, np.ndarray)
expected = np.array([getattr(box, op)(g) for g in polygons], dtype=object)
assert_geometries_equal(result, expected)
# check scalar
result = getattr(box, op)(polygons[0])
assert isinstance(result, (Polygon, MultiPolygon))
@pytest.mark.parametrize("op", ["distance", "hausdorff_distance"])
def test_array_argument_float(op):
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
points = shapely.points([(0, 0), (0.5, 0.5), (1, 1)])
result = getattr(polygon, op)(points)
assert isinstance(result, np.ndarray)
expected = np.array([getattr(polygon, op)(p) for p in points], dtype="float64")
np.testing.assert_array_equal(result, expected)
# check scalar
result = getattr(polygon, op)(points[0])
assert type(result) is float
@pytest.mark.parametrize("op", ["line_interpolate_point", "interpolate"])
def test_array_argument_linear_point(op):
line = LineString([(0, 0), (0, 1), (1, 1)])
distances = np.array([0, 0.5, 1])
result = getattr(line, op)(distances)
assert isinstance(result, np.ndarray)
expected = np.array(
[line.line_interpolate_point(d) for d in distances], dtype=object
)
assert_geometries_equal(result, expected)
# check scalar
result = getattr(line, op)(distances[0])
assert isinstance(result, Point)
@pytest.mark.parametrize("op", ["line_locate_point", "project"])
def test_array_argument_linear_float(op):
line = LineString([(0, 0), (0, 1), (1, 1)])
points = shapely.points([(0, 0), (0.5, 0.5), (1, 1)])
result = getattr(line, op)(points)
assert isinstance(result, np.ndarray)
expected = np.array([line.line_locate_point(p) for p in points], dtype="float64")
np.testing.assert_array_equal(result, expected)
# check scalar
result = getattr(line, op)(points[0])
assert type(result) is float
def test_array_argument_buffer():
point = Point(1, 1)
distances = np.array([0, 0.5, 1])
result = point.buffer(distances)
assert isinstance(result, np.ndarray)
expected = np.array([point.buffer(d) for d in distances], dtype=object)
assert_geometries_equal(result, expected)
# check scalar
result = point.buffer(distances[0])
assert isinstance(result, Polygon)
@@ -0,0 +1,28 @@
import pytest
import shapely
from shapely.affinity import translate
from shapely.geometry import GeometryCollection, LineString, MultiPoint, Point
@pytest.mark.parametrize(
"geom",
[
Point(1, 2),
MultiPoint([(1, 2), (3, 4)]),
LineString([(1, 2), (3, 4)]),
Point(0, 0).buffer(1.0),
GeometryCollection([Point(1, 2), LineString([(1, 2), (3, 4)])]),
],
ids=[
"Point",
"MultiPoint",
"LineString",
"Polygon",
"GeometryCollection",
],
)
def test_hash(geom):
h1 = hash(geom)
assert h1 == hash(shapely.from_wkb(geom.wkb))
assert h1 != hash(translate(geom, 1.0, 2.0))
@@ -0,0 +1,213 @@
import numpy as np
import pytest
import shapely
from shapely import LinearRing, LineString, Point
from shapely.coords import CoordinateSequence
def test_from_coordinate_sequence():
# From coordinate tuples
line = LineString([(1.0, 2.0), (3.0, 4.0)])
assert len(line.coords) == 2
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
line = LineString([(1.0, 2.0), (3.0, 4.0)])
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
def test_from_coordinate_sequence_3D():
line = LineString([(1.0, 2.0, 3.0), (3.0, 4.0, 5.0)])
assert line.has_z
assert line.coords[:] == [(1.0, 2.0, 3.0), (3.0, 4.0, 5.0)]
def test_from_points():
# From Points
line = LineString([Point(1.0, 2.0), Point(3.0, 4.0)])
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
line = LineString([Point(1.0, 2.0), Point(3.0, 4.0)])
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
def test_from_mix():
# From mix of tuples and Points
line = LineString([Point(1.0, 2.0), (2.0, 3.0), Point(3.0, 4.0)])
assert line.coords[:] == [(1.0, 2.0), (2.0, 3.0), (3.0, 4.0)]
def test_from_linestring():
# From another linestring
line = LineString([(1.0, 2.0), (3.0, 4.0)])
copy = LineString(line)
assert copy.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
assert copy.geom_type == "LineString"
def test_from_linearring():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing(coords)
copy = LineString(ring)
assert copy.coords[:] == coords
assert copy.geom_type == "LineString"
def test_from_linestring_z():
coords = [(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)]
line = LineString(coords)
copy = LineString(line)
assert copy.coords[:] == coords
assert copy.geom_type == "LineString"
def test_from_generator():
gen = (coord for coord in [(1.0, 2.0), (3.0, 4.0)])
line = LineString(gen)
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
def test_from_empty():
line = LineString()
assert line.is_empty
assert isinstance(line.coords, CoordinateSequence)
assert line.coords[:] == []
line = LineString([])
assert line.is_empty
assert isinstance(line.coords, CoordinateSequence)
assert line.coords[:] == []
def test_from_numpy():
# Construct from a numpy array
line = LineString(np.array([[1.0, 2.0], [3.0, 4.0]]))
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
def test_numpy_empty_linestring_coords():
# Check empty
line = LineString([])
la = np.asarray(line.coords)
assert la.shape == (0, 2)
def test_numpy_object_array():
geom = LineString([(0.0, 0.0), (0.0, 1.0)])
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
@pytest.mark.filterwarnings("ignore:Creating an ndarray from ragged nested sequences:")
def test_from_invalid_dim():
# TODO(shapely-2.0) better error message?
# pytest.raises(ValueError, match="at least 2 coordinate tuples|at least 2 coordinates"):
with pytest.raises(shapely.GEOSException):
LineString([(1, 2)])
# exact error depends on numpy version
with pytest.raises((ValueError, TypeError)):
LineString([(1, 2, 3), (4, 5)])
with pytest.raises((ValueError, TypeError)):
LineString([(1, 2), (3, 4, 5)])
msg = r"The ordinate \(last\) dimension should be 2 or 3, got {}"
with pytest.raises(ValueError, match=msg.format(4)):
LineString([(1, 2, 3, 4), (4, 5, 6, 7)])
with pytest.raises(ValueError, match=msg.format(1)):
LineString([(1,), (4,)])
def test_from_single_coordinate():
"""Test for issue #486"""
coords = [[-122.185933073564, 37.3629353839073]]
with pytest.raises(shapely.GEOSException):
ls = LineString(coords)
ls.geom_type # caused segfault before fix
class TestLineString:
def test_linestring(self):
# From coordinate tuples
line = LineString([(1.0, 2.0), (3.0, 4.0)])
assert len(line.coords) == 2
assert line.coords[:] == [(1.0, 2.0), (3.0, 4.0)]
# Bounds
assert line.bounds == (1.0, 2.0, 3.0, 4.0)
# Coordinate access
assert tuple(line.coords) == ((1.0, 2.0), (3.0, 4.0))
assert line.coords[0] == (1.0, 2.0)
assert line.coords[1] == (3.0, 4.0)
with pytest.raises(IndexError):
line.coords[2] # index out of range
# Geo interface
assert line.__geo_interface__ == {
"type": "LineString",
"coordinates": ((1.0, 2.0), (3.0, 4.0)),
}
def test_linestring_empty(self):
# Test Non-operability of Null geometry
l_null = LineString()
assert l_null.wkt == "LINESTRING EMPTY"
assert l_null.length == 0.0
def test_equals_argument_order(self):
"""
Test equals predicate functions correctly regardless of the order
of the inputs. See issue #317.
"""
coords = ((0, 0), (1, 0), (1, 1), (0, 0))
ls = LineString(coords)
lr = LinearRing(coords)
assert ls.__eq__(lr) is False # previously incorrectly returned True
assert lr.__eq__(ls) is False
assert (ls == lr) is False
assert (lr == ls) is False
ls_clone = LineString(coords)
lr_clone = LinearRing(coords)
assert ls.__eq__(ls_clone) is True
assert lr.__eq__(lr_clone) is True
assert (ls == ls_clone) is True
assert (lr == lr_clone) is True
def test_numpy_linestring_coords(self):
from numpy.testing import assert_array_equal
line = LineString([(1.0, 2.0), (3.0, 4.0)])
expected = np.array([[1.0, 2.0], [3.0, 4.0]])
# Coordinate sequences can be adapted as well
la = np.asarray(line.coords)
assert_array_equal(la, expected)
def test_linestring_immutable():
line = LineString([(1.0, 2.0), (3.0, 4.0)])
with pytest.raises(AttributeError):
line.coords = [(-1.0, -1.0), (1.0, 1.0)]
with pytest.raises(TypeError):
line.coords[0] = (-1.0, -1.0)
def test_linestring_array_coercion():
# don't convert to array of coordinates, keep objects
line = LineString([(1.0, 2.0), (3.0, 4.0)])
arr = np.array(line)
assert arr.ndim == 0
assert arr.size == 1
assert arr.dtype == np.dtype("object")
assert arr.item() == line
@@ -0,0 +1,11 @@
import numpy as np
test_int_types = [int, np.int16, np.int32, np.int64]
class MultiGeometryTestCase:
def subgeom_access_test(self, cls, geoms):
geom = cls(geoms)
for t in test_int_types:
for i, g in enumerate(geoms):
assert geom.geoms[t(i)] == geoms[i]
@@ -0,0 +1,78 @@
import numpy as np
import pytest
from shapely import LineString, MultiLineString
from shapely.errors import EmptyPartError
from shapely.geometry.base import dump_coords
from shapely.tests.geometry.test_multi import MultiGeometryTestCase
class TestMultiLineString(MultiGeometryTestCase):
def test_multilinestring(self):
# From coordinate tuples
geom = MultiLineString([[(1.0, 2.0), (3.0, 4.0)]])
assert isinstance(geom, MultiLineString)
assert len(geom.geoms) == 1
assert dump_coords(geom) == [[(1.0, 2.0), (3.0, 4.0)]]
# From lines
a = LineString([(1.0, 2.0), (3.0, 4.0)])
ml = MultiLineString([a])
assert len(ml.geoms) == 1
assert dump_coords(ml) == [[(1.0, 2.0), (3.0, 4.0)]]
# From another multi-line
ml2 = MultiLineString(ml)
assert len(ml2.geoms) == 1
assert dump_coords(ml2) == [[(1.0, 2.0), (3.0, 4.0)]]
# Sub-geometry Access
geom = MultiLineString([(((0.0, 0.0), (1.0, 2.0)))])
assert isinstance(geom.geoms[0], LineString)
assert dump_coords(geom.geoms[0]) == [(0.0, 0.0), (1.0, 2.0)]
with pytest.raises(IndexError): # index out of range
geom.geoms[1]
# Geo interface
assert geom.__geo_interface__ == {
"type": "MultiLineString",
"coordinates": (((0.0, 0.0), (1.0, 2.0)),),
}
def test_from_multilinestring_z(self):
coords1 = [(0.0, 1.0, 2.0), (3.0, 4.0, 5.0)]
coords2 = [(6.0, 7.0, 8.0), (9.0, 10.0, 11.0)]
# From coordinate tuples
ml = MultiLineString([coords1, coords2])
copy = MultiLineString(ml)
assert isinstance(copy, MultiLineString)
assert copy.geom_type == "MultiLineString"
assert len(copy.geoms) == 2
assert dump_coords(copy.geoms[0]) == coords1
assert dump_coords(copy.geoms[1]) == coords2
def test_numpy(self):
# Construct from a numpy array
geom = MultiLineString([np.array(((0.0, 0.0), (1.0, 2.0)))])
assert isinstance(geom, MultiLineString)
assert len(geom.geoms) == 1
assert dump_coords(geom) == [[(0.0, 0.0), (1.0, 2.0)]]
def test_subgeom_access(self):
line0 = LineString([(0.0, 1.0), (2.0, 3.0)])
line1 = LineString([(4.0, 5.0), (6.0, 7.0)])
self.subgeom_access_test(MultiLineString, [line0, line1])
def test_create_multi_with_empty_component(self):
msg = "Can't create MultiLineString with empty component"
with pytest.raises(EmptyPartError, match=msg):
MultiLineString([LineString([(0, 0), (1, 1), (2, 2)]), LineString()]).wkt
def test_numpy_object_array():
geom = MultiLineString([[[5.0, 6.0], [7.0, 8.0]]])
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
@@ -0,0 +1,78 @@
import numpy as np
import pytest
from shapely import MultiPoint, Point
from shapely.errors import EmptyPartError
from shapely.geometry.base import dump_coords
from shapely.tests.geometry.test_multi import MultiGeometryTestCase
class TestMultiPoint(MultiGeometryTestCase):
def test_multipoint(self):
# From coordinate tuples
geom = MultiPoint([(1.0, 2.0), (3.0, 4.0)])
assert len(geom.geoms) == 2
assert dump_coords(geom) == [[(1.0, 2.0)], [(3.0, 4.0)]]
# From points
geom = MultiPoint([Point(1.0, 2.0), Point(3.0, 4.0)])
assert len(geom.geoms) == 2
assert dump_coords(geom) == [[(1.0, 2.0)], [(3.0, 4.0)]]
# From another multi-point
geom2 = MultiPoint(geom)
assert len(geom2.geoms) == 2
assert dump_coords(geom2) == [[(1.0, 2.0)], [(3.0, 4.0)]]
# Sub-geometry Access
assert isinstance(geom.geoms[0], Point)
assert geom.geoms[0].x == 1.0
assert geom.geoms[0].y == 2.0
with pytest.raises(IndexError): # index out of range
geom.geoms[2]
# Geo interface
assert geom.__geo_interface__ == {
"type": "MultiPoint",
"coordinates": ((1.0, 2.0), (3.0, 4.0)),
}
def test_multipoint_from_numpy(self):
# Construct from a numpy array
geom = MultiPoint(np.array([[0.0, 0.0], [1.0, 2.0]]))
assert isinstance(geom, MultiPoint)
assert len(geom.geoms) == 2
assert dump_coords(geom) == [[(0.0, 0.0)], [(1.0, 2.0)]]
def test_subgeom_access(self):
p0 = Point(1.0, 2.0)
p1 = Point(3.0, 4.0)
self.subgeom_access_test(MultiPoint, [p0, p1])
def test_create_multi_with_empty_component(self):
msg = "Can't create MultiPoint with empty component"
with pytest.raises(EmptyPartError, match=msg):
MultiPoint([Point(0, 0), Point()]).wkt
def test_multipoint_array_coercion():
geom = MultiPoint([(1.0, 2.0), (3.0, 4.0)])
arr = np.array(geom)
assert arr.ndim == 0
assert arr.size == 1
assert arr.dtype == np.dtype("object")
assert arr.item() == geom
def test_numpy_object_array():
geom = MultiPoint([(1.0, 2.0), (3.0, 4.0)])
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
def test_len_raises():
geom = MultiPoint([[5.0, 6.0], [7.0, 8.0]])
with pytest.raises(TypeError):
len(geom)
@@ -0,0 +1,134 @@
import numpy as np
import pytest
from shapely import MultiPolygon, Polygon
from shapely.geometry.base import dump_coords
from shapely.tests.geometry.test_multi import MultiGeometryTestCase
class TestMultiPolygon(MultiGeometryTestCase):
def test_multipolygon(self):
# From coordinate tuples
coords = [
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))],
)
]
geom = MultiPolygon(coords)
assert isinstance(geom, MultiPolygon)
assert len(geom.geoms) == 1
assert dump_coords(geom) == [
[
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)],
]
]
# Or without holes
coords2 = [(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),)]
geom = MultiPolygon(coords2)
assert isinstance(geom, MultiPolygon)
assert len(geom.geoms) == 1
assert dump_coords(geom) == [
[
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
]
]
# Or from polygons
p = Polygon(
((0, 0), (0, 1), (1, 1), (1, 0)),
[((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))],
)
geom = MultiPolygon([p])
assert len(geom.geoms) == 1
assert dump_coords(geom) == [
[
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)],
]
]
# Or from another multi-polygon
geom2 = MultiPolygon(geom)
assert len(geom2.geoms) == 1
assert dump_coords(geom2) == [
[
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)],
]
]
# Sub-geometry Access
assert isinstance(geom.geoms[0], Polygon)
assert dump_coords(geom.geoms[0]) == [
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)],
]
with pytest.raises(IndexError): # index out of range
geom.geoms[1]
# Geo interface
assert geom.__geo_interface__ == {
"type": "MultiPolygon",
"coordinates": [
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)),
)
],
}
def test_subgeom_access(self):
poly0 = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
poly1 = Polygon([(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)])
self.subgeom_access_test(MultiPolygon, [poly0, poly1])
def test_fail_list_of_multipolygons():
"""A list of multipolygons is not a valid multipolygon ctor argument"""
multi = MultiPolygon(
[
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))],
)
]
)
with pytest.raises(ValueError):
MultiPolygon([multi])
def test_numpy_object_array():
geom = MultiPolygon(
[
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))],
)
]
)
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
@@ -0,0 +1,188 @@
import numpy as np
import pytest
from shapely import Point
from shapely.coords import CoordinateSequence
from shapely.errors import DimensionError
def test_from_coordinates():
# 2D points
p = Point(1.0, 2.0)
assert p.coords[:] == [(1.0, 2.0)]
assert p.has_z is False
# 3D Point
p = Point(1.0, 2.0, 3.0)
assert p.coords[:] == [(1.0, 2.0, 3.0)]
assert p.has_z
# empty
p = Point()
assert p.is_empty
assert isinstance(p.coords, CoordinateSequence)
assert p.coords[:] == []
def test_from_sequence():
# From single coordinate pair
p = Point((3.0, 4.0))
assert p.coords[:] == [(3.0, 4.0)]
p = Point([3.0, 4.0])
assert p.coords[:] == [(3.0, 4.0)]
# From coordinate sequence
p = Point([(3.0, 4.0)])
assert p.coords[:] == [(3.0, 4.0)]
p = Point([[3.0, 4.0]])
assert p.coords[:] == [(3.0, 4.0)]
# 3D
p = Point((3.0, 4.0, 5.0))
assert p.coords[:] == [(3.0, 4.0, 5.0)]
p = Point([3.0, 4.0, 5.0])
assert p.coords[:] == [(3.0, 4.0, 5.0)]
p = Point([(3.0, 4.0, 5.0)])
assert p.coords[:] == [(3.0, 4.0, 5.0)]
def test_from_numpy():
# Construct from a numpy array
p = Point(np.array([1.0, 2.0]))
assert p.coords[:] == [(1.0, 2.0)]
p = Point(np.array([1.0, 2.0, 3.0]))
assert p.coords[:] == [(1.0, 2.0, 3.0)]
def test_from_numpy_xy():
# Construct from separate x, y numpy arrays - if those are length 1,
# this is allowed for compat with shapely 1.8
# (https://github.com/shapely/shapely/issues/1587)
p = Point(np.array([1.0]), np.array([2.0]))
assert p.coords[:] == [(1.0, 2.0)]
p = Point(np.array([1.0]), np.array([2.0]), np.array([3.0]))
assert p.coords[:] == [(1.0, 2.0, 3.0)]
def test_from_point():
# From another point
p = Point(3.0, 4.0)
q = Point(p)
assert q.coords[:] == [(3.0, 4.0)]
p = Point(3.0, 4.0, 5.0)
q = Point(p)
assert q.coords[:] == [(3.0, 4.0, 5.0)]
def test_from_generator():
gen = (coord for coord in [(1.0, 2.0)])
p = Point(gen)
assert p.coords[:] == [(1.0, 2.0)]
def test_from_invalid():
with pytest.raises(TypeError, match="takes at most 3 arguments"):
Point(1, 2, 3, 4)
# this worked in shapely 1.x, just ignoring the other coords
with pytest.raises(
ValueError, match="takes only scalar or 1-size vector arguments"
):
Point([(2, 3), (11, 4)])
class TestPoint:
def test_point(self):
# Test 2D points
p = Point(1.0, 2.0)
assert p.x == 1.0
assert type(p.x) is float
assert p.y == 2.0
assert type(p.y) is float
assert p.coords[:] == [(1.0, 2.0)]
assert str(p) == p.wkt
assert p.has_z is False
with pytest.raises(DimensionError):
p.z
# Check 3D
p = Point(1.0, 2.0, 3.0)
assert p.coords[:] == [(1.0, 2.0, 3.0)]
assert str(p) == p.wkt
assert p.has_z is True
assert p.z == 3.0
assert type(p.z) is float
# Coordinate access
p = Point((3.0, 4.0))
assert p.x == 3.0
assert p.y == 4.0
assert tuple(p.coords) == ((3.0, 4.0),)
assert p.coords[0] == (3.0, 4.0)
with pytest.raises(IndexError): # index out of range
p.coords[1]
# Bounds
assert p.bounds == (3.0, 4.0, 3.0, 4.0)
# Geo interface
assert p.__geo_interface__ == {"type": "Point", "coordinates": (3.0, 4.0)}
def test_point_empty(self):
# Test Non-operability of Null geometry
p_null = Point()
assert p_null.wkt == "POINT EMPTY"
assert p_null.coords[:] == []
assert p_null.area == 0.0
def test_coords(self):
# From Array.txt
p = Point(0.0, 0.0, 1.0)
coords = p.coords[0]
assert coords == (0.0, 0.0, 1.0)
# Convert to Numpy array, passing through Python sequence
a = np.asarray(coords)
assert a.ndim == 1
assert a.size == 3
assert a.shape == (3,)
def test_point_immutable():
p = Point(3.0, 4.0)
with pytest.raises(AttributeError):
p.coords = (2.0, 1.0)
with pytest.raises(TypeError):
p.coords[0] = (2.0, 1.0)
def test_point_array_coercion():
# don't convert to array of coordinates, keep objects
p = Point(3.0, 4.0)
arr = np.array(p)
assert arr.ndim == 0
assert arr.size == 1
assert arr.dtype == np.dtype("object")
assert arr.item() == p
def test_numpy_empty_point_coords():
pe = Point()
# Access the coords
a = np.asarray(pe.coords)
assert a.shape == (0, 2)
def test_numpy_object_array():
geom = Point(3.0, 4.0)
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
@@ -0,0 +1,463 @@
"""Polygons and Linear Rings
"""
import numpy as np
import pytest
from shapely import LinearRing, LineString, Point, Polygon
from shapely.coords import CoordinateSequence
from shapely.errors import TopologicalError
from shapely.wkb import loads as load_wkb
def test_empty_linearring_coords():
assert LinearRing().coords[:] == []
def test_linearring_from_coordinate_sequence():
expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
assert ring.coords[:] == expected_coords
ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
assert ring.coords[:] == expected_coords
def test_linearring_from_points():
# From Points
expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing([Point(0.0, 0.0), Point(0.0, 1.0), Point(1.0, 1.0)])
assert ring.coords[:] == expected_coords
def test_linearring_from_closed_linestring():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
line = LineString(coords)
ring = LinearRing(line)
assert len(ring.coords) == 4
assert ring.coords[:] == coords
assert ring.geom_type == "LinearRing"
def test_linearring_from_unclosed_linestring():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
line = LineString(coords[:-1]) # Pass in unclosed line
ring = LinearRing(line)
assert len(ring.coords) == 4
assert ring.coords[:] == coords
assert ring.geom_type == "LinearRing"
def test_linearring_from_invalid():
coords = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
line = LineString(coords)
assert not line.is_valid
with pytest.raises(TopologicalError):
LinearRing(line)
def test_linearring_from_too_short_linestring():
# Creation of LinearRing request at least 3 coordinates (unclosed) or
# 4 coordinates (closed)
coords = [(0.0, 0.0), (1.0, 1.0)]
line = LineString(coords)
with pytest.raises(ValueError, match="requires at least 4 coordinates"):
LinearRing(line)
def test_linearring_from_linearring():
coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing(coords)
assert ring.coords[:] == coords
def test_linearring_from_generator():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
gen = (coord for coord in coords)
ring = LinearRing(gen)
assert ring.coords[:] == coords
def test_linearring_from_empty():
ring = LinearRing()
assert ring.is_empty
assert isinstance(ring.coords, CoordinateSequence)
assert ring.coords[:] == []
ring = LinearRing([])
assert ring.is_empty
assert isinstance(ring.coords, CoordinateSequence)
assert ring.coords[:] == []
def test_linearring_from_numpy():
# Construct from a numpy array
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing(np.array(coords))
assert ring.coords[:] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
def test_numpy_linearring_coords():
from numpy.testing import assert_array_equal
ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
ra = np.asarray(ring.coords)
expected = np.asarray([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)])
assert_array_equal(ra, expected)
def test_numpy_empty_linearring_coords():
ring = LinearRing()
assert np.asarray(ring.coords).shape == (0, 2)
def test_numpy_object_array():
geom = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
ar = np.empty(1, object)
ar[:] = [geom]
assert ar[0] == geom
def test_polygon_from_coordinate_sequence():
coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
# Construct a polygon, exterior ring only
polygon = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
assert polygon.exterior.coords[:] == coords
assert len(polygon.interiors) == 0
polygon = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
assert polygon.exterior.coords[:] == coords
assert len(polygon.interiors) == 0
def test_polygon_from_coordinate_sequence_with_holes():
coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
# Interior rings (holes)
polygon = Polygon(coords, [[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)]])
assert polygon.exterior.coords[:] == coords
assert len(polygon.interiors) == 1
assert len(polygon.interiors[0].coords) == 5
# Multiple interior rings with different length
coords = [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]
holes = [
[(1, 1), (2, 1), (2, 2), (1, 2), (1, 1)],
[(3, 3), (3, 4), (4, 5), (5, 4), (5, 3), (3, 3)],
]
polygon = Polygon(coords, holes)
assert polygon.exterior.coords[:] == coords
assert len(polygon.interiors) == 2
assert len(polygon.interiors[0].coords) == 5
assert len(polygon.interiors[1].coords) == 6
def test_polygon_from_linearring():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
ring = LinearRing(coords)
polygon = Polygon(ring)
assert polygon.exterior.coords[:] == coords
assert len(polygon.interiors) == 0
# from shell and holes linearrings
shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
holes = [
LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
]
polygon = Polygon(shell, holes)
assert polygon.exterior.coords[:] == shell.coords[:]
assert len(polygon.interiors) == 3
for i in range(3):
assert polygon.interiors[i].coords[:] == holes[i].coords[:]
def test_polygon_from_linestring():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
line = LineString(coords)
polygon = Polygon(line)
assert polygon.exterior.coords[:] == coords
# from unclosed linestring
line = LineString(coords[:-1])
polygon = Polygon(line)
assert polygon.exterior.coords[:] == coords
def test_polygon_from_points():
polygon = Polygon([Point(0.0, 0.0), Point(0.0, 1.0), Point(1.0, 1.0)])
expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
assert polygon.exterior.coords[:] == expected_coords
def test_polygon_from_polygon():
coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]
polygon = Polygon(coords, [[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)]])
# Test from another Polygon
copy = Polygon(polygon)
assert len(copy.exterior.coords) == 5
assert len(copy.interiors) == 1
assert len(copy.interiors[0].coords) == 5
def test_polygon_from_invalid():
# Error handling
with pytest.raises(ValueError):
# A LinearRing must have at least 3 coordinate tuples
Polygon([[1, 2], [2, 3]])
def test_polygon_from_empty():
polygon = Polygon()
assert polygon.is_empty
assert polygon.exterior.coords[:] == []
polygon = Polygon([])
assert polygon.is_empty
assert polygon.exterior.coords[:] == []
def test_polygon_from_numpy():
a = np.array(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)))
polygon = Polygon(a)
assert len(polygon.exterior.coords) == 5
assert polygon.exterior.coords[:] == [
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(1.0, 0.0),
(0.0, 0.0),
]
assert len(polygon.interiors) == 0
def test_polygon_from_generator():
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
gen = (coord for coord in coords)
polygon = Polygon(gen)
assert polygon.exterior.coords[:] == coords
class TestPolygon:
def test_linearring(self):
# Initialization
# Linear rings won't usually be created by users, but by polygons
coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0))
ring = LinearRing(coords)
assert len(ring.coords) == 5
assert ring.coords[0] == ring.coords[4]
assert ring.coords[0] == ring.coords[-1]
assert ring.is_ring is True
def test_polygon(self):
coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0))
# Construct a polygon, exterior ring only
polygon = Polygon(coords)
assert len(polygon.exterior.coords) == 5
# Ring Access
assert isinstance(polygon.exterior, LinearRing)
ring = polygon.exterior
assert len(ring.coords) == 5
assert ring.coords[0] == ring.coords[4]
assert ring.coords[0] == (0.0, 0.0)
assert ring.is_ring is True
assert len(polygon.interiors) == 0
# Create a new polygon from WKB
data = polygon.wkb
polygon = None
ring = None
polygon = load_wkb(data)
ring = polygon.exterior
assert len(ring.coords) == 5
assert ring.coords[0] == ring.coords[4]
assert ring.coords[0] == (0.0, 0.0)
assert ring.is_ring is True
polygon = None
# Interior rings (holes)
polygon = Polygon(
coords, [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))]
)
assert len(polygon.exterior.coords) == 5
assert len(polygon.interiors[0].coords) == 5
with pytest.raises(IndexError): # index out of range
polygon.interiors[1]
# Coordinate getter raises exceptions
with pytest.raises(NotImplementedError):
polygon.coords
# Geo interface
assert polygon.__geo_interface__ == {
"type": "Polygon",
"coordinates": (
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)),
),
}
def test_linearring_empty(self):
# Test Non-operability of Null rings
r_null = LinearRing()
assert r_null.wkt == "LINEARRING EMPTY"
assert r_null.length == 0.0
def test_dimensions(self):
# Background: see http://trac.gispython.org/lab/ticket/168
# http://lists.gispython.org/pipermail/community/2008-August/001859.html
coords = ((0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0))
polygon = Polygon(coords)
assert polygon._ndim == 3
gi = polygon.__geo_interface__
assert gi["coordinates"] == (
(
(0.0, 0.0, 0.0),
(0.0, 1.0, 0.0),
(1.0, 1.0, 0.0),
(1.0, 0.0, 0.0),
(0.0, 0.0, 0.0),
),
)
e = polygon.exterior
assert e._ndim == 3
gi = e.__geo_interface__
assert gi["coordinates"] == (
(0.0, 0.0, 0.0),
(0.0, 1.0, 0.0),
(1.0, 1.0, 0.0),
(1.0, 0.0, 0.0),
(0.0, 0.0, 0.0),
)
def test_attribute_chains(self):
# Attribute Chaining
# See also ticket #151.
p = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
assert list(p.boundary.coords) == [
(0.0, 0.0),
(0.0, 1.0),
(-1.0, 1.0),
(-1.0, 0.0),
(0.0, 0.0),
]
ec = list(Point(0.0, 0.0).buffer(1.0, 1).exterior.coords)
assert isinstance(ec, list) # TODO: this is a poor test
# Test chained access to interiors
p = Polygon(
[(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)],
[[(-0.25, 0.25), (-0.25, 0.75), (-0.75, 0.75), (-0.75, 0.25)]],
)
assert p.area == 0.75
"""Not so much testing the exact values here, which are the
responsibility of the geometry engine (GEOS), but that we can get
chain functions and properties using anonymous references.
"""
assert list(p.interiors[0].coords) == [
(-0.25, 0.25),
(-0.25, 0.75),
(-0.75, 0.75),
(-0.75, 0.25),
(-0.25, 0.25),
]
xy = list(p.interiors[0].buffer(1).exterior.coords)[0]
assert len(xy) == 2
# Test multiple operators, boundary of a buffer
ec = list(p.buffer(1).boundary.coords)
assert isinstance(ec, list) # TODO: this is a poor test
def test_empty_equality(self):
# Test equals operator, including empty geometries
# see issue #338
point1 = Point(0, 0)
polygon1 = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
polygon2 = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
polygon_empty1 = Polygon()
polygon_empty2 = Polygon()
assert point1 != polygon1
assert polygon_empty1 == polygon_empty2
assert polygon1 != polygon_empty1
assert polygon1 == polygon2
assert polygon_empty1 is not None
def test_from_bounds(self):
xmin, ymin, xmax, ymax = -180, -90, 180, 90
coords = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
assert Polygon(coords) == Polygon.from_bounds(xmin, ymin, xmax, ymax)
def test_empty_polygon_exterior(self):
p = Polygon()
assert p.exterior == LinearRing()
def test_linearring_immutable():
ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
with pytest.raises(AttributeError):
ring.coords = [(1.0, 1.0), (2.0, 2.0), (1.0, 2.0)]
with pytest.raises(TypeError):
ring.coords[0] = (1.0, 1.0)
class TestLinearRingGetItem:
def test_index_linearring(self):
shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
holes = [
LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
]
g = Polygon(shell, holes)
for i in range(-3, 3):
assert g.interiors[i].equals(holes[i])
with pytest.raises(IndexError):
g.interiors[3]
with pytest.raises(IndexError):
g.interiors[-4]
def test_index_linearring_misc(self):
g = Polygon() # empty
with pytest.raises(IndexError):
g.interiors[0]
with pytest.raises(TypeError):
g.interiors[0.0]
def test_slice_linearring(self):
shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
holes = [
LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
]
g = Polygon(shell, holes)
t = [a.equals(b) for (a, b) in zip(g.interiors[1:], holes[1:])]
assert all(t)
t = [a.equals(b) for (a, b) in zip(g.interiors[:-1], holes[:-1])]
assert all(t)
t = [a.equals(b) for (a, b) in zip(g.interiors[::-1], holes[::-1])]
assert all(t)
t = [a.equals(b) for (a, b) in zip(g.interiors[::2], holes[::2])]
assert all(t)
t = [a.equals(b) for (a, b) in zip(g.interiors[:3], holes[:3])]
assert all(t)
assert g.interiors[3:] == holes[3:] == []
@@ -0,0 +1,10 @@
import sys
import numpy
from shapely.geos import geos_version_string
# Show some diagnostic information; handy for CI
print("Python version: " + sys.version.replace("\n", " "))
print("GEOS version: " + geos_version_string)
print("Numpy version: " + numpy.version.version)
@@ -0,0 +1,21 @@
import numpy
import pytest
from shapely.geos import geos_version
requires_geos_38 = pytest.mark.skipif(
geos_version < (3, 8, 0), reason="GEOS >= 3.8.0 is required."
)
requires_geos_342 = pytest.mark.skipif(
geos_version < (3, 4, 2), reason="GEOS > 3.4.2 is required."
)
shapely20_todo = pytest.mark.xfail(
strict=True, reason="Not yet implemented for Shapely 2.0"
)
shapely20_wontfix = pytest.mark.xfail(strict=True, reason="Will fail for Shapely 2.0")
def pytest_report_header(config):
"""Header for pytest."""
return f"dependencies: numpy-{numpy.__version__}"
@@ -0,0 +1,311 @@
import unittest
from math import pi
import numpy as np
import pytest
from shapely import affinity
from shapely.geometry import Point
from shapely.wkt import loads as load_wkt
class AffineTestCase(unittest.TestCase):
def test_affine_params(self):
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
with pytest.raises(TypeError):
affinity.affine_transform(g, None)
with pytest.raises(ValueError):
affinity.affine_transform(g, [1, 2, 3, 4, 5, 6, 7, 8, 9])
with pytest.raises(AttributeError):
affinity.affine_transform(None, [1, 2, 3, 4, 5, 6])
def test_affine_geom_types(self):
# identity matrices, which should result with no transformation
matrix2d = (1, 0, 0, 1, 0, 0)
matrix3d = (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
# empty in, empty out
empty2d = load_wkt("MULTIPOLYGON EMPTY")
assert affinity.affine_transform(empty2d, matrix2d).is_empty
def test_geom(g2, g3=None):
assert not g2.has_z
a2 = affinity.affine_transform(g2, matrix2d)
assert not a2.has_z
assert g2.equals(a2)
if g3 is not None:
assert g3.has_z
a3 = affinity.affine_transform(g3, matrix3d)
assert a3.has_z
assert g3.equals(a3)
return
pt2d = load_wkt("POINT(12.3 45.6)")
pt3d = load_wkt("POINT(12.3 45.6 7.89)")
test_geom(pt2d, pt3d)
ls2d = load_wkt("LINESTRING(0.9 3.4, 0.7 2, 2.5 2.7)")
ls3d = load_wkt("LINESTRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5)")
test_geom(ls2d, ls3d)
lr2d = load_wkt("LINEARRING(0.9 3.4, 0.7 2, 2.5 2.7, 0.9 3.4)")
lr3d = load_wkt("LINEARRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5, 0.9 3.4 3.3)")
test_geom(lr2d, lr3d)
test_geom(
load_wkt(
"POLYGON((0.9 2.3, 0.5 1.1, 2.4 0.8, 0.9 2.3), "
"(1.1 1.7, 0.9 1.3, 1.4 1.2, 1.1 1.7), "
"(1.6 1.3, 1.7 1, 1.9 1.1, 1.6 1.3))"
)
)
test_geom(
load_wkt("MULTIPOINT ((-300 300), (700 300), (-800 -1100), (200 -300))")
)
test_geom(
load_wkt(
"MULTILINESTRING((0 0, -0.7 -0.7, 0.6 -1), "
"(-0.5 0.5, 0.7 0.6, 0 -0.6))"
)
)
test_geom(
load_wkt(
"MULTIPOLYGON(((900 4300, -1100 -400, 900 -800, 900 4300)), "
"((1200 4300, 2300 4400, 1900 1000, 1200 4300)))"
)
)
test_geom(
load_wkt(
"GEOMETRYCOLLECTION(POINT(20 70),"
" POLYGON((60 70, 13 35, 60 -30, 60 70)),"
" LINESTRING(60 70, 50 100, 80 100))"
)
)
def test_affine_2d(self):
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
# custom scale and translate
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
a2 = affinity.affine_transform(g, matrix2d)
assert a2.equals_exact(expected2d, 1e-6)
assert not a2.has_z
# Make sure a 3D matrix does not make a 3D shape from a 2D input
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 10, -5, 4.1, 100)
a3 = affinity.affine_transform(g, matrix3d)
assert a3.equals_exact(expected2d, 1e-6)
assert not a3.has_z
def test_affine_3d(self):
g2 = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
g3 = load_wkt("LINESTRING(2.4 4.1 100.2, 2.4 3 132.8, 3 3 128.6)")
# custom scale and translate
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 0.3048, -5, 4.1, 100)
# Combinations of 2D and 3D geometries and matrices
a22 = affinity.affine_transform(g2, matrix2d)
a23 = affinity.affine_transform(g2, matrix3d)
a32 = affinity.affine_transform(g3, matrix2d)
a33 = affinity.affine_transform(g3, matrix3d)
# Check dimensions
assert not a22.has_z
assert not a23.has_z
assert a32.has_z
assert a33.has_z
# 2D equality checks
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
expected3d = load_wkt(
"LINESTRING(-0.2 14.35 130.54096, " "-0.2 11.6 140.47744, 1 11.6 139.19728)"
)
expected32 = load_wkt(
"LINESTRING(-0.2 14.35 100.2, " "-0.2 11.6 132.8, 1 11.6 128.6)"
)
assert a22.equals_exact(expected2d, 1e-6)
assert a23.equals_exact(expected2d, 1e-6)
# Do explicit 3D check of coordinate values
for a, e in zip(a32.coords, expected32.coords):
for ap, ep in zip(a, e):
self.assertAlmostEqual(ap, ep)
for a, e in zip(a33.coords, expected3d.coords):
for ap, ep in zip(a, e):
self.assertAlmostEqual(ap, ep)
class TransformOpsTestCase(unittest.TestCase):
def test_rotate(self):
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
# counter-clockwise degrees
rls = affinity.rotate(ls, 90)
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
assert rls.equals(els)
# retest with named parameters for the same result
rls = affinity.rotate(geom=ls, angle=90, origin="center")
assert rls.equals(els)
# clockwise radians
rls = affinity.rotate(ls, -pi / 2, use_radians=True)
els = load_wkt("LINESTRING(320 380, 220 380, 220 320)")
assert rls.equals(els)
## other `origin` parameters
# around the centroid
rls = affinity.rotate(ls, 90, origin="centroid")
els = load_wkt("LINESTRING(182.5 320, 282.5 320, 282.5 380)")
assert rls.equals(els)
# around the second coordinate tuple
rls = affinity.rotate(ls, 90, origin=ls.coords[1])
els = load_wkt("LINESTRING(140 300, 240 300, 240 360)")
assert rls.equals(els)
# around the absolute Point of origin
rls = affinity.rotate(ls, 90, origin=Point(0, 0))
els = load_wkt("LINESTRING(-400 240, -300 240, -300 300)")
assert rls.equals(els)
def test_rotate_empty(self):
rls = affinity.rotate(load_wkt("LINESTRING EMPTY"), 90)
els = load_wkt("LINESTRING EMPTY")
assert rls.equals(els)
def test_rotate_angle_array(self):
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
# check with degrees
theta = np.array(90.0)
rls = affinity.rotate(ls, theta)
assert theta.item() == 90.0
assert rls.equals(els)
# check with radians
theta = np.array(pi / 2)
rls = affinity.rotate(ls, theta, use_radians=True)
assert theta.item() == pi / 2
assert rls.equals(els)
def test_scale(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test defaults of 1.0
sls = affinity.scale(ls)
assert sls.equals(ls)
# different scaling in different dimensions
sls = affinity.scale(ls, 2, 3, 0.5)
els = load_wkt("LINESTRING(210 500 5, 210 200 15, 330 200 10)")
assert sls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(sls.coords, els.coords):
for ap, bp in zip(a, b):
self.assertEqual(ap, bp)
# retest with named parameters for the same result
sls = affinity.scale(geom=ls, xfact=2, yfact=3, zfact=0.5, origin="center")
assert sls.equals(els)
## other `origin` parameters
# around the centroid
sls = affinity.scale(ls, 2, 3, 0.5, origin="centroid")
els = load_wkt("LINESTRING(228.75 537.5, 228.75 237.5, 348.75 237.5)")
assert sls.equals(els)
# around the second coordinate tuple
sls = affinity.scale(ls, 2, 3, 0.5, origin=ls.coords[1])
els = load_wkt("LINESTRING(240 600, 240 300, 360 300)")
assert sls.equals(els)
# around some other 3D Point of origin
sls = affinity.scale(ls, 2, 3, 0.5, origin=Point(100, 200, 1000))
els = load_wkt("LINESTRING(380 800 505, 380 500 515, 500 500 510)")
assert sls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(sls.coords, els.coords):
for ap, bp in zip(a, b):
assert ap == bp
def test_scale_empty(self):
sls = affinity.scale(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
assert sls.equals(els)
def test_skew(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test default shear angles of 0.0
sls = affinity.skew(ls)
assert sls.equals(ls)
# different shearing in x- and y-directions
sls = affinity.skew(ls, 15, -30)
els = load_wkt(
"LINESTRING (253.39745962155615 417.3205080756888, "
"226.60254037844385 317.3205080756888, "
"286.60254037844385 282.67949192431126)"
)
assert sls.equals_exact(els, 1e-6)
# retest with radians for the same result
sls = affinity.skew(ls, pi / 12, -pi / 6, use_radians=True)
assert sls.equals_exact(els, 1e-6)
# retest with named parameters for the same result
sls = affinity.skew(geom=ls, xs=15, ys=-30, origin="center", use_radians=False)
assert sls.equals_exact(els, 1e-6)
## other `origin` parameters
# around the centroid
sls = affinity.skew(ls, 15, -30, origin="centroid")
els = load_wkt(
"LINESTRING(258.42150697963973 406.49519052838332, "
"231.6265877365273980 306.4951905283833185, "
"291.6265877365274264 271.8541743770057337)"
)
assert sls.equals_exact(els, 1e-6)
# around the second coordinate tuple
sls = affinity.skew(ls, 15, -30, origin=ls.coords[1])
els = load_wkt(
"LINESTRING(266.7949192431123038 400, 240 300, " "300 265.3589838486224153)"
)
assert sls.equals_exact(els, 1e-6)
# around the absolute Point of origin
sls = affinity.skew(ls, 15, -30, origin=Point(0, 0))
els = load_wkt(
"LINESTRING(347.179676972449101 261.435935394489832, "
"320.3847577293367976 161.4359353944898317, "
"380.3847577293367976 126.7949192431122754)"
)
assert sls.equals_exact(els, 1e-6)
def test_skew_empty(self):
sls = affinity.skew(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
assert sls.equals(els)
def test_skew_xs_ys_array(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
els = load_wkt(
"LINESTRING (253.39745962155615 417.3205080756888, "
"226.60254037844385 317.3205080756888, "
"286.60254037844385 282.67949192431126)"
)
# check with degrees
xs_ys = np.array([15.0, -30.0])
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...])
assert xs_ys[0] == 15.0
assert xs_ys[1] == -30.0
assert sls.equals_exact(els, 1e-6)
# check with radians
xs_ys = np.array([pi / 12, -pi / 6])
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...], use_radians=True)
assert xs_ys[0] == pi / 12
assert xs_ys[1] == -pi / 6
assert sls.equals_exact(els, 1e-6)
def test_translate(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test default offset of 0.0
tls = affinity.translate(ls)
assert tls.equals(ls)
# test all offsets
tls = affinity.translate(ls, 100, 400, -10)
els = load_wkt("LINESTRING(340 800 0, 340 700 20, 400 700 10)")
assert tls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(tls.coords, els.coords):
for ap, bp in zip(a, b):
assert ap == bp
# retest with named parameters for the same result
tls = affinity.translate(geom=ls, xoff=100, yoff=400, zoff=-10)
assert tls.equals(els)
def test_translate_empty(self):
tls = affinity.translate(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
self.assertTrue(tls.equals(els))
assert tls.equals(els)
@@ -0,0 +1,20 @@
import unittest
from shapely import geometry
class BoxTestCase(unittest.TestCase):
def test_ccw(self):
b = geometry.box(0, 0, 1, 1, ccw=True)
assert b.exterior.coords[0] == (1.0, 0.0)
assert b.exterior.coords[1] == (1.0, 1.0)
def test_ccw_default(self):
b = geometry.box(0, 0, 1, 1)
assert b.exterior.coords[0] == (1.0, 0.0)
assert b.exterior.coords[1] == (1.0, 1.0)
def test_cw(self):
b = geometry.box(0, 0, 1, 1, ccw=False)
assert b.exterior.coords[0] == (0.0, 0.0)
assert b.exterior.coords[1] == (0.0, 1.0)
@@ -0,0 +1,172 @@
import unittest
import pytest
from shapely import geometry
from shapely.constructive import BufferCapStyle, BufferJoinStyle
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
@pytest.mark.parametrize("distance", [float("nan"), float("inf")])
def test_non_finite_distance(distance):
g = geometry.Point(0, 0)
with pytest.raises(ValueError, match="distance must be finite"):
g.buffer(distance)
class BufferTests(unittest.TestCase):
"""Test Buffer Point/Line/Polygon with and without single_sided params"""
def test_empty(self):
g = geometry.Point(0, 0)
h = g.buffer(0)
assert h.is_empty
def test_point(self):
g = geometry.Point(0, 0)
h = g.buffer(1, quad_segs=1)
assert h.geom_type == "Polygon"
expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_point_single_sidedd(self):
g = geometry.Point(0, 0)
h = g.buffer(1, quad_segs=1, single_sided=True)
assert h.geom_type == "Polygon"
expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_line(self):
g = geometry.LineString([[0, 0], [0, 1]])
h = g.buffer(1, quad_segs=1)
assert h.geom_type == "Polygon"
expected_coord = [
(-1.0, 1.0),
(0, 2.0),
(1.0, 1.0),
(1.0, 0.0),
(0, -1.0),
(-1.0, 0.0),
(-1.0, 1.0),
]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_line_single_sideded_left(self):
g = geometry.LineString([[0, 0], [0, 1]])
h = g.buffer(1, quad_segs=1, single_sided=True)
assert h.geom_type == "Polygon"
expected_coord = [(0.0, 1.0), (0.0, 0.0), (-1.0, 0.0), (-1.0, 1.0), (0.0, 1.0)]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_line_single_sideded_right(self):
g = geometry.LineString([[0, 0], [0, 1]])
h = g.buffer(-1, quad_segs=1, single_sided=True)
assert h.geom_type == "Polygon"
expected_coord = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_polygon(self):
g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
h = g.buffer(1, quad_segs=1)
assert h.geom_type == "Polygon"
expected_coord = [
(-1.0, 0.0),
(-1.0, 1.0),
(0.0, 2.0),
(1.0, 2.0),
(2.0, 1.0),
(2.0, 0.0),
(1.0, -1.0),
(0.0, -1.0),
(-1.0, 0.0),
]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_polygon_single_sideded(self):
g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
h = g.buffer(1, quad_segs=1, single_sided=True)
assert h.geom_type == "Polygon"
expected_coord = [
(-1.0, 0.0),
(-1.0, 1.0),
(0.0, 2.0),
(1.0, 2.0),
(2.0, 1.0),
(2.0, 0.0),
(1.0, -1.0),
(0.0, -1.0),
(-1.0, 0.0),
]
for index, coord in enumerate(h.exterior.coords):
assert coord[0] == pytest.approx(expected_coord[index][0])
assert coord[1] == pytest.approx(expected_coord[index][1])
def test_enum_values(self):
assert CAP_STYLE.round == 1
assert CAP_STYLE.round == BufferCapStyle.round
assert CAP_STYLE.flat == 2
assert CAP_STYLE.flat == BufferCapStyle.flat
assert CAP_STYLE.square == 3
assert CAP_STYLE.square == BufferCapStyle.square
assert JOIN_STYLE.round == 1
assert JOIN_STYLE.round == BufferJoinStyle.round
assert JOIN_STYLE.mitre == 2
assert JOIN_STYLE.mitre == BufferJoinStyle.mitre
assert JOIN_STYLE.bevel == 3
assert JOIN_STYLE.bevel == BufferJoinStyle.bevel
def test_cap_style(self):
g = geometry.LineString([[0, 0], [1, 0]])
h = g.buffer(1, cap_style=BufferCapStyle.round)
assert h == g.buffer(1, cap_style=CAP_STYLE.round)
assert h == g.buffer(1, cap_style="round")
h = g.buffer(1, cap_style=BufferCapStyle.flat)
assert h == g.buffer(1, cap_style=CAP_STYLE.flat)
assert h == g.buffer(1, cap_style="flat")
h = g.buffer(1, cap_style=BufferCapStyle.square)
assert h == g.buffer(1, cap_style=CAP_STYLE.square)
assert h == g.buffer(1, cap_style="square")
def test_buffer_style(self):
g = geometry.LineString([[0, 0], [1, 0]])
h = g.buffer(1, join_style=BufferJoinStyle.round)
assert h == g.buffer(1, join_style=JOIN_STYLE.round)
assert h == g.buffer(1, join_style="round")
h = g.buffer(1, join_style=BufferJoinStyle.mitre)
assert h == g.buffer(1, join_style=JOIN_STYLE.mitre)
assert h == g.buffer(1, join_style="mitre")
h = g.buffer(1, join_style=BufferJoinStyle.bevel)
assert h == g.buffer(1, join_style=JOIN_STYLE.bevel)
assert h == g.buffer(1, join_style="bevel")
def test_deprecated_quadsegs():
point = geometry.Point(0, 0)
with pytest.warns(FutureWarning):
result = point.buffer(1, quadsegs=1)
expected = point.buffer(1, quad_segs=1)
assert result.equals(expected)
def test_resolution_alias():
point = geometry.Point(0, 0)
result = point.buffer(1, resolution=1)
expected = point.buffer(1, quad_segs=1)
assert result.equals(expected)
@@ -0,0 +1,51 @@
import unittest
import pytest
from shapely.geometry.polygon import LinearRing, orient, Polygon, signed_area
class SignedAreaTestCase(unittest.TestCase):
def test_triangle(self):
tri = LinearRing([(0, 0), (2, 5), (7, 0)])
assert signed_area(tri) == pytest.approx(-7 * 5 / 2)
def test_square(self):
xmin, xmax = (-1, 1)
ymin, ymax = (-2, 3)
rect = LinearRing(
[(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)]
)
assert signed_area(rect) == pytest.approx(10.0)
class RingOrientationTestCase(unittest.TestCase):
def test_ccw(self):
ring = LinearRing([(1, 0), (0, 1), (0, 0)])
assert ring.is_ccw
def test_cw(self):
ring = LinearRing([(0, 0), (0, 1), (1, 0)])
assert not ring.is_ccw
class PolygonOrienterTestCase(unittest.TestCase):
def test_no_holes(self):
ring = LinearRing([(0, 0), (0, 1), (1, 0)])
polygon = Polygon(ring)
assert not polygon.exterior.is_ccw
polygon = orient(polygon, 1)
assert polygon.exterior.is_ccw
def test_holes(self):
# fmt: off
polygon = Polygon(
[(0, 0), (0, 1), (1, 0)],
[[(0.5, 0.25), (0.25, 0.5), (0.25, 0.25)]]
)
# fmt: on
assert not polygon.exterior.is_ccw
assert polygon.interiors[0].is_ccw
polygon = orient(polygon, 1)
assert polygon.exterior.is_ccw
assert not polygon.interiors[0].is_ccw
@@ -0,0 +1,120 @@
"""
Tests for GEOSClipByRect based on unit tests from libgeos.
There are some expected differences due to Shapely's handling of empty
geometries.
"""
import pytest
from shapely.ops import clip_by_rect
from shapely.wkt import dumps as dump_wkt
from shapely.wkt import loads as load_wkt
def test_point_outside():
"""Point outside"""
geom1 = load_wkt("POINT (0 0)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def test_point_inside():
"""Point inside"""
geom1 = load_wkt("POINT (15 15)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "POINT (15 15)"
def test_point_on_boundary():
"""Point on boundary"""
geom1 = load_wkt("POINT (15 10)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def test_line_outside():
"""Line outside"""
geom1 = load_wkt("LINESTRING (0 0, -5 5)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def test_line_inside():
"""Line inside"""
geom1 = load_wkt("LINESTRING (15 15, 16 15)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 15, 16 15)"
def test_line_on_boundary():
"""Line on boundary"""
geom1 = load_wkt("LINESTRING (10 15, 10 10, 15 10)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def test_line_splitting_rectangle():
"""Line splitting rectangle"""
geom1 = load_wkt("LINESTRING (10 5, 25 20)")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 10, 20 15)"
@pytest.mark.xfail(reason="TODO issue to CCW")
def test_polygon_shell_ccw_fully_on_rectangle_boundary():
"""Polygon shell (CCW) fully on rectangle boundary"""
geom1 = load_wkt("POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert (
dump_wkt(geom2, rounding_precision=0)
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
)
@pytest.mark.xfail(reason="TODO issue to CW")
def test_polygon_shell_cc_fully_on_rectangle_boundary():
"""Polygon shell (CW) fully on rectangle boundary"""
geom1 = load_wkt("POLYGON ((10 10, 10 20, 20 20, 20 10, 10 10))")
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert (
dump_wkt(geom2, rounding_precision=0)
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
)
def polygon_hole_ccw_fully_on_rectangle_boundary():
"""Polygon hole (CCW) fully on rectangle boundary"""
geom1 = load_wkt(
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
)
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def polygon_hole_cw_fully_on_rectangle_boundary():
"""Polygon hole (CW) fully on rectangle boundary"""
geom1 = load_wkt(
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 10 20, 20 20, 20 10, 10 10))"
)
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
def polygon_fully_within_rectangle():
"""Polygon fully within rectangle"""
wkt = "POLYGON ((1 1, 1 30, 30 30, 30 1, 1 1), (10 10, 20 10, 20 20, 10 20, 10 10))"
geom1 = load_wkt(wkt)
geom2 = clip_by_rect(geom1, 0, 0, 40, 40)
assert dump_wkt(geom2, rounding_precision=0) == wkt
def polygon_overlapping_rectangle():
"""Polygon overlapping rectangle"""
wkt = "POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
geom1 = load_wkt(wkt)
geom2 = clip_by_rect(geom1, 5, 5, 15, 15)
assert (
dump_wkt(geom2, rounding_precision=0)
== "POLYGON ((5 5, 5 15, 10 15, 10 10, 15 10, 15 5, 5 5))"
)
@@ -0,0 +1,214 @@
"""
When a "context" passed to shape/asShape has a coordinate
which is missing a dimension we should raise a descriptive error.
When we use mixed dimensions in a WKT geometry, the parser strips
any dimension which is not present in every coordinate.
"""
import pytest
from shapely import wkt
from shapely.errors import GEOSException
from shapely.geometry import LineString, Polygon, shape
from shapely.geos import geos_version
geojson_cases = [
{"type": "LineString", "coordinates": [[1, 1, 1], [2, 2]]},
# Specific test case from #869
{
"type": "Polygon",
"coordinates": [
[
[55.12916764533149, 24.980385694214384, 2.5],
[55.13098248044217, 24.979828079961905],
[55.13966519231666, 24.97801442415322],
[55.13966563924936, 24.97801442415322],
[55.14139286840762, 24.982307444496097],
[55.14169331277646, 24.983717465495562],
[55.14203489144224, 24.985419446276566, 2.5],
[55.14180327151276, 24.98428602667792, 2.5],
[55.14170091915952, 24.984242720177235, 2.5],
[55.14122966992623, 24.984954809433702, 2.5],
[55.14134021791831, 24.985473928648396, 2.5],
[55.141405876161286, 24.986090184809793, 2.5],
[55.141361358941225, 24.986138101357326, 2.5],
[55.14093322994411, 24.986218753894093, 2.5],
[55.140897653420964, 24.986214283545635, 2.5],
[55.14095492976058, 24.9863027591922, 2.5],
[55.140900447388745, 24.98628436557094, 2.5],
[55.140867059473706, 24.98628869622101, 2.5],
[55.14089155325796, 24.986402364143782, 2.5],
[55.14090938808566, 24.986479011993385, 2.5],
[55.140943893587824, 24.986471188883584, 2.5],
[55.1410161176551, 24.9864174050037, 2.5],
[55.140996932409635, 24.986521806266644, 2.5],
[55.14163554031332, 24.986910400619593, 2.5],
[55.14095781686062, 24.987033474900578, 2.5],
[55.14058258698692, 24.98693261266349, 2.5],
[55.14032624044253, 24.98747538747211, 2.5],
[55.14007240846915, 24.988001119077232, 2.5],
[55.14013122149105, 24.98831115636925, 2.5],
[55.13991827457961, 24.98834356639557, 2.5],
[55.139779460946755, 24.988254625087706, 2.5],
[55.13974742344948, 24.988261377176524, 2.5],
[55.139515198160304, 24.98841811876934, 2.5],
[55.13903617238334, 24.98817914139135, 2.5],
[55.1391330764994, 24.988660542040925, 2.5],
[55.13914369357698, 24.989438289540374, 2.5],
[55.136431216517785, 24.98966711550207, 2.0],
[55.13659028641709, 24.99041706302204, 2.0],
[55.1355852030721, 24.990933481401207, 2.5],
[55.13535549235394, 24.99110470506038, 2.5],
[55.13512578163577, 24.99127592871955, 2.5],
[55.129969653784556, 24.991440074326995, 2.5],
[55.130221623112746, 24.988070688875112, 2.5],
[55.130451333830905, 24.98789946521594, 2.5],
[55.13089208224919, 24.98742639990359, 2.5],
[55.132177586827666, 24.989003408454433, 2.5],
[55.13238862452779, 24.988701566801254, 2.5],
[55.132482594977674, 24.988501518707757, 2.5],
[55.132525994610624, 24.988048802794115, 2.5],
[55.13249018525683, 24.987180623870653, 2.5],
[55.13253358488978, 24.986727907957015, 2.5],
[55.1322761673244, 24.985827132742713, 2.5],
[55.13163341503516, 24.98503862846729, 2.5],
[55.131514764536504, 24.984469124700183, 2.5],
[55.131275600894, 24.983796337257242, 2.0],
[55.13066865795855, 24.98387601190528, 2.0],
[55.13026930682963, 24.981537228037503, 2.0],
[55.130260412698846, 24.981495691049748, 2.0],
[55.13025151856806, 24.981454154061993, 2.0],
[55.13022925995803, 24.98096497686874, 2.5],
[55.12984453059386, 24.9804285816199, 2.5],
[55.129998291954365, 24.98021419115843, 2.5],
[55.12916764533149, 24.980385694214384, 2.5],
]
],
},
]
direct_cases = [
(LineString, [[[0, 0, 0], [1, 1]]]),
(Polygon, [[[0, 0, 0], [1, 1, 0], [1, 1], [0, 1, 0], [0, 0, 0]]]),
# Specific test case from #869
(
Polygon,
[
[
[55.12916764533149, 24.980385694214384, 2.5],
[55.13098248044217, 24.979828079961905],
[55.13966519231666, 24.97801442415322],
[55.13966563924936, 24.97801442415322],
[55.14139286840762, 24.982307444496097],
[55.14169331277646, 24.983717465495562],
[55.14203489144224, 24.985419446276566, 2.5],
[55.14180327151276, 24.98428602667792, 2.5],
[55.14170091915952, 24.984242720177235, 2.5],
[55.14122966992623, 24.984954809433702, 2.5],
[55.14134021791831, 24.985473928648396, 2.5],
[55.141405876161286, 24.986090184809793, 2.5],
[55.141361358941225, 24.986138101357326, 2.5],
[55.14093322994411, 24.986218753894093, 2.5],
[55.140897653420964, 24.986214283545635, 2.5],
[55.14095492976058, 24.9863027591922, 2.5],
[55.140900447388745, 24.98628436557094, 2.5],
[55.140867059473706, 24.98628869622101, 2.5],
[55.14089155325796, 24.986402364143782, 2.5],
[55.14090938808566, 24.986479011993385, 2.5],
[55.140943893587824, 24.986471188883584, 2.5],
[55.1410161176551, 24.9864174050037, 2.5],
[55.140996932409635, 24.986521806266644, 2.5],
[55.14163554031332, 24.986910400619593, 2.5],
[55.14095781686062, 24.987033474900578, 2.5],
[55.14058258698692, 24.98693261266349, 2.5],
[55.14032624044253, 24.98747538747211, 2.5],
[55.14007240846915, 24.988001119077232, 2.5],
[55.14013122149105, 24.98831115636925, 2.5],
[55.13991827457961, 24.98834356639557, 2.5],
[55.139779460946755, 24.988254625087706, 2.5],
[55.13974742344948, 24.988261377176524, 2.5],
[55.139515198160304, 24.98841811876934, 2.5],
[55.13903617238334, 24.98817914139135, 2.5],
[55.1391330764994, 24.988660542040925, 2.5],
[55.13914369357698, 24.989438289540374, 2.5],
[55.136431216517785, 24.98966711550207, 2.0],
[55.13659028641709, 24.99041706302204, 2.0],
[55.1355852030721, 24.990933481401207, 2.5],
[55.13535549235394, 24.99110470506038, 2.5],
[55.13512578163577, 24.99127592871955, 2.5],
[55.129969653784556, 24.991440074326995, 2.5],
[55.130221623112746, 24.988070688875112, 2.5],
[55.130451333830905, 24.98789946521594, 2.5],
[55.13089208224919, 24.98742639990359, 2.5],
[55.132177586827666, 24.989003408454433, 2.5],
[55.13238862452779, 24.988701566801254, 2.5],
[55.132482594977674, 24.988501518707757, 2.5],
[55.132525994610624, 24.988048802794115, 2.5],
[55.13249018525683, 24.987180623870653, 2.5],
[55.13253358488978, 24.986727907957015, 2.5],
[55.1322761673244, 24.985827132742713, 2.5],
[55.13163341503516, 24.98503862846729, 2.5],
[55.131514764536504, 24.984469124700183, 2.5],
[55.131275600894, 24.983796337257242, 2.0],
[55.13066865795855, 24.98387601190528, 2.0],
[55.13026930682963, 24.981537228037503, 2.0],
[55.130260412698846, 24.981495691049748, 2.0],
[55.13025151856806, 24.981454154061993, 2.0],
[55.13022925995803, 24.98096497686874, 2.5],
[55.12984453059386, 24.9804285816199, 2.5],
[55.129998291954365, 24.98021419115843, 2.5],
[55.12916764533149, 24.980385694214384, 2.5],
]
],
),
]
wkt_cases = [
# preserve 3rd dimension
("MULTIPOINT (1 1 1, 2 2)", "MULTIPOINT Z (1 1 1, 2 2 0)"),
("MULTIPOINT (1 1, 2 2 2)", "MULTIPOINT Z (1 1 0, 2 2 2)"),
("LINESTRING (1 1 1, 2 2)", "LINESTRING Z (1 1 1, 2 2 0)"),
(
"POLYGON ((0 0 0, 1 0 0, 1 1, 0 1 0, 0 0 0))",
"POLYGON Z ((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))",
),
# drop 3rd dimension
("LINESTRING (1 1, 2 2 2)", "LINESTRING (1 1, 2 2)"),
("POLYGON ((0 0, 1 0 1, 1 1, 0 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"),
]
@pytest.mark.filterwarnings("ignore:Creating an ndarray from ragged nested sequences:")
@pytest.mark.parametrize("geojson", geojson_cases)
def test_create_from_geojson(geojson):
# exact error depends on numpy version
with pytest.raises((ValueError, TypeError)) as exc:
shape(geojson).wkt
assert exc.match(
"Inconsistent coordinate dimensionality|Input operand 0 does not have enough dimensions|ufunc 'linestrings' not supported for the input types|setting an array element with a sequence. The requested array has an inhomogeneous shape"
)
@pytest.mark.filterwarnings("ignore:Creating an ndarray from ragged nested sequences:")
@pytest.mark.parametrize("constructor, args", direct_cases)
def test_create_directly(constructor, args):
with pytest.raises((ValueError, TypeError)) as exc:
constructor(*args)
assert exc.match(
"Inconsistent coordinate dimensionality|Input operand 0 does not have enough dimensions|ufunc 'linestrings' not supported for the input types|setting an array element with a sequence. The requested array has an inhomogeneous shape"
)
@pytest.mark.parametrize("wkt_geom,expected", wkt_cases)
def test_create_from_wkt(wkt_geom, expected):
if geos_version >= (3, 12, 0):
# https://github.com/shapely/shapely/issues/1541
with pytest.raises(GEOSException):
wkt.loads(wkt_geom)
else:
geom = wkt.loads(wkt_geom)
assert geom.wkt == expected
@@ -0,0 +1,32 @@
import unittest
from shapely.geometry import LineString, Point, Polygon
from shapely.ops import triangulate
class DelaunayTriangulation(unittest.TestCase):
"""
Only testing the number of triangles and their type here.
This doesn't actually test the points in the resulting geometries.
"""
def setUp(self):
self.p = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
def test_polys(self):
polys = triangulate(self.p)
assert len(polys) == 2
for p in polys:
assert isinstance(p, Polygon)
def test_lines(self):
polys = triangulate(self.p, edges=True)
assert len(polys) == 5
for p in polys:
assert isinstance(p, LineString)
def test_point(self):
p = Point(1, 1)
polys = triangulate(p)
assert len(polys) == 0
@@ -0,0 +1,22 @@
from shapely.geometry import MultiPolygon, Point, Polygon
def test_empty_polygon():
"""No constructor arg makes an empty polygon geometry."""
assert Polygon().is_empty
def test_empty_multipolygon():
"""No constructor arg makes an empty multipolygon geometry."""
assert MultiPolygon().is_empty
def test_multipolygon_empty_polygon():
"""An empty polygon passed to MultiPolygon() makes an empty
multipolygon geometry."""
assert MultiPolygon([Polygon()]).is_empty
def test_multipolygon_empty_among_polygon():
"""An empty polygon passed to MultiPolygon() is ignored."""
assert len(MultiPolygon([Point(0, 0).buffer(1.0), Polygon()]).geoms) == 1
@@ -0,0 +1,32 @@
import pytest
from shapely import Point
from shapely.errors import ShapelyDeprecationWarning
def test_equals_exact():
p1 = Point(1.0, 1.0)
p2 = Point(2.0, 2.0)
assert not p1.equals(p2)
assert not p1.equals_exact(p2, 0.001)
assert p1.equals_exact(p2, 1.42)
def test_almost_equals_default():
p1 = Point(1.0, 1.0)
p2 = Point(1.0 + 1e-7, 1.0 + 1e-7) # almost equal to 6 places
p3 = Point(1.0 + 1e-6, 1.0 + 1e-6) # not almost equal
with pytest.warns(ShapelyDeprecationWarning):
assert p1.almost_equals(p2)
with pytest.warns(ShapelyDeprecationWarning):
assert not p1.almost_equals(p3)
def test_almost_equals():
p1 = Point(1.0, 1.0)
p2 = Point(1.1, 1.1)
assert not p1.equals(p2)
with pytest.warns(ShapelyDeprecationWarning):
assert p1.almost_equals(p2, 0)
with pytest.warns(ShapelyDeprecationWarning):
assert not p1.almost_equals(p2, 1)
@@ -0,0 +1,118 @@
import unittest
from shapely import wkt
from shapely.geometry import shape
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.polygon import LinearRing, Polygon
class GeoThing:
def __init__(self, d):
self.__geo_interface__ = d
class GeoInterfaceTestCase(unittest.TestCase):
def test_geointerface(self):
# Convert a dictionary
d = {"type": "Point", "coordinates": (0.0, 0.0)}
geom = shape(d)
assert geom.geom_type == "Point"
assert tuple(geom.coords) == ((0.0, 0.0),)
# Convert an object that implements the geo protocol
geom = None
thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)})
geom = shape(thing)
assert geom.geom_type == "Point"
assert tuple(geom.coords) == ((0.0, 0.0),)
# Check line string
geom = shape({"type": "LineString", "coordinates": ((-1.0, -1.0), (1.0, 1.0))})
assert isinstance(geom, LineString)
assert tuple(geom.coords) == ((-1.0, -1.0), (1.0, 1.0))
# Check linearring
geom = shape(
{
"type": "LinearRing",
"coordinates": (
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(2.0, -1.0),
(0.0, 0.0),
),
}
)
assert isinstance(geom, LinearRing)
assert tuple(geom.coords) == (
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(2.0, -1.0),
(0.0, 0.0),
)
# polygon
geom = shape(
{
"type": "Polygon",
"coordinates": (
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)),
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
),
}
)
assert isinstance(geom, Polygon)
assert tuple(geom.exterior.coords) == (
(0.0, 0.0),
(0.0, 1.0),
(1.0, 1.0),
(2.0, -1.0),
(0.0, 0.0),
)
assert len(geom.interiors) == 1
# multi point
geom = shape({"type": "MultiPoint", "coordinates": ((1.0, 2.0), (3.0, 4.0))})
assert isinstance(geom, MultiPoint)
assert len(geom.geoms) == 2
# multi line string
geom = shape(
{"type": "MultiLineString", "coordinates": (((0.0, 0.0), (1.0, 2.0)),)}
)
assert isinstance(geom, MultiLineString)
assert len(geom.geoms) == 1
# multi polygon
geom = shape(
{
"type": "MultiPolygon",
"coordinates": [
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
)
],
}
)
assert isinstance(geom, MultiPolygon)
assert len(geom.geoms) == 1
def test_empty_wkt_polygon():
"""Confirm fix for issue #450"""
g = wkt.loads("POLYGON EMPTY")
assert g.__geo_interface__["type"] == "Polygon"
assert g.__geo_interface__["coordinates"] == ()
def test_empty_polygon():
"""Confirm fix for issue #450"""
g = Polygon()
assert g.__geo_interface__["type"] == "Polygon"
assert g.__geo_interface__["coordinates"] == ()
@@ -0,0 +1,28 @@
"""Test recovery from operation on invalid geometries
"""
import unittest
import pytest
import shapely
from shapely.errors import TopologicalError
from shapely.geometry import Polygon
class InvalidGeometriesTestCase(unittest.TestCase):
def test_invalid_intersection(self):
# Make a self-intersecting polygon
polygon_invalid = Polygon([(0, 0), (1, 1), (1, -1), (0, 1), (0, 0)])
assert not polygon_invalid.is_valid
# Intersect with a valid polygon
polygon = Polygon([(-0.5, -0.5), (-0.5, 0.5), (0.5, 0.5), (0.5, -5)])
assert polygon.is_valid
assert polygon_invalid.intersects(polygon)
with pytest.raises((TopologicalError, shapely.GEOSException)):
polygon_invalid.intersection(polygon)
with pytest.raises((TopologicalError, shapely.GEOSException)):
polygon.intersection(polygon_invalid)
return
@@ -0,0 +1,72 @@
import unittest
import pytest
import shapely
from shapely.geometry import LineString, MultiLineString, Point
class LinearReferencingTestCase(unittest.TestCase):
def setUp(self):
self.point = Point(1, 1)
self.line1 = LineString([(0, 0), (2, 0)])
self.line2 = LineString([(3, 0), (3, 6)])
self.multiline = MultiLineString(
[list(self.line1.coords), list(self.line2.coords)]
)
def test_line1_project(self):
assert self.line1.project(self.point) == 1.0
assert self.line1.project(self.point, normalized=True) == 0.5
def test_alias_project(self):
assert self.line1.line_locate_point(self.point) == 1.0
assert self.line1.line_locate_point(self.point, normalized=True) == 0.5
def test_line2_project(self):
assert self.line2.project(self.point) == 1.0
assert self.line2.project(self.point, normalized=True) == pytest.approx(
0.16666666666, 8
)
def test_multiline_project(self):
assert self.multiline.project(self.point) == 1.0
assert self.multiline.project(self.point, normalized=True) == 0.125
def test_not_supported_project(self):
with pytest.raises(shapely.GEOSException, match="IllegalArgumentException"):
self.point.buffer(1.0).project(self.point)
def test_not_on_line_project(self):
# Points that aren't on the line project to 0.
assert self.line1.project(Point(-10, -10)) == 0.0
def test_line1_interpolate(self):
assert self.line1.interpolate(0.5).equals(Point(0.5, 0.0))
assert self.line1.interpolate(-0.5).equals(Point(1.5, 0.0))
assert self.line1.interpolate(0.5, normalized=True).equals(Point(1, 0))
assert self.line1.interpolate(-0.5, normalized=True).equals(Point(1, 0))
def test_alias_interpolate(self):
assert self.line1.line_interpolate_point(0.5).equals(Point(0.5, 0.0))
assert self.line1.line_interpolate_point(-0.5).equals(Point(1.5, 0.0))
assert self.line1.line_interpolate_point(0.5, normalized=True).equals(
Point(1, 0)
)
assert self.line1.line_interpolate_point(-0.5, normalized=True).equals(
Point(1, 0)
)
def test_line2_interpolate(self):
assert self.line2.interpolate(0.5).equals(Point(3.0, 0.5))
assert self.line2.interpolate(0.5, normalized=True).equals(Point(3, 3))
def test_multiline_interpolate(self):
assert self.multiline.interpolate(0.5).equals(Point(0.5, 0))
assert self.multiline.interpolate(0.5, normalized=True).equals(Point(3.0, 2.0))
def test_line_ends_interpolate(self):
# Distances greater than length of the line or less than
# zero yield the line's ends.
assert self.line1.interpolate(-1000).equals(Point(0.0, 0.0))
assert self.line1.interpolate(1000).equals(Point(2.0, 0.0))
@@ -0,0 +1,44 @@
import unittest
from shapely.geometry import LineString, MultiLineString
from shapely.ops import linemerge
class LineMergeTestCase(unittest.TestCase):
def test_linemerge(self):
lines = MultiLineString([[(0, 0), (1, 1)], [(2, 0), (2, 1), (1, 1)]])
result = linemerge(lines)
assert isinstance(result, LineString)
assert not result.is_ring
assert len(result.coords) == 4
assert result.coords[0] == (0.0, 0.0)
assert result.coords[3] == (2.0, 0.0)
lines2 = MultiLineString([((0, 0), (1, 1)), ((0, 0), (2, 0), (2, 1), (1, 1))])
result = linemerge(lines2)
assert result.is_ring
assert len(result.coords) == 5
lines3 = [
LineString([(0, 0), (1, 1)]),
LineString([(0, 0), (0, 1)]),
]
result = linemerge(lines3)
assert not result.is_ring
assert len(result.coords) == 3
assert result.coords[0] == (0.0, 1.0)
assert result.coords[2] == (1.0, 1.0)
lines4 = [
[(0, 0), (1, 1)],
[(0, 0), (0, 1)],
]
assert result.equals(linemerge(lines4))
lines5 = [
((0, 0), (1, 1)),
((1, 0), (0, 1)),
]
result = linemerge(lines5)
assert result.geom_type == "MultiLineString"
@@ -0,0 +1,56 @@
"""Test locale independence of WKT
"""
import locale
import sys
import unittest
from shapely.wkt import dumps, loads
# Set locale to one that uses a comma as decimal separator
# TODO: try a few other common locales
if sys.platform == "win32":
test_locales = {"Portuguese": "portuguese_brazil", "Italian": "italian_italy"}
else:
test_locales = {
"Portuguese": "pt_BR.UTF-8",
"Italian": "it_IT.UTF-8",
}
do_test_locale = False
def setUpModule():
global do_test_locale
for name in test_locales:
try:
test_locale = test_locales[name]
locale.setlocale(locale.LC_ALL, test_locale)
do_test_locale = True
break
except Exception:
pass
if not do_test_locale:
raise unittest.SkipTest("test locale not found")
def tearDownModule():
if sys.platform == "win32" or sys.version_info[0:2] >= (3, 11):
locale.setlocale(locale.LC_ALL, "")
else:
# Deprecated since version 3.11, will be removed in version 3.13
locale.resetlocale()
class LocaleTestCase(unittest.TestCase):
# @unittest.skipIf(not do_test_locale, 'test locale not found')
def test_wkt_locale(self):
# Test reading and writing
p = loads("POINT (0.0 0.0)")
assert p.x == 0.0
assert p.y == 0.0
wkt = dumps(p)
assert wkt.startswith("POINT")
assert "," not in wkt
@@ -0,0 +1,18 @@
from shapely.geometry import Polygon
from shapely.tests.legacy.conftest import requires_geos_38
from shapely.validation import make_valid
@requires_geos_38
def test_make_valid_invalid_input():
geom = Polygon([(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)])
valid = make_valid(geom)
assert len(valid.geoms) == 2
assert all(geom.geom_type == "Polygon" for geom in valid.geoms)
@requires_geos_38
def test_make_valid_input():
geom = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
valid = make_valid(geom)
assert id(valid) == id(geom)
@@ -0,0 +1,14 @@
import unittest
from shapely.geometry import mapping, Point, Polygon
class MappingTestCase(unittest.TestCase):
def test_point(self):
m = mapping(Point(0, 0))
assert m["type"] == "Point"
assert m["coordinates"] == (0.0, 0.0)
def test_empty_polygon(self):
"""Empty polygons will round trip without error"""
assert mapping(Polygon()) is not None
@@ -0,0 +1,40 @@
"""
Tests for the minimum clearance property.
"""
import math
import pytest
from shapely.geos import geos_version
from shapely.wkt import loads as load_wkt
requires_geos_36 = pytest.mark.skipif(
geos_version < (3, 6, 0), reason="GEOS >= 3.6.0 is required."
)
@requires_geos_36
def test_point():
point = load_wkt("POINT (0 0)")
assert point.minimum_clearance == math.inf
@requires_geos_36
def test_linestring():
line = load_wkt("LINESTRING (0 0, 1 1, 2 2)")
assert round(line.minimum_clearance, 6) == 1.414214
@requires_geos_36
def test_simple_polygon():
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
assert poly.minimum_clearance == 1.0
@requires_geos_36
def test_more_complicated_polygon():
poly = load_wkt(
"POLYGON ((20 20, 34 124, 70 140, 130 130, 70 100, 110 70, 170 20, 90 10, 20 20))"
)
assert round(poly.minimum_clearance, 6) == 35.777088
@@ -0,0 +1,42 @@
# Tests of support for Numpy ndarrays. See
# https://github.com/sgillies/shapely/issues/26 for discussion.
import unittest
from functools import reduce
import numpy as np
from shapely import geometry
class TransposeTestCase(unittest.TestCase):
def test_multipoint(self):
arr = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
tarr = arr.T
shape = geometry.MultiPoint(tarr)
coords = reduce(lambda x, y: x + y, [list(g.coords) for g in shape.geoms])
assert coords == [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)]
def test_linestring(self):
a = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
t = a.T
s = geometry.LineString(t)
assert list(s.coords) == [
(1.0, 3.0),
(1.0, 4.0),
(2.0, 4.0),
(2.0, 3.0),
(1.0, 3.0),
]
def test_polygon(self):
a = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
t = a.T
s = geometry.Polygon(t)
assert list(s.exterior.coords) == [
(1.0, 3.0),
(1.0, 4.0),
(2.0, 4.0),
(2.0, 3.0),
(1.0, 3.0),
]
@@ -0,0 +1,18 @@
import unittest
import pytest
from shapely.geometry import Point
from shapely.ops import nearest_points
class Nearest(unittest.TestCase):
def test_nearest(self):
first, second = nearest_points(
Point(0, 0).buffer(1.0),
Point(3, 0).buffer(1.0),
)
assert first.x == pytest.approx(1.0)
assert second.x == pytest.approx(2.0)
assert first.y == pytest.approx(0.0)
assert second.y == pytest.approx(0.0)
@@ -0,0 +1,122 @@
import unittest
import pytest
import shapely
from shapely import geos_version
from shapely.errors import TopologicalError
from shapely.geometry import GeometryCollection, LineString, MultiPoint, Point, Polygon
from shapely.wkt import loads
class OperationsTestCase(unittest.TestCase):
def test_operations(self):
point = Point(0.0, 0.0)
# General geometry
assert point.area == 0.0
assert point.length == 0.0
assert point.distance(Point(-1.0, -1.0)) == pytest.approx(1.4142135623730951)
# Topology operations
# Envelope
assert isinstance(point.envelope, Point)
# Intersection
assert point.intersection(Point(-1, -1)).is_empty
# Buffer
assert isinstance(point.buffer(10.0), Polygon)
assert isinstance(point.buffer(10.0, 32), Polygon)
# Simplify
p = loads(
"POLYGON ((120 120, 140 199, 160 200, 180 199, 220 120, 122 122, 121 121, 120 120))"
)
expected = loads(
"POLYGON ((120 120, 140 199, 160 200, 180 199, 220 120, 120 120))"
)
s = p.simplify(10.0, preserve_topology=False)
assert s.equals_exact(expected, 0.001)
p = loads(
"POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),"
"(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))"
)
expected = loads(
"POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),"
"(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))"
)
s = p.simplify(10.0, preserve_topology=True)
assert s.equals_exact(expected, 0.001)
# Convex Hull
assert isinstance(point.convex_hull, Point)
# Differences
assert isinstance(point.difference(Point(-1, 1)), Point)
assert isinstance(point.symmetric_difference(Point(-1, 1)), MultiPoint)
# Boundary
assert isinstance(point.boundary, GeometryCollection)
# Union
assert isinstance(point.union(Point(-1, 1)), MultiPoint)
assert isinstance(point.representative_point(), Point)
assert isinstance(point.point_on_surface(), Point)
assert point.representative_point() == point.point_on_surface()
assert isinstance(point.centroid, Point)
def test_relate(self):
# Relate
assert Point(0, 0).relate(Point(-1, -1)) == "FF0FFF0F2"
# issue #294: should raise TopologicalError on exception
invalid_polygon = loads(
"POLYGON ((40 100, 80 100, 80 60, 40 60, 40 100), (60 60, 80 60, 80 40, 60 40, 60 60))"
)
assert not invalid_polygon.is_valid
if geos_version < (3, 13, 0):
with pytest.raises((TopologicalError, shapely.GEOSException)):
invalid_polygon.relate(invalid_polygon)
else: # resolved with RelateNG
assert invalid_polygon.relate(invalid_polygon) == "2FFF1FFF2"
def test_hausdorff_distance(self):
point = Point(1, 1)
line = LineString([(2, 0), (2, 4), (3, 4)])
distance = point.hausdorff_distance(line)
assert distance == point.distance(Point(3, 4))
def test_interpolate(self):
# successful interpolation
test_line = LineString([(1, 1), (1, 2)])
known_point = Point(1, 1.5)
interpolated_point = test_line.interpolate(0.5, normalized=True)
assert interpolated_point == known_point
# Issue #653; should nog segfault for empty geometries
empty_line = loads("LINESTRING EMPTY")
assert empty_line.is_empty
interpolated_point = empty_line.interpolate(0.5, normalized=True)
assert interpolated_point.is_empty
# invalid geometry should raise TypeError on exception
polygon = loads("POLYGON EMPTY")
with pytest.raises(TypeError, match="incorrect geometry type"):
polygon.interpolate(0.5, normalized=True)
def test_normalize(self):
point = Point(1, 1)
result = point.normalize()
assert result == point
line = loads("MULTILINESTRING ((1 1, 0 0), (1 1, 1 2))")
result = line.normalize()
expected = loads("MULTILINESTRING ((1 1, 1 2), (0 0, 1 1))")
assert result == expected
@@ -0,0 +1,60 @@
import unittest
from shapely.geometry import LineString, MultiPoint, Point, Polygon
class OperatorsTestCase(unittest.TestCase):
def test_point(self):
point = Point(0, 0)
point2 = Point(-1, 1)
assert point.union(point2).equals(point | point2)
assert (point & point2).is_empty
assert point.equals(point - point2)
assert point.symmetric_difference(point2).equals(point ^ point2)
assert point != point2
point_dupe = Point(0, 0)
assert point, point_dupe
def test_multipoint(self):
mp1 = MultiPoint([(0, 0), (1, 1)])
mp1_dup = MultiPoint([(0, 0), (1, 1)])
mp1_rev = MultiPoint([(1, 1), (0, 0)])
mp2 = MultiPoint([(0, 0), (1, 1), (2, 2)])
mp3 = MultiPoint([(0, 0), (1, 1), (2, 3)])
assert mp1 == mp1_dup
assert mp1 != mp1_rev
assert mp1 != mp2
assert mp2 != mp3
p = Point(0, 0)
mp = MultiPoint([(0, 0)])
assert p != mp
assert mp != p
def test_polygon(self):
shell = ((0, 0), (3, 0), (3, 3), (0, 3))
hole = ((1, 1), (2, 1), (2, 2), (1, 2))
p_solid = Polygon(shell)
p2_solid = Polygon(shell)
p_hole = Polygon(shell, holes=[hole])
p2_hole = Polygon(shell, holes=[hole])
assert p_solid == p2_solid
assert p_hole == p2_hole
assert p_solid != p_hole
shell2 = ((-5, 2), (10.5, 3), (7, 3))
p3_hole = Polygon(shell2, holes=[hole])
assert p_hole != p3_hole
def test_linestring(self):
line1 = LineString([(0, 0), (1, 1), (2, 2)])
line2 = LineString([(0, 0), (2, 2)])
line2_dup = LineString([(0, 0), (2, 2)])
# .equals() indicates these are the same
assert line1.equals(line2)
# but != indicates these are different
assert line1 != line2
# but dupes are the same with ==
assert line2 == line2_dup
@@ -0,0 +1,64 @@
import unittest
from shapely.geometry import (
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
from shapely.ops import orient
class OrientTestCase(unittest.TestCase):
def test_point(self):
point = Point(0, 0)
assert orient(point, 1) == point
assert orient(point, -1) == point
def test_multipoint(self):
multipoint = MultiPoint([(0, 0), (1, 1)])
assert orient(multipoint, 1) == multipoint
assert orient(multipoint, -1) == multipoint
def test_linestring(self):
linestring = LineString([(0, 0), (1, 1)])
assert orient(linestring, 1) == linestring
assert orient(linestring, -1) == linestring
def test_multilinestring(self):
multilinestring = MultiLineString([[(0, 0), (1, 1)], [(1, 0), (0, 1)]])
assert orient(multilinestring, 1) == multilinestring
assert orient(multilinestring, -1) == multilinestring
def test_linearring(self):
linearring = LinearRing([(0, 0), (0, 1), (1, 0)])
assert orient(linearring, 1) == linearring
assert orient(linearring, -1) == linearring
def test_polygon(self):
polygon = Polygon([(0, 0), (0, 1), (1, 0)])
polygon_reversed = Polygon(polygon.exterior.coords[::-1])
assert (orient(polygon, 1)) == polygon_reversed
assert (orient(polygon, -1)) == polygon
def test_multipolygon(self):
polygon1 = Polygon([(0, 0), (0, 1), (1, 0)])
polygon2 = Polygon([(1, 0), (2, 0), (2, 1)])
polygon1_reversed = Polygon(polygon1.exterior.coords[::-1])
polygon2_reversed = Polygon(polygon2.exterior.coords[::-1])
multipolygon = MultiPolygon([polygon1, polygon2])
assert not polygon1.exterior.is_ccw
assert polygon2.exterior.is_ccw
assert orient(multipolygon, 1) == MultiPolygon([polygon1_reversed, polygon2])
assert orient(multipolygon, -1) == MultiPolygon([polygon1, polygon2_reversed])
def test_geometrycollection(self):
polygon = Polygon([(0, 0), (0, 1), (1, 0)])
polygon_reversed = Polygon(polygon.exterior.coords[::-1])
collection = GeometryCollection([polygon])
assert orient(collection, 1) == GeometryCollection([polygon_reversed])
assert orient(collection, -1) == GeometryCollection([polygon])
@@ -0,0 +1,60 @@
import unittest
import pytest
from shapely.geometry import LinearRing, LineString
from shapely.testing import assert_geometries_equal
@pytest.mark.parametrize("distance", [float("nan"), float("inf")])
def test_non_finite_distance(distance):
g = LineString([(0, 0), (10, 0)])
with pytest.raises(ValueError, match="distance must be finite"):
g.parallel_offset(distance)
class OperationsTestCase(unittest.TestCase):
def test_parallel_offset_linestring(self):
line1 = LineString([(0, 0), (10, 0)])
left = line1.parallel_offset(5, "left")
assert_geometries_equal(left, LineString([(0, 5), (10, 5)]))
right = line1.parallel_offset(5, "right")
assert_geometries_equal(right, LineString([(10, -5), (0, -5)]), normalize=True)
right = line1.parallel_offset(-5, "left")
assert_geometries_equal(right, LineString([(10, -5), (0, -5)]), normalize=True)
left = line1.parallel_offset(-5, "right")
assert_geometries_equal(left, LineString([(0, 5), (10, 5)]))
# by default, parallel_offset is right-handed
assert_geometries_equal(line1.parallel_offset(5), right)
line2 = LineString([(0, 0), (5, 0), (5, -5)])
assert_geometries_equal(
line2.parallel_offset(2, "left", join_style=3),
LineString([(0, 2), (5, 2), (7, 0), (7, -5)]),
)
assert_geometries_equal(
line2.parallel_offset(2, "left", join_style=2),
LineString([(0, 2), (7, 2), (7, -5)]),
)
# offset_curve alias
assert_geometries_equal(
line1.offset_curve(2, quad_segs=10),
line1.parallel_offset(2, "left", resolution=10),
)
assert_geometries_equal(
line1.offset_curve(-2, join_style="mitre"),
line1.parallel_offset(2, "right", join_style=2),
)
def test_parallel_offset_linear_ring(self):
lr1 = LinearRing([(0, 0), (5, 0), (5, 5), (0, 5), (0, 0)])
assert_geometries_equal(
lr1.parallel_offset(2, "left", resolution=1),
LineString([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)]),
)
# offset_curve alias
assert_geometries_equal(
lr1.offset_curve(2, quad_segs=1),
lr1.parallel_offset(2, "left", resolution=1),
)
@@ -0,0 +1,51 @@
"""Persistence tests
"""
import pickle
import struct
import unittest
from shapely import wkb, wkt
from shapely.geometry import Point
class PersistTestCase(unittest.TestCase):
def test_pickle(self):
p = Point(0.0, 0.0)
data = pickle.dumps(p)
q = pickle.loads(data)
assert q.equals(p)
def test_wkb(self):
p = Point(0.0, 0.0)
wkb_big_endian = wkb.dumps(p, big_endian=True)
wkb_little_endian = wkb.dumps(p, big_endian=False)
# Regardless of byte order, loads ought to correctly recover the
# geometry
assert p.equals(wkb.loads(wkb_big_endian))
assert p.equals(wkb.loads(wkb_little_endian))
def test_wkb_dumps_endianness(self):
p = Point(0.5, 2.0)
wkb_big_endian = wkb.dumps(p, big_endian=True)
wkb_little_endian = wkb.dumps(p, big_endian=False)
assert wkb_big_endian != wkb_little_endian
# According to WKB specification in section 3.3 of OpenGIS
# Simple Features Specification for SQL, revision 1.1, the
# first byte of a WKB representation indicates byte order.
# Big-endian is 0, little-endian is 1.
assert wkb_big_endian[0] == 0
assert wkb_little_endian[0] == 1
# Check that the doubles (0.5, 2.0) are in correct byte order
double_size = struct.calcsize("d")
assert wkb_big_endian[(-2 * double_size) :] == struct.pack(">2d", p.x, p.y)
assert wkb_little_endian[(-2 * double_size) :] == struct.pack("<2d", p.x, p.y)
def test_wkt(self):
p = Point(0.0, 0.0)
text = wkt.dumps(p)
assert text.startswith("POINT")
pt = wkt.loads(text)
assert pt.equals(p)
@@ -0,0 +1,84 @@
import pathlib
import pickle
import warnings
from pickle import dumps, HIGHEST_PROTOCOL, loads
import pytest
import shapely
from shapely import wkt
from shapely.geometry import (
box,
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
HERE = pathlib.Path(__file__).parent
TEST_DATA = {
"point2d": Point([(1.0, 2.0)]),
"point3d": Point([(1.0, 2.0, 3.0)]),
"linestring": LineString([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]),
"linearring": LinearRing([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
"polygon": Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
"multipoint": MultiPoint([(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)]),
"multilinestring": MultiLineString(
[[(0.0, 0.0), (1.0, 1.0)], [(1.0, 2.0), (3.0, 3.0)]]
),
"multipolygon": MultiPolygon([box(0, 0, 1, 1), box(2, 2, 3, 3)]),
"geometrycollection": GeometryCollection([Point(1.0, 2.0), box(0, 0, 1, 1)]),
"emptypoint": wkt.loads("POINT EMPTY"),
"emptypolygon": wkt.loads("POLYGON EMPTY"),
}
TEST_NAMES, TEST_GEOMS = zip(*TEST_DATA.items())
@pytest.mark.parametrize("geom1", TEST_GEOMS, ids=TEST_NAMES)
def test_pickle_round_trip(geom1):
data = dumps(geom1, HIGHEST_PROTOCOL)
with warnings.catch_warnings():
warnings.simplefilter("error")
geom2 = loads(data)
assert geom2.has_z == geom1.has_z
assert type(geom2) is type(geom1)
assert geom2.geom_type == geom1.geom_type
assert geom2.wkt == geom1.wkt
@pytest.mark.parametrize(
"fname", (HERE / "data").glob("*.pickle"), ids=lambda fname: fname.name
)
def test_unpickle_pre_20(fname):
from shapely.testing import assert_geometries_equal
geom_type = fname.name.split("_")[0]
expected = TEST_DATA[geom_type]
with open(fname, "rb") as f:
with pytest.warns(UserWarning):
result = pickle.load(f)
assert_geometries_equal(result, expected)
if __name__ == "__main__":
datadir = HERE / "data"
datadir.mkdir(exist_ok=True)
shapely_version = shapely.__version__
print(shapely_version)
print(shapely.geos.geos_version)
for name, geom in TEST_DATA.items():
if name == "emptypoint" and shapely.geos.geos_version < (3, 9, 0):
# Empty Points cannot be represented in WKB
continue
with open(datadir / f"{name}_{shapely_version}.pickle", "wb") as f:
pickle.dump(geom, f)
@@ -0,0 +1,44 @@
import unittest
from shapely.geometry import LineString, Point, Polygon
from shapely.geometry.base import dump_coords
from shapely.ops import polygonize, polygonize_full
class PolygonizeTestCase(unittest.TestCase):
def test_polygonize(self):
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),
]
result = list(polygonize(lines))
assert all(isinstance(x, Polygon) for x in result)
def test_polygonize_full(self):
lines2 = [
[(0, 0), (1, 1)],
[(0, 0), (0, 1)],
[(0, 1), (1, 1)],
[(1, 1), (1, 0)],
[(1, 0), (0, 0)],
[(5, 5), (6, 6)],
[(1, 1), (100, 100)],
]
result2, cuts, dangles, invalids = polygonize_full(lines2)
assert len(result2.geoms) == 2
assert all(isinstance(x, Polygon) for x in result2.geoms)
assert list(cuts.geoms) == []
assert all(isinstance(x, LineString) for x in dangles.geoms)
assert dump_coords(dangles) == [
[(1.0, 1.0), (100.0, 100.0)],
[(5.0, 5.0), (6.0, 6.0)],
]
assert list(invalids.geoms) == []
@@ -0,0 +1,97 @@
import unittest
import pytest
from shapely.algorithms.polylabel import Cell, polylabel
from shapely.errors import TopologicalError
from shapely.geometry import LineString, Point, Polygon
class PolylabelTestCase(unittest.TestCase):
def test_polylabel(self):
"""
Finds pole of inaccessibility for a polygon with a tolerance of 10
"""
polygon = LineString(
[(0, 0), (50, 200), (100, 100), (20, 50), (-100, -20), (-150, -200)]
).buffer(100)
label = polylabel(polygon, tolerance=10)
expected = Point(59.35615556364569, 121.8391962974644)
assert expected.equals_exact(label, 1e-6)
def test_invalid_polygon(self):
"""
Makes sure that the polylabel function throws an exception when provided
an invalid polygon.
"""
bowtie_polygon = Polygon(
[(0, 0), (0, 20), (10, 10), (20, 20), (20, 0), (10, 10), (0, 0)]
)
with pytest.raises(TopologicalError):
polylabel(bowtie_polygon)
def test_cell_sorting(self):
"""
Tests rich comparison operators of Cells for use in the polylabel
minimum priority queue.
"""
polygon = Point(0, 0).buffer(100)
cell1 = Cell(0, 0, 50, polygon) # closest
cell2 = Cell(50, 50, 50, polygon) # furthest
assert cell1 < cell2
assert cell1 <= cell2
assert (cell2 <= cell1) is False
assert cell1 == cell1
assert (cell1 == cell2) is False
assert cell1 != cell2
assert (cell1 != cell1) is False
assert cell2 > cell1
assert (cell1 > cell2) is False
assert cell2 >= cell1
assert (cell1 >= cell2) is False
def test_concave_polygon(self):
"""
Finds pole of inaccessibility for a concave polygon and ensures that
the point is inside.
"""
concave_polygon = LineString([(500, 0), (0, 0), (0, 500), (500, 500)]).buffer(
100
)
label = polylabel(concave_polygon)
assert concave_polygon.contains(label)
def test_rectangle_special_case(self):
"""
The centroid algorithm used is vulnerable to floating point errors
and can give unexpected results for rectangular polygons. Test
that this special case is handled correctly.
https://github.com/mapbox/polylabel/issues/3
"""
polygon = Polygon(
[
(32.71997, -117.19310),
(32.71997, -117.21065),
(32.72408, -117.21065),
(32.72408, -117.19310),
]
)
label = polylabel(polygon)
assert label.coords[:] == [(32.722025, -117.201875)]
def test_polygon_with_hole(self):
"""
Finds pole of inaccessibility for a polygon with a hole
https://github.com/shapely/shapely/issues/817
"""
polygon = Polygon(
shell=[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
holes=[[(2, 2), (6, 2), (6, 6), (2, 6), (2, 2)]],
)
label = polylabel(polygon, 0.05)
assert label.x == pytest.approx(7.65625)
assert label.y == pytest.approx(7.65625)
@@ -0,0 +1,99 @@
"""Test GEOS predicates
"""
import unittest
import pytest
import shapely
from shapely import geos_version
from shapely.geometry import Point, Polygon
class PredicatesTestCase(unittest.TestCase):
def test_binary_predicates(self):
point = Point(0.0, 0.0)
point2 = Point(2.0, 2.0)
assert point.disjoint(Point(-1.0, -1.0))
assert not point.touches(Point(-1.0, -1.0))
assert not point.crosses(Point(-1.0, -1.0))
assert not point.within(Point(-1.0, -1.0))
assert not point.contains(Point(-1.0, -1.0))
assert not point.equals(Point(-1.0, -1.0))
assert not point.touches(Point(-1.0, -1.0))
assert point.equals(Point(0.0, 0.0))
assert point.covers(Point(0.0, 0.0))
assert point.covered_by(Point(0.0, 0.0))
assert not point.covered_by(point2)
assert not point2.covered_by(point)
assert not point.covers(Point(-1.0, -1.0))
def test_unary_predicates(self):
point = Point(0.0, 0.0)
assert not point.is_empty
assert point.is_valid
assert point.is_simple
assert not point.is_ring
assert not point.has_z
def test_binary_predicate_exceptions(self):
p1 = [
(339, 346),
(459, 346),
(399, 311),
(340, 277),
(399, 173),
(280, 242),
(339, 415),
(280, 381),
(460, 207),
(339, 346),
]
p2 = [
(339, 207),
(280, 311),
(460, 138),
(399, 242),
(459, 277),
(459, 415),
(399, 381),
(519, 311),
(520, 242),
(519, 173),
(399, 450),
(339, 207),
]
g1 = Polygon(p1)
g2 = Polygon(p2)
assert not g1.is_valid
assert not g2.is_valid
if geos_version < (3, 13, 0):
with pytest.raises(shapely.GEOSException):
g1.within(g2)
else: # resolved with RelateNG
assert not g1.within(g2)
def test_relate_pattern(self):
# a pair of partially overlapping polygons, and a nearby point
g1 = Polygon([(0, 0), (0, 1), (3, 1), (3, 0), (0, 0)])
g2 = Polygon([(1, -1), (1, 2), (2, 2), (2, -1), (1, -1)])
g3 = Point(5, 5)
assert g1.relate(g2) == "212101212"
assert g1.relate_pattern(g2, "212101212")
assert g1.relate_pattern(g2, "*********")
assert g1.relate_pattern(g2, "2********")
assert g1.relate_pattern(g2, "T********")
assert not g1.relate_pattern(g2, "112101212")
assert not g1.relate_pattern(g2, "1********")
assert g1.relate_pattern(g3, "FF2FF10F2")
# an invalid pattern should raise an exception
with pytest.raises(shapely.GEOSException, match="IllegalArgumentException"):
g1.relate_pattern(g2, "fail")
@@ -0,0 +1,62 @@
import numpy as np
import pytest
from shapely.geometry import Point, Polygon
from shapely.prepared import prep, PreparedGeometry
def test_prepared_geometry():
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
p = PreparedGeometry(polygon)
assert p.contains(Point(0.5, 0.5))
assert not p.contains(Point(0.5, 1.5))
def test_prep():
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
p = prep(polygon)
assert p.contains(Point(0.5, 0.5))
assert not p.contains(Point(0.5, 1.5))
def test_op_not_allowed():
p = PreparedGeometry(Point(0.0, 0.0).buffer(1.0))
with pytest.raises(TypeError):
Point(0.0, 0.0).union(p)
def test_predicate_not_allowed():
p = PreparedGeometry(Point(0.0, 0.0).buffer(1.0))
with pytest.raises(TypeError):
Point(0.0, 0.0).contains(p)
def test_prepared_predicates():
# check prepared predicates give the same result as regular predicates
polygon1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
polygon2 = Polygon([(0.5, 0.5), (1.5, 0.5), (1.0, 1.0), (0.5, 0.5)])
point2 = Point(0.5, 0.5)
polygon_empty = Polygon()
prepared_polygon1 = PreparedGeometry(polygon1)
for geom2 in (polygon2, point2, polygon_empty):
with np.errstate(invalid="ignore"):
assert polygon1.disjoint(geom2) == prepared_polygon1.disjoint(geom2)
assert polygon1.touches(geom2) == prepared_polygon1.touches(geom2)
assert polygon1.intersects(geom2) == prepared_polygon1.intersects(geom2)
assert polygon1.crosses(geom2) == prepared_polygon1.crosses(geom2)
assert polygon1.within(geom2) == prepared_polygon1.within(geom2)
assert polygon1.contains(geom2) == prepared_polygon1.contains(geom2)
assert polygon1.overlaps(geom2) == prepared_polygon1.overlaps(geom2)
def test_prepare_already_prepared():
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
prepared = prep(polygon)
# attempt to prepare an already prepared geometry with `prep`
result = prep(prepared)
assert isinstance(result, PreparedGeometry)
assert result.context is polygon
# attempt to prepare an already prepared geometry with `PreparedGeometry`
result = PreparedGeometry(prepared)
assert isinstance(result, PreparedGeometry)
assert result.context is polygon
@@ -0,0 +1,13 @@
import unittest
from shapely.geometry import LineString
class ProductZTestCase(unittest.TestCase):
def test_line_intersection(self):
line1 = LineString([(0, 0, 0), (1, 1, 1)])
line2 = LineString([(0, 1, 1), (1, 0, 0)])
interxn = line1.intersection(line2)
assert interxn.has_z
assert interxn._ndim == 3
assert 0.0 <= interxn.z <= 1.0
@@ -0,0 +1,48 @@
import pytest
from shapely.geometry import MultiLineString, Polygon, shape
from shapely.geometry.geo import _is_coordinates_empty
@pytest.mark.parametrize(
"geom",
[{"type": "Polygon", "coordinates": None}, {"type": "Polygon", "coordinates": []}],
)
def test_polygon_no_coords(geom):
assert shape(geom) == Polygon()
def test_polygon_empty_np_array():
np = pytest.importorskip("numpy")
geom = {"type": "Polygon", "coordinates": np.array([])}
assert shape(geom) == Polygon()
def test_polygon_with_coords_list():
geom = {"type": "Polygon", "coordinates": [[[5, 10], [10, 10], [10, 5]]]}
obj = shape(geom)
assert obj == Polygon([(5, 10), (10, 10), (10, 5)])
def test_polygon_not_empty_np_array():
np = pytest.importorskip("numpy")
geom = {"type": "Polygon", "coordinates": np.array([[[5, 10], [10, 10], [10, 5]]])}
obj = shape(geom)
assert obj == Polygon([(5, 10), (10, 10), (10, 5)])
@pytest.mark.parametrize(
"geom",
[
{"type": "MultiLineString", "coordinates": []},
{"type": "MultiLineString", "coordinates": [[]]},
{"type": "MultiLineString", "coordinates": None},
],
)
def test_multilinestring_empty(geom):
assert shape(geom) == MultiLineString()
@pytest.mark.parametrize("coords", [[], [[]], [[], []], None, [[[]]]])
def test_is_coordinates_empty(coords):
assert _is_coordinates_empty(coords)
@@ -0,0 +1,45 @@
import unittest
import pytest
from shapely.errors import GeometryTypeError
from shapely.geometry import GeometryCollection, LineString, MultiLineString, Point
from shapely.ops import shared_paths
class SharedPaths(unittest.TestCase):
def test_shared_paths_forward(self):
g1 = LineString([(0, 0), (10, 0), (10, 5), (20, 5)])
g2 = LineString([(5, 0), (15, 0)])
result = shared_paths(g1, g2)
assert isinstance(result, GeometryCollection)
assert len(result.geoms) == 2
a, b = result.geoms
assert isinstance(a, MultiLineString)
assert len(a.geoms) == 1
assert a.geoms[0].coords[:] == [(5, 0), (10, 0)]
assert b.is_empty
def test_shared_paths_forward2(self):
g1 = LineString([(0, 0), (10, 0), (10, 5), (20, 5)])
g2 = LineString([(15, 0), (5, 0)])
result = shared_paths(g1, g2)
assert isinstance(result, GeometryCollection)
assert len(result.geoms) == 2
a, b = result.geoms
assert isinstance(b, MultiLineString)
assert len(b.geoms) == 1
assert b.geoms[0].coords[:] == [(5, 0), (10, 0)]
assert a.is_empty
def test_wrong_type(self):
g1 = Point(0, 0)
g2 = LineString([(5, 0), (15, 0)])
with pytest.raises(GeometryTypeError):
shared_paths(g1, g2)
with pytest.raises(GeometryTypeError):
shared_paths(g2, g1)
@@ -0,0 +1,15 @@
import unittest
from shapely.geometry import Polygon
class PolygonTestCase(unittest.TestCase):
def test_polygon_3(self):
p = (1.0, 1.0)
poly = Polygon([p, p, p])
assert poly.bounds == (1.0, 1.0, 1.0, 1.0)
def test_polygon_5(self):
p = (1.0, 1.0)
poly = Polygon([p, p, p, p, p])
assert poly.bounds == (1.0, 1.0, 1.0, 1.0)
@@ -0,0 +1,25 @@
import unittest
from shapely.geometry import LineString, Polygon
from shapely.ops import snap
class Snap(unittest.TestCase):
def test_snap(self):
# input geometries
square = Polygon([(1, 1), (2, 1), (2, 2), (1, 2), (1, 1)])
line = LineString([(0, 0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)])
square_coords = square.exterior.coords[:]
line_coords = line.coords[:]
result = snap(line, square, 0.5)
# test result is correct
assert isinstance(result, LineString)
assert result.coords[:] == [(0.0, 0.0), (1.0, 1.0), (2.0, 1.0), (2.6, 0.5)]
# test inputs have not been modified
assert square.exterior.coords[:] == square_coords
assert line.coords[:] == line_coords
@@ -0,0 +1,249 @@
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 segments
splitter = LineString([(1, 3), (1, -3)])
self.helper(self.poly_simple, splitter, 2)
self.helper(self.poly_hole, splitter, 2)
# 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_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)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,213 @@
# Tests SVG output and validity
import os
import unittest
from xml.dom.minidom import parseString as parse_xml_string
from shapely.geometry import (
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
)
from shapely.geometry.collection import GeometryCollection
class SvgTestCase(unittest.TestCase):
def assertSVG(self, geom, expected, **kwrds):
"""Helper function to check XML and debug SVG"""
svg_elem = geom.svg(**kwrds)
try:
parse_xml_string(svg_elem)
except Exception:
raise AssertionError("XML is not valid for SVG element: " + str(svg_elem))
svg_doc = geom._repr_svg_()
try:
doc = parse_xml_string(svg_doc)
except Exception:
raise AssertionError("XML is not valid for SVG document: " + str(svg_doc))
svg_output_dir = None
# svg_output_dir = '.' # useful for debugging SVG files
if svg_output_dir:
fname = geom.geom_type
if geom.is_empty:
fname += "_empty"
if not geom.is_valid:
fname += "_invalid"
if kwrds:
fname += "_" + ",".join(str(k) + "=" + str(kwrds[k]) for k in kwrds)
svg_path = os.path.join(svg_output_dir, fname + ".svg")
with open(svg_path, "w") as fp:
fp.write(doc.toprettyxml())
assert svg_elem == expected
def test_point(self):
# Empty
self.assertSVG(Point(), "<g />")
# Valid
g = Point(6, 7)
self.assertSVG(
g,
'<circle cx="6.0" cy="7.0" r="3.0" stroke="#555555" '
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />',
)
self.assertSVG(
g,
'<circle cx="6.0" cy="7.0" r="15.0" stroke="#555555" '
'stroke-width="5.0" fill="#66cc99" opacity="0.6" />',
scale_factor=5,
)
def test_multipoint(self):
# Empty
self.assertSVG(MultiPoint(), "<g />")
# Valid
g = MultiPoint([(6, 7), (3, 4)])
self.assertSVG(
g,
'<g><circle cx="6.0" cy="7.0" r="3.0" stroke="#555555" '
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />'
'<circle cx="3.0" cy="4.0" r="3.0" stroke="#555555" '
'stroke-width="1.0" fill="#66cc99" opacity="0.6" /></g>',
)
self.assertSVG(
g,
'<g><circle cx="6.0" cy="7.0" r="15.0" stroke="#555555" '
'stroke-width="5.0" fill="#66cc99" opacity="0.6" />'
'<circle cx="3.0" cy="4.0" r="15.0" stroke="#555555" '
'stroke-width="5.0" fill="#66cc99" opacity="0.6" /></g>',
scale_factor=5,
)
def test_linestring(self):
# Empty
self.assertSVG(LineString(), "<g />")
# Valid
g = LineString([(5, 8), (496, -6), (530, 20)])
self.assertSVG(
g,
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
'points="5.0,8.0 496.0,-6.0 530.0,20.0" opacity="0.8" />',
)
self.assertSVG(
g,
'<polyline fill="none" stroke="#66cc99" stroke-width="10.0" '
'points="5.0,8.0 496.0,-6.0 530.0,20.0" opacity="0.8" />',
scale_factor=5,
)
# Invalid
self.assertSVG(
LineString([(0, 0), (0, 0)]),
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
'points="0.0,0.0 0.0,0.0" opacity="0.8" />',
)
def test_multilinestring(self):
# Empty
self.assertSVG(MultiLineString(), "<g />")
# Valid
self.assertSVG(
MultiLineString([[(6, 7), (3, 4)], [(2, 8), (9, 1)]]),
'<g><polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
'points="6.0,7.0 3.0,4.0" opacity="0.8" />'
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
'points="2.0,8.0 9.0,1.0" opacity="0.8" /></g>',
)
# Invalid
self.assertSVG(
MultiLineString([[(2, 3), (2, 3)], [(2, 8), (9, 1)]]),
'<g><polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
'points="2.0,3.0 2.0,3.0" opacity="0.8" />'
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
'points="2.0,8.0 9.0,1.0" opacity="0.8" /></g>',
)
def test_polygon(self):
# Empty
self.assertSVG(Polygon(), "<g />")
# Valid
g = Polygon(
[(35, 10), (45, 45), (15, 40), (10, 20), (35, 10)],
[[(20, 30), (35, 35), (30, 20), (20, 30)]],
)
self.assertSVG(
g,
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 35.0,10.0 L 45.0,45.0 L '
"15.0,40.0 L 10.0,20.0 L 35.0,10.0 z M 20.0,30.0 L 35.0,35.0 L "
'30.0,20.0 L 20.0,30.0 z" />',
)
self.assertSVG(
g,
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
'stroke-width="10.0" opacity="0.6" d="M 35.0,10.0 L 45.0,45.0 L '
"15.0,40.0 L 10.0,20.0 L 35.0,10.0 z M 20.0,30.0 L 35.0,35.0 L "
'30.0,20.0 L 20.0,30.0 z" />',
scale_factor=5,
)
# Invalid
self.assertSVG(
Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]),
'<path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 0.0,40.0 L 0.0,0.0 L '
'40.0,40.0 L 40.0,0.0 L 0.0,40.0 z" />',
)
def test_multipolygon(self):
# Empty
self.assertSVG(MultiPolygon(), "<g />")
# Valid
self.assertSVG(
MultiPolygon(
[
Polygon([(40, 40), (20, 45), (45, 30), (40, 40)]),
Polygon(
[(20, 35), (10, 30), (10, 10), (30, 5), (45, 20), (20, 35)],
[[(30, 20), (20, 15), (20, 25), (30, 20)]],
),
]
),
'<g><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 40.0,40.0 L 20.0,45.0 L '
'45.0,30.0 L 40.0,40.0 z" />'
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 20.0,35.0 L 10.0,30.0 L '
"10.0,10.0 L 30.0,5.0 L 45.0,20.0 L 20.0,35.0 z M 30.0,20.0 L "
'20.0,15.0 L 20.0,25.0 L 30.0,20.0 z" /></g>',
)
# Invalid
self.assertSVG(
MultiPolygon(
[
Polygon([(140, 140), (120, 145), (145, 130), (140, 140)]),
Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]),
]
),
'<g><path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 140.0,140.0 L '
'120.0,145.0 L 145.0,130.0 L 140.0,140.0 z" />'
'<path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
'stroke-width="2.0" opacity="0.6" d="M 0.0,40.0 L 0.0,0.0 L '
'40.0,40.0 L 40.0,0.0 L 0.0,40.0 z" /></g>',
)
def test_collection(self):
# Empty
self.assertSVG(GeometryCollection(), "<g />")
# Valid
self.assertSVG(
GeometryCollection([Point(7, 3), LineString([(4, 2), (8, 4)])]),
'<g><circle cx="7.0" cy="3.0" r="3.0" stroke="#555555" '
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />'
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
'points="4.0,2.0 8.0,4.0" opacity="0.8" /></g>',
)
# Invalid
self.assertSVG(
Point(7, 3).union(LineString([(4, 2), (4, 2)])),
'<g><circle cx="7.0" cy="3.0" r="3.0" stroke="#555555" '
'stroke-width="1.0" fill="#ff3333" opacity="0.6" />'
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
'points="4.0,2.0 4.0,2.0" opacity="0.8" /></g>',
)
@@ -0,0 +1,80 @@
import unittest
import pytest
from shapely import geometry
from shapely.ops import transform
class IdentityTestCase(unittest.TestCase):
"""New geometry/coordseq method 'xy' makes numpy interop easier"""
def func(self, x, y, z=None):
return tuple(c for c in [x, y, z] if c)
def test_empty(self):
g = geometry.Point()
h = transform(self.func, g)
assert h.is_empty
def test_point(self):
g = geometry.Point(0, 1)
h = transform(self.func, g)
assert h.geom_type == "Point"
assert list(h.coords) == [(0, 1)]
def test_line(self):
g = geometry.LineString([(0, 1), (2, 3)])
h = transform(self.func, g)
assert h.geom_type == "LineString"
assert list(h.coords) == [(0, 1), (2, 3)]
def test_linearring(self):
g = geometry.LinearRing([(0, 1), (2, 3), (2, 2), (0, 1)])
h = transform(self.func, g)
assert h.geom_type == "LinearRing"
assert list(h.coords) == [(0, 1), (2, 3), (2, 2), (0, 1)]
def test_polygon(self):
g = geometry.Point(0, 1).buffer(1.0)
h = transform(self.func, g)
assert h.geom_type == "Polygon"
assert g.area == pytest.approx(h.area)
def test_multipolygon(self):
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
h = transform(self.func, g)
assert h.geom_type == "MultiPolygon"
assert g.area == pytest.approx(h.area)
class LambdaTestCase(unittest.TestCase):
"""New geometry/coordseq method 'xy' makes numpy interop easier"""
def test_point(self):
g = geometry.Point(0, 1)
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "Point"
assert list(h.coords) == [(1.0, 2.0)]
def test_line(self):
g = geometry.LineString([(0, 1), (2, 3)])
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "LineString"
assert list(h.coords) == [(1.0, 2.0), (3.0, 4.0)]
def test_polygon(self):
g = geometry.Point(0, 1).buffer(1.0)
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "Polygon"
assert g.area == pytest.approx(h.area)
assert h.centroid.x == pytest.approx(1.0)
assert h.centroid.y == pytest.approx(2.0)
def test_multipolygon(self):
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
assert h.geom_type == "MultiPolygon"
assert g.area == pytest.approx(h.area)
assert h.centroid.x == pytest.approx(1.0)
assert h.centroid.y == pytest.approx(3.5)
@@ -0,0 +1,70 @@
import random
import unittest
from functools import partial
from itertools import islice
import pytest
from shapely.errors import ShapelyDeprecationWarning
from shapely.geometry import MultiPolygon, Point
from shapely.ops import cascaded_union, unary_union
def halton(base):
"""Returns an iterator over an infinite Halton sequence"""
def value(index):
result = 0.0
f = 1.0 / base
i = index
while i > 0:
result += f * (i % base)
i = i // base
f = f / base
return result
i = 1
while i > 0:
yield value(i)
i += 1
class UnionTestCase(unittest.TestCase):
def test_cascaded_union(self):
# cascaded_union is deprecated, as it was superseded by unary_union
# Use a partial function to make 100 points uniformly distributed
# in a 40x40 box centered on 0,0.
r = partial(random.uniform, -20.0, 20.0)
points = [Point(r(), r()) for i in range(100)]
# Buffer the points, producing 100 polygon spots
spots = [p.buffer(2.5) for p in points]
# Perform a cascaded union of the polygon spots, dissolving them
# into a collection of polygon patches
with pytest.warns(ShapelyDeprecationWarning, match="is deprecated"):
u = cascaded_union(spots)
assert u.geom_type in ("Polygon", "MultiPolygon")
def setUp(self):
# Instead of random points, use deterministic, pseudo-random Halton
# sequences for repeatability sake.
self.coords = zip(
list(islice(halton(5), 20, 120)),
list(islice(halton(7), 20, 120)),
)
def test_unary_union(self):
patches = [Point(xy).buffer(0.05) for xy in self.coords]
u = unary_union(patches)
assert u.geom_type == "MultiPolygon"
assert u.area == pytest.approx(0.718572540569)
def test_unary_union_multi(self):
# Test of multipart input based on comment by @schwehr at
# https://github.com/shapely/shapely/issues/47#issuecomment-21809308
patches = MultiPolygon([Point(xy).buffer(0.05) for xy in self.coords])
assert unary_union(patches).area == pytest.approx(0.71857254056)
assert unary_union([patches, patches]).area == pytest.approx(0.71857254056)
@@ -0,0 +1,9 @@
import unittest
from shapely.geometry import Point
from shapely.validation import explain_validity
class ValidationTestCase(unittest.TestCase):
def test_valid(self):
assert explain_validity(Point(0, 0)) == "Valid Geometry"
@@ -0,0 +1,105 @@
import unittest
import numpy as np
from shapely.geometry import box, MultiPolygon, Point
class VectorizedContainsTestCase(unittest.TestCase):
def assertContainsResults(self, geom, x, y):
from shapely.vectorized import contains
result = contains(geom, x, y)
x = np.asanyarray(x)
y = np.asanyarray(y)
self.assertIsInstance(result, np.ndarray)
self.assertEqual(result.dtype, bool)
result_flat = result.flat
x_flat, y_flat = x.flat, y.flat
# Do the equivalent operation, only slowly, comparing the result
# as we go.
for idx in range(x.size):
assert result_flat[idx] == geom.contains(Point(x_flat[idx], y_flat[idx]))
return result
def construct_torus(self):
point = Point(0, 0)
return point.buffer(5).symmetric_difference(point.buffer(2.5))
def test_contains_poly(self):
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
self.assertContainsResults(self.construct_torus(), x, y)
def test_contains_point(self):
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
self.assertContainsResults(Point(x[0], y[0]), x, y)
def test_contains_linestring(self):
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
self.assertContainsResults(Point(x[0], y[0]), x, y)
def test_contains_multipoly(self):
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
# Construct a geometry of the torus cut in half vertically.
cut_poly = box(-1, -10, -2.5, 10)
geom = self.construct_torus().difference(cut_poly)
assert isinstance(geom, MultiPolygon)
self.assertContainsResults(geom, x, y)
def test_y_array_order(self):
y, x = np.mgrid[-10:10:5j, -5:15:5j]
y = y.copy("f")
self.assertContainsResults(self.construct_torus(), x, y)
def test_x_array_order(self):
y, x = np.mgrid[-10:10:5j, -5:15:5j]
x = x.copy("f")
self.assertContainsResults(self.construct_torus(), x, y)
def test_xy_array_order(self):
y, x = np.mgrid[-10:10:5j, -5:15:5j]
x = x.copy("f")
y = y.copy("f")
result = self.assertContainsResults(self.construct_torus(), x, y)
# Preserve the order
assert result.flags["F_CONTIGUOUS"]
def test_array_dtype(self):
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
x = x.astype(np.int16)
self.assertContainsResults(self.construct_torus(), x, y)
def test_array_2d(self):
y, x = np.mgrid[-10:10:15j, -5:15:16j]
result = self.assertContainsResults(self.construct_torus(), x, y)
assert result.shape == x.shape
def test_shapely_xy_attr_contains(self):
g = Point(0, 0).buffer(10.0)
self.assertContainsResults(self.construct_torus(), *g.exterior.xy)
class VectorizedTouchesTestCase(unittest.TestCase):
def test_touches(self):
from shapely.vectorized import touches
y, x = np.mgrid[-2:3:6j, -1:3:5j]
geom = box(0, -1, 2, 2)
result = touches(geom, x, y)
expected = np.array(
[
[False, False, False, False, False],
[False, True, True, True, False],
[False, True, False, True, False],
[False, True, False, True, False],
[False, True, True, True, False],
[False, False, False, False, False],
],
dtype=bool,
)
from numpy.testing import assert_array_equal
assert_array_equal(result, expected)
@@ -0,0 +1,146 @@
"""
Test cases for Voronoi Diagram creation.
Overall, I'm trying less to test the correctness of the result
and more to cover input cases and behavior, making sure
that we return a sane result without error or raise a useful one.
"""
import numpy as np
import pytest
from shapely.geometry import MultiPoint
from shapely.geos import geos_version
from shapely.ops import voronoi_diagram
from shapely.wkt import loads as load_wkt
requires_geos_35 = pytest.mark.skipif(
geos_version < (3, 5, 0), reason="GEOS >= 3.5.0 is required."
)
@requires_geos_35
def test_no_regions():
mp = MultiPoint(points=[(0.5, 0.5)])
with np.errstate(invalid="ignore"):
regions = voronoi_diagram(mp)
assert len(regions.geoms) == 0
@requires_geos_35
def test_two_regions():
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
regions = voronoi_diagram(mp)
assert len(regions.geoms) == 2
@requires_geos_35
def test_edges():
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
regions = voronoi_diagram(mp, edges=True)
assert len(regions.geoms) == 1
# can be LineString or MultiLineString depending on the GEOS version
assert all(r.geom_type.endswith("LineString") for r in regions.geoms)
@requires_geos_35
def test_smaller_envelope():
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
regions = voronoi_diagram(mp, envelope=poly)
assert len(regions.geoms) == 2
assert sum(r.area for r in regions.geoms) > poly.area
@requires_geos_35
def test_larger_envelope():
"""When the envelope we specify is larger than the
area of the input feature, the created regions should
expand to fill that area."""
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
poly = load_wkt("POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))")
regions = voronoi_diagram(mp, envelope=poly)
assert len(regions.geoms) == 2
assert sum(r.area for r in regions.geoms) == poly.area
@requires_geos_35
def test_from_polygon():
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
regions = voronoi_diagram(poly)
assert len(regions.geoms) == 4
@requires_geos_35
def test_from_polygon_with_enough_tolerance():
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
regions = voronoi_diagram(poly, tolerance=1.0)
assert len(regions.geoms) == 2
@requires_geos_35
def test_from_polygon_without_enough_tolerance():
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
with pytest.raises(ValueError) as exc:
voronoi_diagram(poly, tolerance=0.6)
assert "Could not create Voronoi Diagram with the specified inputs" in str(
exc.value
)
assert "Try running again with default tolerance value." in str(exc.value)
@requires_geos_35
def test_from_polygon_without_floating_point_coordinates():
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
with pytest.raises(ValueError) as exc:
voronoi_diagram(poly, tolerance=0.1)
assert "Could not create Voronoi Diagram with the specified inputs" in str(
exc.value
)
assert "Try running again with default tolerance value." in str(exc.value)
@requires_geos_35
def test_from_multipoint_without_floating_point_coordinates():
"""A Multipoint with the same "shape" as the above Polygon raises the same error..."""
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 1, 0 1)")
with pytest.raises(ValueError) as exc:
voronoi_diagram(mp, tolerance=0.1)
assert "Could not create Voronoi Diagram with the specified inputs" in str(
exc.value
)
assert "Try running again with default tolerance value." in str(exc.value)
@requires_geos_35
def test_from_multipoint_with_tolerace_without_floating_point_coordinates():
"""This multipoint will not work with a tolerance value."""
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 2, 0 1)")
with pytest.raises(ValueError) as exc:
voronoi_diagram(mp, tolerance=0.1)
assert "Could not create Voronoi Diagram with the specified inputs" in str(
exc.value
)
assert "Try running again with default tolerance value." in str(exc.value)
@requires_geos_35
def test_from_multipoint_without_tolerace_without_floating_point_coordinates():
"""But it's fine without it."""
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 2, 0 1)")
regions = voronoi_diagram(mp)
assert len(regions.geoms) == 4
@@ -0,0 +1,193 @@
import binascii
import math
import struct
import sys
import pytest
from shapely import wkt
from shapely.geometry import Point
from shapely.geos import geos_version
from shapely.tests.legacy.conftest import shapely20_todo
from shapely.wkb import dump, dumps, load, loads
@pytest.fixture(scope="module")
def some_point():
return Point(1.2, 3.4)
def bin2hex(value):
return binascii.b2a_hex(value).upper().decode("utf-8")
def hex2bin(value):
return binascii.a2b_hex(value)
def hostorder(fmt, value):
"""Re-pack a hex WKB value to native endianness if needed
This routine does not understand WKB format, so it must be provided a
struct module format string, without initial indicator character ("@=<>!"),
which will be interpreted as big- or little-endian with standard sizes
depending on the endian flag in the first byte of the value.
"""
if fmt and fmt[0] in "@=<>!":
raise ValueError("Initial indicator character, one of @=<>!, in fmt")
if not fmt or fmt[0] not in "cbB":
raise ValueError("Missing endian flag in fmt")
(hexendian,) = struct.unpack(fmt[0], hex2bin(value[:2]))
hexorder = {0: ">", 1: "<"}[hexendian]
sysorder = {"little": "<", "big": ">"}[sys.byteorder]
if hexorder == sysorder:
return value # Nothing to do
return bin2hex(
struct.pack(
sysorder + fmt,
{">": 0, "<": 1}[sysorder],
*struct.unpack(hexorder + fmt, hex2bin(value))[1:]
)
)
def test_dumps_srid(some_point):
result = dumps(some_point)
assert bin2hex(result) == hostorder(
"BIdd", "0101000000333333333333F33F3333333333330B40"
)
result = dumps(some_point, srid=4326)
assert bin2hex(result) == hostorder(
"BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
)
def test_dumps_endianness(some_point):
result = dumps(some_point)
assert bin2hex(result) == hostorder(
"BIdd", "0101000000333333333333F33F3333333333330B40"
)
result = dumps(some_point, big_endian=False)
assert bin2hex(result) == "0101000000333333333333F33F3333333333330B40"
result = dumps(some_point, big_endian=True)
assert bin2hex(result) == "00000000013FF3333333333333400B333333333333"
def test_dumps_hex(some_point):
result = dumps(some_point, hex=True)
assert result == hostorder("BIdd", "0101000000333333333333F33F3333333333330B40")
def test_loads_srid():
# load a geometry which includes an srid
geom = loads(hex2bin("0101000020E6100000333333333333F33F3333333333330B40"))
assert isinstance(geom, Point)
assert geom.coords[:] == [(1.2, 3.4)]
# by default srid is not exported
result = dumps(geom)
assert bin2hex(result) == hostorder(
"BIdd", "0101000000333333333333F33F3333333333330B40"
)
# include the srid in the output
result = dumps(geom, include_srid=True)
assert bin2hex(result) == hostorder(
"BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
)
# replace geometry srid with another
result = dumps(geom, srid=27700)
assert bin2hex(result) == hostorder(
"BIIdd", "0101000020346C0000333333333333F33F3333333333330B40"
)
def test_loads_hex(some_point):
assert loads(dumps(some_point, hex=True), hex=True) == some_point
def test_dump_load_binary(some_point, tmpdir):
file = tmpdir.join("test.wkb")
with open(file, "wb") as file_pointer:
dump(some_point, file_pointer)
with open(file, "rb") as file_pointer:
restored = load(file_pointer)
assert some_point == restored
def test_dump_load_hex(some_point, tmpdir):
file = tmpdir.join("test.wkb")
with open(file, "w") as file_pointer:
dump(some_point, file_pointer, hex=True)
with open(file, "r") as file_pointer:
restored = load(file_pointer, hex=True)
assert some_point == restored
# pygeos handles both bytes and str
@shapely20_todo
def test_dump_hex_load_binary(some_point, tmpdir):
"""Asserts that reading a binary file as text (hex mode) fails."""
file = tmpdir.join("test.wkb")
with open(file, "w") as file_pointer:
dump(some_point, file_pointer, hex=True)
with pytest.raises(TypeError):
with open(file, "rb") as file_pointer:
load(file_pointer)
def test_dump_binary_load_hex(some_point, tmpdir):
"""Asserts that reading a text file (hex mode) as binary fails."""
file = tmpdir.join("test.wkb")
with open(file, "wb") as file_pointer:
dump(some_point, file_pointer)
# TODO(shapely-2.0) on windows this doesn't seem to error with pygeos,
# but you get back a point with garbage coordinates
if sys.platform == "win32":
with open(file, "r") as file_pointer:
restored = load(file_pointer, hex=True)
assert some_point != restored
return
with pytest.raises((UnicodeEncodeError, UnicodeDecodeError)):
with open(file, "r") as file_pointer:
load(file_pointer, hex=True)
requires_geos_380 = pytest.mark.xfail(
geos_version < (3, 8, 0), reason="GEOS >= 3.8.0 is required", strict=True
)
@requires_geos_380
def test_point_empty():
g = wkt.loads("POINT EMPTY")
result = dumps(g, big_endian=False)
# Use math.isnan for second part of the WKB representation there are
# many byte representations for NaN)
assert result[: -2 * 8] == b"\x01\x01\x00\x00\x00"
coords = struct.unpack("<2d", result[-2 * 8 :])
assert len(coords) == 2
assert all(math.isnan(val) for val in coords)
# Generally GEOS only serializes this correctly starting with GEOS 3.9
# For some reason MacOS has different behaviour (it's actually correct for
# older GEOS versions, but not for GEOS 3.8)
@pytest.mark.xfail(
(
geos_version < (3, 9, 0)
and not (geos_version < (3, 8, 0) and sys.platform == "darwin")
),
reason="GEOS >= 3.9.0 is required",
)
def test_point_z_empty():
g = wkt.loads("POINT Z EMPTY")
assert g.wkb_hex == hostorder(
"BIddd", "0101000080000000000000F87F000000000000F87F000000000000F87F"
)
@@ -0,0 +1,61 @@
from math import pi
import pytest
from shapely.geometry import Point
from shapely.wkt import dump, dumps, load, loads
@pytest.fixture(scope="module")
def some_point():
return Point(pi, -pi)
@pytest.fixture(scope="module")
def empty_geometry():
return Point()
def test_wkt(some_point):
""".wkt and wkt.dumps() both do not trim by default."""
assert some_point.wkt == f"POINT ({pi:.15f} {-pi:.15f})"
def test_wkt_null(empty_geometry):
assert empty_geometry.wkt == "POINT EMPTY"
def test_dump_load(some_point, tmpdir):
file = tmpdir.join("test.wkt")
with open(file, "w") as file_pointer:
dump(some_point, file_pointer)
with open(file, "r") as file_pointer:
restored = load(file_pointer)
assert some_point == restored
def test_dump_load_null_geometry(empty_geometry, tmpdir):
file = tmpdir.join("test.wkt")
with open(file, "w") as file_pointer:
dump(empty_geometry, file_pointer)
with open(file, "r") as file_pointer:
restored = load(file_pointer)
# This is does not work with __eq__():
assert empty_geometry.equals(restored)
def test_dumps_loads(some_point):
assert dumps(some_point) == f"POINT ({pi:.16f} {-pi:.16f})"
assert loads(dumps(some_point)) == some_point
def test_dumps_loads_null_geometry(empty_geometry):
assert dumps(empty_geometry) == "POINT EMPTY"
# This is does not work with __eq__():
assert loads(dumps(empty_geometry)).equals(empty_geometry)
def test_dumps_precision(some_point):
assert dumps(some_point, rounding_precision=4) == f"POINT ({pi:.4f} {-pi:.4f})"
@@ -0,0 +1,44 @@
import threading
from binascii import b2a_hex
def main():
num_threads = 10
use_threads = True
if not use_threads:
# Run core code
runShapelyBuilding()
else:
threads = [
threading.Thread(target=runShapelyBuilding, name=str(i), args=(i,))
for i in range(num_threads)
]
for t in threads:
t.start()
for t in threads:
t.join()
def runShapelyBuilding(num):
print("%s: Running shapely tests on wkb" % num)
import shapely.geos
print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle))
import shapely.wkb
import shapely.wkt
p = shapely.wkt.loads("POINT (0 0)")
print("%s WKT: %s" % (num, shapely.wkt.dumps(p)))
wkb = shapely.wkb.dumps(p)
print("%s WKB: %s" % (num, b2a_hex(wkb)))
for i in range(10):
shapely.wkb.loads(wkb)
print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle))
print("Done %s" % num)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,266 @@
import numpy as np
import pytest
from numpy.testing import assert_allclose, assert_equal
import shapely
from shapely import count_coordinates, get_coordinates, set_coordinates, transform
from shapely.tests.common import (
empty,
empty_line_string_z,
empty_point,
empty_point_z,
geometry_collection,
geometry_collection_z,
line_string,
line_string_z,
linear_ring,
multi_line_string,
multi_point,
multi_polygon,
point,
point_z,
polygon,
polygon_with_hole,
polygon_z,
)
nested_2 = shapely.geometrycollections([geometry_collection, point])
nested_3 = shapely.geometrycollections([nested_2, point])
@pytest.mark.parametrize(
"geoms,count",
[
([], 0),
([empty], 0),
([point, empty], 1),
([empty, point, empty], 1),
([point, None], 1),
([None, point, None], 1),
([point, point], 2),
([point, point_z], 2),
([line_string, linear_ring], 8),
([polygon], 5),
([polygon_with_hole], 10),
([multi_point, multi_line_string], 4),
([multi_polygon], 10),
([geometry_collection], 3),
([nested_2], 4),
([nested_3], 5),
],
)
def test_count_coords(geoms, count):
actual = count_coordinates(np.array(geoms, np.object_))
assert actual == count
# fmt: off
@pytest.mark.parametrize("include_z", [True, False])
@pytest.mark.parametrize(
"geoms,x,y",
[
([], [], []),
([empty], [], []),
([point, empty], [2], [3]),
([empty, point, empty], [2], [3]),
([point, None], [2], [3]),
([None, point, None], [2], [3]),
([point, point], [2, 2], [3, 3]),
([line_string, linear_ring], [0, 1, 1, 0, 1, 1, 0, 0], [0, 0, 1, 0, 0, 1, 1, 0]),
([polygon], [0, 2, 2, 0, 0], [0, 0, 2, 2, 0]),
([polygon_with_hole], [0, 0, 10, 10, 0, 2, 2, 4, 4, 2], [0, 10, 10, 0, 0, 2, 4, 4, 2, 2]),
([multi_point, multi_line_string], [0, 1, 0, 1], [0, 2, 0, 2]),
([multi_polygon], [0, 1, 1, 0, 0, 2.1, 2.2, 2.2, 2.1, 2.1], [0, 0, 1, 1, 0, 2.1, 2.1, 2.2, 2.2, 2.1]),
([geometry_collection], [51, 52, 49], [-1, -1, 2]),
([nested_2], [51, 52, 49, 2], [-1, -1, 2, 3]),
([nested_3], [51, 52, 49, 2, 2], [-1, -1, 2, 3, 3]),
],
) # fmt: on
def test_get_coords(geoms, x, y, include_z):
actual = get_coordinates(np.array(geoms, np.object_), include_z=include_z)
if not include_z:
expected = np.array([x, y], np.float64).T
else:
expected = np.array([x, y, [np.nan] * len(x)], np.float64).T
assert_equal(actual, expected)
# fmt: off
@pytest.mark.parametrize(
"geoms,index",
[
([], []),
([empty], []),
([point, empty], [0]),
([empty, point, empty], [1]),
([point, None], [0]),
([None, point, None], [1]),
([point, point], [0, 1]),
([point, line_string], [0, 1, 1, 1]),
([line_string, point], [0, 0, 0, 1]),
([line_string, linear_ring], [0, 0, 0, 1, 1, 1, 1, 1]),
],
) # fmt: on
def test_get_coords_index(geoms, index):
_, actual = get_coordinates(np.array(geoms, np.object_), return_index=True)
expected = np.array(index, dtype=np.intp)
assert_equal(actual, expected)
@pytest.mark.parametrize("order", ["C", "F"])
def test_get_coords_index_multidim(order):
geometry = np.array([[point, line_string], [empty, empty]], order=order)
expected = [0, 1, 1, 1] # would be [0, 2, 2, 2] with fortran order
_, actual = get_coordinates(geometry, return_index=True)
assert_equal(actual, expected)
# fmt: off
@pytest.mark.parametrize("include_z", [True, False])
@pytest.mark.parametrize(
"geoms,x,y,z",
[
([point, point_z], [2, 2], [3, 3], [np.nan, 4]),
([line_string_z], [0, 1, 1], [0, 0, 1], [4, 4, 4]),
([polygon_z], [0, 2, 2, 0, 0], [0, 0, 2, 2, 0], [4, 4, 4, 4, 4]),
([geometry_collection_z], [2, 0, 1, 1], [3, 0, 0, 1], [4, 4, 4, 4]),
([point, empty_point], [2], [3], [np.nan]),
],
) # fmt: on
def test_get_coords_3d(geoms, x, y, z, include_z):
actual = get_coordinates(np.array(geoms, np.object_), include_z=include_z)
if include_z:
expected = np.array([x, y, z], np.float64).T
else:
expected = np.array([x, y], np.float64).T
assert_equal(actual, expected)
@pytest.mark.parametrize("include_z", [True, False])
@pytest.mark.parametrize(
"geoms,count,has_ring",
[
([], 0, False),
([empty], 0, False),
([empty_point], 0, False),
([point, empty], 1, False),
([empty, point, empty], 1, False),
([point, None], 1, False),
([None, point, None], 1, False),
([point, point], 2, False),
([point, point_z], 2, False),
([line_string, linear_ring], 8, True),
([line_string_z], 3, True),
([polygon], 5, True),
([polygon_z], 5, True),
([polygon_with_hole], 10, True),
([multi_point, multi_line_string], 4, False),
([multi_polygon], 10, True),
([geometry_collection], 3, False),
([geometry_collection_z], 3, False),
([nested_2], 4, False),
([nested_3], 5, False),
],
)
def test_set_coords(geoms, count, has_ring, include_z):
arr_geoms = np.array(geoms, np.object_)
n = 3 if include_z else 2
coords = get_coordinates(arr_geoms, include_z=include_z) + np.random.random((1, n))
new_geoms = set_coordinates(arr_geoms, coords)
assert_equal(coords, get_coordinates(new_geoms, include_z=include_z))
def test_set_coords_nan():
geoms = np.array([point])
coords = np.array([[np.nan, np.inf]])
new_geoms = set_coordinates(geoms, coords)
assert_equal(coords, get_coordinates(new_geoms))
def test_set_coords_breaks_ring():
with pytest.raises(shapely.GEOSException):
set_coordinates(linear_ring, np.random.random((5, 2)))
def test_set_coords_0dim():
# a geometry input returns a geometry
actual = set_coordinates(point, [[1, 1]])
assert isinstance(actual, shapely.Geometry)
# a 0-dim array input returns a 0-dim array
actual = set_coordinates(np.asarray(point), [[1, 1]])
assert isinstance(actual, np.ndarray)
assert actual.ndim == 0
@pytest.mark.parametrize("include_z", [True, False])
def test_set_coords_mixed_dimension(include_z):
geoms = np.array([point, point_z], dtype=object)
coords = get_coordinates(geoms, include_z=include_z)
new_geoms = set_coordinates(geoms, coords * 2)
if include_z:
# preserve original dimensionality
assert not shapely.has_z(new_geoms[0])
assert shapely.has_z(new_geoms[1])
else:
# all 2D
assert not shapely.has_z(new_geoms).any()
@pytest.mark.parametrize("include_z", [True, False])
@pytest.mark.parametrize(
"geoms",
[[], [empty], [None, point, None], [nested_3], [point, point_z], [line_string_z]],
)
def test_transform(geoms, include_z):
geoms = np.array(geoms, np.object_)
coordinates_before = get_coordinates(geoms, include_z=include_z)
new_geoms = transform(geoms, lambda x: x + 1, include_z=include_z)
assert new_geoms is not geoms
coordinates_after = get_coordinates(new_geoms, include_z=include_z)
assert_allclose(coordinates_before + 1, coordinates_after, equal_nan=True)
def test_transform_0dim():
# a geometry input returns a geometry
actual = transform(point, lambda x: x + 1)
assert isinstance(actual, shapely.Geometry)
# a 0-dim array input returns a 0-dim array
actual = transform(np.asarray(point), lambda x: x + 1)
assert isinstance(actual, np.ndarray)
assert actual.ndim == 0
def test_transform_check_shape():
def remove_coord(arr):
return arr[:-1]
with pytest.raises(ValueError):
transform(linear_ring, remove_coord)
def test_transform_correct_coordinate_dimension():
# ensure that new geometry is 2D with include_z=False
geom = line_string_z
assert shapely.get_coordinate_dimension(geom) == 3
new_geom = transform(geom, lambda x: x + 1, include_z=False)
assert shapely.get_coordinate_dimension(new_geom) == 2
@pytest.mark.parametrize("geom", [
pytest.param(empty_point_z, marks=pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="Empty points don't have a dimensionality before GEOS 3.9")),
empty_line_string_z,
])
def test_transform_empty_preserve_z(geom):
assert shapely.get_coordinate_dimension(geom) == 3
new_geom = transform(geom, lambda x: x + 1, include_z=True)
assert shapely.get_coordinate_dimension(new_geom) == 3
@pytest.mark.parametrize("geom", [
pytest.param(empty_point_z, marks=pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="Empty points don't have a dimensionality before GEOS 3.9")),
empty_line_string_z,
])
def test_transform_remove_z(geom):
assert shapely.get_coordinate_dimension(geom) == 3
new_geom = transform(geom, lambda x: x + 1, include_z=False)
assert shapely.get_coordinate_dimension(new_geom) == 2
@@ -0,0 +1,594 @@
import numpy as np
import pytest
import shapely
# Note: Point is not imported because it is overridden for testing
from shapely import (
GeometryCollection,
GeometryType,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Polygon,
)
from shapely.testing import assert_geometries_equal
from shapely.tests.common import (
empty_polygon,
geometry_collection,
line_string,
linear_ring,
multi_line_string,
multi_point,
multi_polygon,
point,
polygon,
)
def box_tpl(x1, y1, x2, y2):
return (x2, y1), (x2, y2), (x1, y2), (x1, y1), (x2, y1)
def test_points_from_coords():
actual = shapely.points([[0, 0], [2, 2]])
assert_geometries_equal(actual, [shapely.Point(0, 0), shapely.Point(2, 2)])
def test_points_from_xy():
actual = shapely.points(2, [0, 1])
assert_geometries_equal(actual, [shapely.Point(2, 0), shapely.Point(2, 1)])
def test_points_from_xyz():
actual = shapely.points(1, 1, [0, 1])
assert_geometries_equal(actual, [shapely.Point(1, 1, 0), shapely.Point(1, 1, 1)])
def test_points_invalid_ndim():
with pytest.raises(ValueError, match="dimension should be 2 or 3, got 4"):
shapely.points([0, 1, 2, 3])
with pytest.raises(ValueError, match="dimension should be 2 or 3, got 1"):
shapely.points([0])
@pytest.mark.skipif(
shapely.geos_version[:2] not in ((3, 10), (3, 11), (3, 12)),
reason="GEOS not in 3.10, 3.11, 3.12",
)
def test_points_nan_all_nan_becomes_empty():
actual = shapely.points(np.nan, np.nan)
assert actual.wkt == "POINT EMPTY"
@pytest.mark.skipif(
shapely.geos_version[:2] not in ((3, 10), (3, 11)),
reason="GEOS not in 3.10, 3.11",
)
def test_points_nan_3D_all_nan_becomes_empty_2D():
actual = shapely.points(np.nan, np.nan, np.nan)
assert actual.wkt == "POINT EMPTY"
@pytest.mark.skipif(shapely.geos_version[:2] != (3, 12), reason="GEOS != 3.12")
def test_points_nan_3D_all_nan_becomes_empty():
actual = shapely.points(np.nan, np.nan, np.nan)
assert actual.wkt == "POINT Z EMPTY"
@pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
@pytest.mark.parametrize(
"coords,expected_wkt",
[
pytest.param(
[np.nan, np.nan],
"POINT (NaN NaN)",
marks=pytest.mark.skipif(
shapely.geos_version < (3, 13, 0), reason="GEOS < 3.13"
),
),
pytest.param(
[np.nan, np.nan, np.nan],
"POINT Z (NaN NaN NaN)",
marks=pytest.mark.skipif(
shapely.geos_version < (3, 13, 0), reason="GEOS < 3.13"
),
),
([1, np.nan], "POINT (1 NaN)"),
([np.nan, 1], "POINT (NaN 1)"),
([np.nan, 1, np.nan], "POINT Z (NaN 1 NaN)"),
([np.nan, np.nan, 1], "POINT Z (NaN NaN 1)"),
],
)
def test_points_handle_nan_allow(coords, expected_wkt):
actual = shapely.points(coords)
assert actual.wkt == expected_wkt
def test_linestrings_from_coords():
actual = shapely.linestrings([[[0, 0], [1, 1]], [[0, 0], [2, 2]]])
assert_geometries_equal(
actual,
[
LineString([(0, 0), (1, 1)]),
LineString([(0, 0), (2, 2)]),
],
)
def test_linestrings_from_xy():
actual = shapely.linestrings([0, 1], [2, 3])
assert_geometries_equal(actual, LineString([(0, 2), (1, 3)]))
def test_linestrings_from_xy_broadcast():
x = [0, 1] # the same X coordinates for both linestrings
y = [2, 3], [4, 5] # each linestring has a different set of Y coordinates
actual = shapely.linestrings(x, y)
assert_geometries_equal(
actual,
[
LineString([(0, 2), (1, 3)]),
LineString([(0, 4), (1, 5)]),
],
)
def test_linestrings_from_xyz():
actual = shapely.linestrings([0, 1], [2, 3], 0)
assert_geometries_equal(actual, LineString([(0, 2, 0), (1, 3, 0)]))
@pytest.mark.parametrize("dim", [2, 3])
def test_linestrings_buffer(dim):
coords = np.random.randn(10, 3, dim)
coords1 = np.asarray(coords, order="C")
result1 = shapely.linestrings(coords1)
coords2 = np.asarray(coords1, order="F")
result2 = shapely.linestrings(coords2)
assert_geometries_equal(result1, result2)
# creating (.., 8, 8*3) strided array so it uses copyFromArrays
coords3 = np.asarray(np.swapaxes(np.swapaxes(coords, 0, 2), 1, 0), order="F")
coords3 = np.swapaxes(np.swapaxes(coords3, 0, 2), 1, 2)
result3 = shapely.linestrings(coords3)
assert_geometries_equal(result1, result3)
def test_linestrings_invalid_shape_scalar():
with pytest.raises(ValueError):
shapely.linestrings((1, 1))
@pytest.mark.parametrize(
"shape",
[
(2, 1, 2), # 2 linestrings of 1 2D point
(1, 1, 2), # 1 linestring of 1 2D point
(1, 2), # 1 linestring of 1 2D point (scalar)
],
)
def test_linestrings_invalid_shape(shape):
with pytest.raises(shapely.GEOSException):
shapely.linestrings(np.ones(shape))
def test_linestrings_invalid_ndim():
msg = r"The ordinate \(last\) dimension should be 2 or 3, got {}"
coords = np.ones((10, 2, 4), order="C")
with pytest.raises(ValueError, match=msg.format(4)):
shapely.linestrings(coords)
coords = np.ones((10, 2, 4), order="F")
with pytest.raises(ValueError, match=msg.format(4)):
shapely.linestrings(coords)
coords = np.swapaxes(np.swapaxes(np.ones((10, 2, 4)), 0, 2), 1, 0)
coords = np.swapaxes(np.swapaxes(np.asarray(coords, order="F"), 0, 2), 1, 2)
with pytest.raises(ValueError, match=msg.format(4)):
shapely.linestrings(coords)
# too few ordinates
coords = np.ones((10, 2, 1))
with pytest.raises(ValueError, match=msg.format(1)):
shapely.linestrings(coords)
def test_linearrings():
actual = shapely.linearrings(box_tpl(0, 0, 1, 1))
assert_geometries_equal(
actual, LinearRing([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)])
)
def test_linearrings_from_xy():
actual = shapely.linearrings([0, 1, 2, 0], [3, 4, 5, 3])
assert_geometries_equal(actual, LinearRing([(0, 3), (1, 4), (2, 5), (0, 3)]))
def test_linearrings_unclosed():
actual = shapely.linearrings(box_tpl(0, 0, 1, 1)[:-1])
assert_geometries_equal(
actual, LinearRing([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)])
)
def test_linearrings_unclosed_all_coords_equal():
actual = shapely.linearrings([(0, 0), (0, 0), (0, 0)])
assert_geometries_equal(actual, LinearRing([(0, 0), (0, 0), (0, 0), (0, 0)]))
def test_linearrings_invalid_shape_scalar():
with pytest.raises(ValueError):
shapely.linearrings((1, 1))
@pytest.mark.parametrize(
"shape",
[
(2, 1, 2), # 2 linearrings of 1 2D point
(1, 1, 2), # 1 linearring of 1 2D point
(1, 2), # 1 linearring of 1 2D point (scalar)
(2, 2, 2), # 2 linearrings of 2 2D points
(1, 2, 2), # 1 linearring of 2 2D points
(2, 2), # 1 linearring of 2 2D points (scalar)
],
)
def test_linearrings_invalid_shape(shape):
coords = np.ones(shape)
with pytest.raises(ValueError):
shapely.linearrings(coords)
# make sure the first coordinate != second coordinate
coords[..., 1] += 1
with pytest.raises(ValueError):
shapely.linearrings(coords)
def test_linearrings_invalid_ndim():
msg = r"The ordinate \(last\) dimension should be 2 or 3, got {}"
coords1 = np.random.randn(10, 3, 4)
with pytest.raises(ValueError, match=msg.format(4)):
shapely.linearrings(coords1)
coords2 = np.hstack((coords1, coords1[:, [0], :]))
with pytest.raises(ValueError, match=msg.format(4)):
shapely.linearrings(coords2)
# too few ordinates
coords3 = np.random.randn(10, 3, 1)
with pytest.raises(ValueError, match=msg.format(1)):
shapely.linestrings(coords3)
def test_linearrings_all_nan():
coords = np.full((4, 2), np.nan)
with pytest.raises(shapely.GEOSException):
shapely.linearrings(coords)
@pytest.mark.parametrize("dim", [2, 3])
@pytest.mark.parametrize("order", ["C", "F"])
def test_linearrings_buffer(dim, order):
coords1 = np.random.randn(10, 4, dim)
coords1 = np.asarray(coords1, order=order)
result1 = shapely.linearrings(coords1)
# with manual closure -> can directly copy from buffer if C order
coords2 = np.hstack((coords1, coords1[:, [0], :]))
coords2 = np.asarray(coords2, order=order)
result2 = shapely.linearrings(coords2)
assert_geometries_equal(result1, result2)
# create scalar -> can also directly copy from buffer if F order
coords3 = np.asarray(coords2[0], order=order)
result3 = shapely.linearrings(coords3)
assert_geometries_equal(result3, result1[0])
def test_polygon_from_linearring():
actual = shapely.polygons(shapely.linearrings(box_tpl(0, 0, 1, 1)))
assert_geometries_equal(actual, Polygon([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)]))
def test_polygons_none():
assert_geometries_equal(shapely.polygons(None), empty_polygon)
assert_geometries_equal(shapely.polygons(None, holes=[linear_ring]), empty_polygon)
def test_polygons():
actual = shapely.polygons(box_tpl(0, 0, 1, 1))
assert_geometries_equal(actual, Polygon([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)]))
def test_polygon_no_hole_list_raises():
with pytest.raises(ValueError):
shapely.polygons(box_tpl(0, 0, 10, 10), box_tpl(1, 1, 2, 2))
def test_polygon_no_hole_wrong_type():
with pytest.raises((TypeError, shapely.GEOSException)):
shapely.polygons(point)
def test_polygon_with_hole_wrong_type():
with pytest.raises((TypeError, shapely.GEOSException)):
shapely.polygons(point, [linear_ring])
def test_polygon_wrong_hole_type():
with pytest.raises((TypeError, shapely.GEOSException)):
shapely.polygons(linear_ring, [point])
def test_polygon_with_1_hole():
actual = shapely.polygons(box_tpl(0, 0, 10, 10), [box_tpl(1, 1, 2, 2)])
assert shapely.area(actual) == 99.0
def test_polygon_with_2_holes():
actual = shapely.polygons(
box_tpl(0, 0, 10, 10), [box_tpl(1, 1, 2, 2), box_tpl(3, 3, 4, 4)]
)
assert shapely.area(actual) == 98.0
def test_polygon_with_none_hole():
actual = shapely.polygons(
shapely.linearrings(box_tpl(0, 0, 10, 10)),
[
shapely.linearrings(box_tpl(1, 1, 2, 2)),
None,
shapely.linearrings(box_tpl(3, 3, 4, 4)),
],
)
assert shapely.area(actual) == 98.0
def test_2_polygons_with_same_hole():
actual = shapely.polygons(
[box_tpl(0, 0, 10, 10), box_tpl(0, 0, 5, 5)], [box_tpl(1, 1, 2, 2)]
)
assert shapely.area(actual).tolist() == [99.0, 24.0]
def test_2_polygons_with_2_same_holes():
actual = shapely.polygons(
[box_tpl(0, 0, 10, 10), box_tpl(0, 0, 5, 5)],
[box_tpl(1, 1, 2, 2), box_tpl(3, 3, 4, 4)],
)
assert shapely.area(actual).tolist() == [98.0, 23.0]
def test_2_polygons_with_different_holes():
actual = shapely.polygons(
[box_tpl(0, 0, 10, 10), box_tpl(0, 0, 5, 5)],
[[box_tpl(1, 1, 3, 3)], [box_tpl(1, 1, 2, 2)]],
)
assert shapely.area(actual).tolist() == [96.0, 24.0]
def test_polygons_not_enough_points_in_shell_scalar():
with pytest.raises(ValueError):
shapely.polygons((1, 1))
@pytest.mark.parametrize(
"shape",
[
(2, 1, 2), # 2 linearrings of 1 2D point
(1, 1, 2), # 1 linearring of 1 2D point
(1, 2), # 1 linearring of 1 2D point (scalar)
(2, 2, 2), # 2 linearrings of 2 2D points
(1, 2, 2), # 1 linearring of 2 2D points
(2, 2), # 1 linearring of 2 2D points (scalar)
],
)
def test_polygons_not_enough_points_in_shell(shape):
coords = np.ones(shape)
with pytest.raises(ValueError):
shapely.polygons(coords)
# make sure the first coordinate != second coordinate
coords[..., 1] += 1
with pytest.raises(ValueError):
shapely.polygons(coords)
def test_polygons_not_enough_points_in_holes_scalar():
with pytest.raises(ValueError):
shapely.polygons(np.ones((1, 4, 2)), (1, 1))
@pytest.mark.parametrize(
"shape",
[
(2, 1, 2), # 2 linearrings of 1 2D point
(1, 1, 2), # 1 linearring of 1 2D point
(1, 2), # 1 linearring of 1 2D point (scalar)
(2, 2, 2), # 2 linearrings of 2 2D points
(1, 2, 2), # 1 linearring of 2 2D points
(2, 2), # 1 linearring of 2 2D points (scalar)
],
)
def test_polygons_not_enough_points_in_holes(shape):
coords = np.ones(shape)
with pytest.raises(ValueError):
shapely.polygons(np.ones((1, 4, 2)), coords)
# make sure the first coordinate != second coordinate
coords[..., 1] += 1
with pytest.raises(ValueError):
shapely.polygons(np.ones((1, 4, 2)), coords)
@pytest.mark.parametrize(
"func,expected",
[
(shapely.multipoints, MultiPoint()),
(shapely.multilinestrings, MultiLineString()),
(shapely.multipolygons, MultiPolygon()),
(shapely.geometrycollections, GeometryCollection()),
],
)
def test_create_collection_only_none(func, expected):
actual = func(np.array([None], dtype=object))
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize(
"func,sub_geom",
[
(shapely.multipoints, point),
(shapely.multilinestrings, line_string),
(shapely.multilinestrings, linear_ring),
(shapely.multipolygons, polygon),
(shapely.geometrycollections, point),
(shapely.geometrycollections, line_string),
(shapely.geometrycollections, linear_ring),
(shapely.geometrycollections, polygon),
(shapely.geometrycollections, multi_point),
(shapely.geometrycollections, multi_line_string),
(shapely.geometrycollections, multi_polygon),
(shapely.geometrycollections, geometry_collection),
],
)
def test_create_collection(func, sub_geom):
actual = func([sub_geom, sub_geom])
assert shapely.get_num_geometries(actual) == 2
@pytest.mark.parametrize(
"func,sub_geom",
[
(shapely.multipoints, point),
(shapely.multilinestrings, line_string),
(shapely.multipolygons, polygon),
(shapely.geometrycollections, polygon),
],
)
def test_create_collection_skips_none(func, sub_geom):
actual = func([sub_geom, None, None, sub_geom])
assert shapely.get_num_geometries(actual) == 2
@pytest.mark.parametrize(
"func,sub_geom",
[
(shapely.multipoints, line_string),
(shapely.multipoints, geometry_collection),
(shapely.multipoints, multi_point),
(shapely.multilinestrings, point),
(shapely.multilinestrings, polygon),
(shapely.multilinestrings, multi_line_string),
(shapely.multipolygons, linear_ring),
(shapely.multipolygons, multi_point),
(shapely.multipolygons, multi_polygon),
],
)
def test_create_collection_wrong_geom_type(func, sub_geom):
with pytest.raises(TypeError):
func([sub_geom])
@pytest.mark.parametrize(
"coords,ccw,expected",
[
((0, 0, 1, 1), True, Polygon([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)])),
(
(0, 0, 1, 1),
False,
Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]),
),
],
)
def test_box(coords, ccw, expected):
actual = shapely.box(*coords, ccw=ccw)
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize(
"coords,ccw,expected",
[
(
(0, 0, [1, 2], [1, 2]),
True,
[
Polygon([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)]),
Polygon([(2, 0), (2, 2), (0, 2), (0, 0), (2, 0)]),
],
),
(
(0, 0, [1, 2], [1, 2]),
[True, False],
[
Polygon([(1, 0), (1, 1), (0, 1), (0, 0), (1, 0)]),
Polygon([(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]),
],
),
],
)
def test_box_array(coords, ccw, expected):
actual = shapely.box(*coords, ccw=ccw)
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize(
"coords",
[
[np.nan, np.nan, np.nan, np.nan],
[np.nan, 0, 1, 1],
[0, np.nan, 1, 1],
[0, 0, np.nan, 1],
[0, 0, 1, np.nan],
],
)
def test_box_nan(coords):
assert shapely.box(*coords) is None
def test_prepare():
arr = np.array([shapely.points(1, 1), None, shapely.box(0, 0, 1, 1)])
assert arr[0]._geom_prepared == 0
assert arr[2]._geom_prepared == 0
shapely.prepare(arr)
assert arr[0]._geom_prepared != 0
assert arr[1] is None
assert arr[2]._geom_prepared != 0
# preparing again actually does nothing
original = arr[0]._geom_prepared
shapely.prepare(arr)
assert arr[0]._geom_prepared == original
def test_destroy_prepared():
arr = np.array([shapely.points(1, 1), None, shapely.box(0, 0, 1, 1)])
shapely.prepare(arr)
assert arr[0]._geom_prepared != 0
assert arr[2]._geom_prepared != 0
shapely.destroy_prepared(arr)
assert arr[0]._geom_prepared == 0
assert arr[1] is None
assert arr[2]._geom_prepared == 0
shapely.destroy_prepared(arr) # does not error
@pytest.mark.parametrize("geom_type", [None, GeometryType.MISSING, -1])
def test_empty_missing(geom_type):
actual = shapely.empty((2,), geom_type=geom_type)
assert shapely.is_missing(actual).all()
@pytest.mark.parametrize("geom_type", range(8))
def test_empty(geom_type):
actual = shapely.empty((2,), geom_type=geom_type)
assert (~shapely.is_missing(actual)).all()
assert shapely.is_empty(actual).all()
assert (shapely.get_type_id(actual) == geom_type).all()
@@ -0,0 +1,418 @@
import numpy as np
import pytest
import shapely
from shapely import LinearRing, Polygon
from shapely.testing import assert_geometries_equal
from shapely.tests.common import empty_point, line_string, linear_ring, point, polygon
pnts = shapely.points
lstrs = shapely.linestrings
geom_coll = shapely.geometrycollections
@pytest.mark.parametrize(
"func", [shapely.points, shapely.linestrings, shapely.linearrings]
)
@pytest.mark.parametrize(
"coordinates",
[
np.empty((2,)), # not enough dimensions
np.empty((2, 4, 1)), # too many dimensions
np.empty((2, 4)), # wrong inner dimension size
None,
np.full((2, 2), "foo", dtype=object), # wrong type
],
)
def test_invalid_coordinates(func, coordinates):
with pytest.raises((TypeError, ValueError)):
func(coordinates, indices=[0, 1])
@pytest.mark.parametrize(
"func",
[
shapely.multipoints,
shapely.multilinestrings,
shapely.multipolygons,
shapely.geometrycollections,
],
)
@pytest.mark.parametrize(
"geometries", [np.array([1, 2], dtype=np.intp), None, np.array([[point]]), "hello"]
)
def test_invalid_geometries(func, geometries):
with pytest.raises((TypeError, ValueError)):
func(geometries, indices=[0, 1])
@pytest.mark.parametrize(
"func", [shapely.points, shapely.linestrings, shapely.linearrings]
)
@pytest.mark.parametrize("indices", [[point], " hello", [0, 1], [-1]])
def test_invalid_indices_simple(func, indices):
with pytest.raises((TypeError, ValueError)):
func([[0.2, 0.3]], indices=indices)
non_writeable = np.empty(3, dtype=object)
non_writeable.flags.writeable = False
@pytest.mark.parametrize(
"func", [shapely.points, shapely.linestrings, shapely.geometrycollections]
)
@pytest.mark.parametrize(
"out",
[
[None, None, None], # not an ndarray
np.empty(3), # wrong dtype
non_writeable, # not writeable
np.empty((3, 2), dtype=object), # too many dimensions
np.empty((), dtype=object), # too few dimensions
np.empty((2,), dtype=object), # too small
],
)
def test_invalid_out(func, out):
if func is shapely.points:
x = [[0.2, 0.3], [0.4, 0.5]]
indices = [0, 2]
elif func is shapely.linestrings:
x = [[1, 1], [2, 1], [2, 2], [3, 3], [3, 4], [4, 4]]
indices = [0, 0, 0, 2, 2, 2]
else:
x = [point, line_string]
indices = [0, 2]
with pytest.raises((TypeError, ValueError)):
func(x, indices=indices, out=out)
def test_points_invalid():
# attempt to construct a point with 2 coordinates
with pytest.raises(shapely.GEOSException):
shapely.points([[1, 1], [2, 2]], indices=[0, 0])
def test_points():
actual = shapely.points(
np.array([[2, 3], [2, 3]], dtype=float),
indices=np.array([0, 1], dtype=np.intp),
)
assert_geometries_equal(actual, [point, point])
def test_points_no_index_raises():
with pytest.raises(ValueError):
shapely.points(
np.array([[2, 3], [2, 3]], dtype=float),
indices=np.array([0, 2], dtype=np.intp),
)
@pytest.mark.parametrize(
"indices,expected",
[
([0, 1], [point, point, empty_point, None]),
([0, 3], [point, None, empty_point, point]),
([2, 3], [None, None, point, point]),
],
)
def test_points_out(indices, expected):
out = np.empty(4, dtype=object)
out[2] = empty_point
actual = shapely.points(
[[2, 3], [2, 3]],
indices=indices,
out=out,
)
assert_geometries_equal(out, expected)
assert actual is out
@pytest.mark.parametrize(
"coordinates,indices,expected",
[
([[1, 1], [2, 2]], [0, 0], [lstrs([[1, 1], [2, 2]])]),
([[1, 1, 1], [2, 2, 2]], [0, 0], [lstrs([[1, 1, 1], [2, 2, 2]])]),
(
[[1, 1], [2, 2], [2, 2], [3, 3]],
[0, 0, 1, 1],
[lstrs([[1, 1], [2, 2]]), lstrs([[2, 2], [3, 3]])],
),
],
)
def test_linestrings(coordinates, indices, expected):
actual = shapely.linestrings(
np.array(coordinates, dtype=float), indices=np.array(indices, dtype=np.intp)
)
assert_geometries_equal(actual, expected)
def test_linestrings_invalid():
# attempt to construct linestrings with 1 coordinate
with pytest.raises(shapely.GEOSException):
shapely.linestrings([[1, 1], [2, 2]], indices=[0, 1])
@pytest.mark.parametrize(
"indices,expected",
[
([0, 0, 0, 1, 1, 1], [line_string, line_string, empty_point, None]),
([0, 0, 0, 3, 3, 3], [line_string, None, empty_point, line_string]),
([2, 2, 2, 3, 3, 3], [None, None, line_string, line_string]),
],
)
def test_linestrings_out(indices, expected):
out = np.empty(4, dtype=object)
out[2] = empty_point
actual = shapely.linestrings(
[(0, 0), (1, 0), (1, 1), (0, 0), (1, 0), (1, 1)],
indices=indices,
out=out,
)
assert_geometries_equal(out, expected)
assert actual is out
@pytest.mark.parametrize(
"coordinates", [([[1, 1], [2, 1], [2, 2], [1, 1]]), ([[1, 1], [2, 1], [2, 2]])]
)
def test_linearrings(coordinates):
actual = shapely.linearrings(
np.array(coordinates, dtype=np.float64),
indices=np.zeros(len(coordinates), dtype=np.intp),
)
assert_geometries_equal(actual, shapely.linearrings(coordinates))
@pytest.mark.parametrize(
"coordinates",
[
([[1, np.nan], [2, 1], [2, 2], [1, 1]]), # starting with nan
],
)
def test_linearrings_invalid(coordinates):
# attempt to construct linestrings with 1 coordinate
with pytest.raises((shapely.GEOSException, ValueError)):
shapely.linearrings(coordinates, indices=np.zeros(len(coordinates)))
def test_linearrings_unclosed_all_coords_equal():
actual = shapely.linearrings([(0, 0), (0, 0), (0, 0)], indices=np.zeros(3))
assert_geometries_equal(actual, LinearRing([(0, 0), (0, 0), (0, 0), (0, 0)]))
@pytest.mark.parametrize(
"indices,expected",
[
([0, 0, 0, 0, 0], [linear_ring, None, None, empty_point]),
([1, 1, 1, 1, 1], [None, linear_ring, None, empty_point]),
([3, 3, 3, 3, 3], [None, None, None, linear_ring]),
],
)
def test_linearrings_out(indices, expected):
out = np.empty(4, dtype=object)
out[3] = empty_point
actual = shapely.linearrings(
[(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)],
indices=indices,
out=out,
)
assert_geometries_equal(out, expected)
assert actual is out
@pytest.mark.parametrize("dim", [2, 3])
@pytest.mark.parametrize("order", ["C", "F"])
def test_linearrings_buffer(dim, order):
coords = np.random.randn(10, 4, dim)
coords1 = np.asarray(coords.reshape(10 * 4, dim), order=order)
indices1 = np.repeat(range(10), 4)
result1 = shapely.linearrings(coords1, indices=indices1)
# with manual closure -> can directly copy from buffer if C order
coords2 = np.hstack((coords, coords[:, [0], :]))
coords2 = np.asarray(coords2.reshape(10 * 5, dim), order=order)
indices2 = np.repeat(range(10), 5)
result2 = shapely.linearrings(coords2, indices=indices2)
assert_geometries_equal(result1, result2)
hole_1 = shapely.linearrings([(0.2, 0.2), (0.2, 0.4), (0.4, 0.4)])
hole_2 = shapely.linearrings([(0.6, 0.6), (0.6, 0.8), (0.8, 0.8)])
poly = shapely.polygons(linear_ring)
poly_empty = Polygon()
poly_hole_1 = shapely.polygons(linear_ring, holes=[hole_1])
poly_hole_2 = shapely.polygons(linear_ring, holes=[hole_2])
poly_hole_1_2 = shapely.polygons(linear_ring, holes=[hole_1, hole_2])
@pytest.mark.parametrize(
"rings,indices,expected",
[
([linear_ring, linear_ring], [0, 1], [poly, poly]),
([None, linear_ring], [0, 1], [poly_empty, poly]),
([None, linear_ring, None, None], [0, 0, 1, 1], [poly, poly_empty]),
([linear_ring, hole_1, linear_ring], [0, 0, 1], [poly_hole_1, poly]),
([linear_ring, linear_ring, hole_1], [0, 1, 1], [poly, poly_hole_1]),
([None, linear_ring, linear_ring, hole_1], [0, 0, 1, 1], [poly, poly_hole_1]),
([linear_ring, None, linear_ring, hole_1], [0, 0, 1, 1], [poly, poly_hole_1]),
([linear_ring, None, linear_ring, hole_1], [0, 1, 1, 1], [poly, poly_hole_1]),
([linear_ring, linear_ring, None, hole_1], [0, 1, 1, 1], [poly, poly_hole_1]),
([linear_ring, linear_ring, hole_1, None], [0, 1, 1, 1], [poly, poly_hole_1]),
(
[linear_ring, hole_1, hole_2, linear_ring],
[0, 0, 0, 1],
[poly_hole_1_2, poly],
),
(
[linear_ring, hole_1, linear_ring, hole_2],
[0, 0, 1, 1],
[poly_hole_1, poly_hole_2],
),
(
[linear_ring, linear_ring, hole_1, hole_2],
[0, 1, 1, 1],
[poly, poly_hole_1_2],
),
(
[linear_ring, hole_1, None, hole_2, linear_ring],
[0, 0, 0, 0, 1],
[poly_hole_1_2, poly],
),
(
[linear_ring, hole_1, None, linear_ring, hole_2],
[0, 0, 0, 1, 1],
[poly_hole_1, poly_hole_2],
),
(
[linear_ring, hole_1, linear_ring, None, hole_2],
[0, 0, 1, 1, 1],
[poly_hole_1, poly_hole_2],
),
],
)
def test_polygons(rings, indices, expected):
actual = shapely.polygons(
np.array(rings, dtype=object), indices=np.array(indices, dtype=np.intp)
)
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize(
"indices,expected",
[
([0, 1], [poly, poly, empty_point, None]),
([0, 3], [poly, None, empty_point, poly]),
([2, 3], [None, None, poly, poly]),
],
)
def test_polygons_out(indices, expected):
out = np.empty(4, dtype=object)
out[2] = empty_point
actual = shapely.polygons([linear_ring, linear_ring], indices=indices, out=out)
assert_geometries_equal(out, expected)
assert actual is out
@pytest.mark.parametrize(
"func",
[
shapely.polygons,
shapely.multipoints,
shapely.multilinestrings,
shapely.multipolygons,
shapely.geometrycollections,
],
)
@pytest.mark.parametrize("indices", [np.array([point]), " hello", [0, 1], [-1]])
def test_invalid_indices_collections(func, indices):
with pytest.raises((TypeError, ValueError)):
func([point], indices=indices)
@pytest.mark.parametrize(
"geometries,indices,expected",
[
([point, line_string], [0, 0], [geom_coll([point, line_string])]),
([point, line_string], [0, 1], [geom_coll([point]), geom_coll([line_string])]),
([point, None], [0, 0], [geom_coll([point])]),
([point, None], [0, 1], [geom_coll([point]), geom_coll([])]),
([None, point, None, None], [0, 0, 1, 1], [geom_coll([point]), geom_coll([])]),
([point, None, line_string], [0, 0, 0], [geom_coll([point, line_string])]),
],
)
def test_geometrycollections(geometries, indices, expected):
actual = shapely.geometrycollections(
np.array(geometries, dtype=object), indices=indices
)
assert_geometries_equal(actual, expected)
def test_geometrycollections_no_index_raises():
with pytest.raises(ValueError):
shapely.geometrycollections(
np.array([point, line_string], dtype=object), indices=[0, 2]
)
@pytest.mark.parametrize(
"indices,expected",
[
([0, 0], [geom_coll([point, line_string]), None, None, empty_point]),
([3, 3], [None, None, None, geom_coll([point, line_string])]),
],
)
def test_geometrycollections_out(indices, expected):
out = np.empty(4, dtype=object)
out[3] = empty_point
actual = shapely.geometrycollections([point, line_string], indices=indices, out=out)
assert_geometries_equal(out, expected)
assert actual is out
def test_multipoints():
actual = shapely.multipoints(
np.array([point], dtype=object), indices=np.zeros(1, dtype=np.intp)
)
assert_geometries_equal(actual, shapely.multipoints([point]))
def test_multilinestrings():
actual = shapely.multilinestrings(
np.array([line_string], dtype=object), indices=np.zeros(1, dtype=np.intp)
)
assert_geometries_equal(actual, shapely.multilinestrings([line_string]))
def test_multilinearrings():
actual = shapely.multilinestrings(
np.array([linear_ring], dtype=object), indices=np.zeros(1, dtype=np.intp)
)
assert_geometries_equal(actual, shapely.multilinestrings([linear_ring]))
def test_multipolygons():
actual = shapely.multipolygons(
np.array([polygon], dtype=object), indices=np.zeros(1, dtype=np.intp)
)
assert_geometries_equal(actual, shapely.multipolygons([polygon]))
@pytest.mark.parametrize(
"geometries,func",
[
([point], shapely.polygons),
([line_string], shapely.polygons),
([polygon], shapely.polygons),
([line_string], shapely.multipoints),
([polygon], shapely.multipoints),
([point], shapely.multilinestrings),
([polygon], shapely.multilinestrings),
([point], shapely.multipolygons),
([line_string], shapely.multipolygons),
],
)
def test_incompatible_types(geometries, func):
with pytest.raises(TypeError):
func(geometries, indices=[0])
@@ -0,0 +1,738 @@
import warnings
import numpy as np
import pytest
import shapely
from shapely import LinearRing, LineString, MultiPolygon, Point, Polygon
from shapely.testing import assert_geometries_equal
from shapely.tests.common import all_types
from shapely.tests.common import empty as empty_geometry_collection
from shapely.tests.common import (
empty_line_string,
empty_line_string_z,
empty_point,
empty_point_z,
empty_polygon,
geometry_collection,
geometry_collection_z,
ignore_invalid,
line_string,
line_string_nan,
line_string_z,
linear_ring,
multi_line_string,
multi_line_string_z,
multi_point,
multi_point_z,
multi_polygon,
multi_polygon_z,
point,
point_z,
polygon,
polygon_with_hole,
polygon_with_hole_z,
polygon_z,
)
def test_get_num_points():
actual = shapely.get_num_points(all_types + (None,)).tolist()
assert actual == [0, 3, 5, 0, 0, 0, 0, 0, 0, 0]
def test_get_num_interior_rings():
actual = shapely.get_num_interior_rings(all_types + (polygon_with_hole, None))
assert actual.tolist() == [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
def test_get_num_geometries():
actual = shapely.get_num_geometries(all_types + (None,)).tolist()
assert actual == [1, 1, 1, 1, 2, 1, 2, 2, 0, 0]
@pytest.mark.parametrize(
"geom",
[
point,
polygon,
multi_point,
multi_line_string,
multi_polygon,
geometry_collection,
],
)
def test_get_point_non_linestring(geom):
actual = shapely.get_point(geom, [0, 2, -1])
assert shapely.is_missing(actual).all()
@pytest.mark.parametrize("geom", [line_string, linear_ring])
def test_get_point(geom):
n = shapely.get_num_points(geom)
actual = shapely.get_point(geom, [0, -n, n, -(n + 1)])
assert_geometries_equal(actual[0], actual[1])
assert shapely.is_missing(actual[2:4]).all()
@pytest.mark.parametrize(
"geom",
[
point,
line_string,
linear_ring,
multi_point,
multi_line_string,
multi_polygon,
geometry_collection,
],
)
def test_get_exterior_ring_non_polygon(geom):
actual = shapely.get_exterior_ring(geom)
assert shapely.is_missing(actual).all()
def test_get_exterior_ring():
actual = shapely.get_exterior_ring([polygon, polygon_with_hole])
assert (shapely.get_type_id(actual) == 2).all()
@pytest.mark.parametrize(
"geom",
[
point,
line_string,
linear_ring,
multi_point,
multi_line_string,
multi_polygon,
geometry_collection,
],
)
def test_get_interior_ring_non_polygon(geom):
actual = shapely.get_interior_ring(geom, [0, 2, -1])
assert shapely.is_missing(actual).all()
def test_get_interior_ring():
actual = shapely.get_interior_ring(polygon_with_hole, [0, -1, 1, -2])
assert_geometries_equal(actual[0], actual[1])
assert shapely.is_missing(actual[2:4]).all()
@pytest.mark.parametrize("geom", [point, line_string, linear_ring, polygon])
def test_get_geometry_simple(geom):
actual = shapely.get_geometry(geom, [0, -1, 1, -2])
assert_geometries_equal(actual[0], actual[1])
assert shapely.is_missing(actual[2:4]).all()
@pytest.mark.parametrize(
"geom", [multi_point, multi_line_string, multi_polygon, geometry_collection]
)
def test_get_geometry_collection(geom):
n = shapely.get_num_geometries(geom)
actual = shapely.get_geometry(geom, [0, -n, n, -(n + 1)])
assert_geometries_equal(actual[0], actual[1])
assert shapely.is_missing(actual[2:4]).all()
def test_get_type_id():
actual = shapely.get_type_id(all_types).tolist()
assert actual == [0, 1, 2, 3, 4, 5, 6, 7, 7]
def test_get_dimensions():
actual = shapely.get_dimensions(all_types).tolist()
assert actual == [0, 1, 1, 2, 0, 1, 2, 1, -1]
def test_get_coordinate_dimension():
actual = shapely.get_coordinate_dimension([point, point_z, None]).tolist()
assert actual == [2, 3, -1]
def test_get_num_coordinates():
actual = shapely.get_num_coordinates(all_types + (None,)).tolist()
assert actual == [1, 3, 5, 5, 2, 2, 10, 3, 0, 0]
def test_get_srid():
"""All geometry types have no SRID by default; None returns -1"""
actual = shapely.get_srid(all_types + (None,)).tolist()
assert actual == [0, 0, 0, 0, 0, 0, 0, 0, 0, -1]
def test_get_set_srid():
actual = shapely.set_srid(point, 4326)
assert shapely.get_srid(point) == 0
assert shapely.get_srid(actual) == 4326
@pytest.mark.parametrize(
"func",
[
shapely.get_x,
shapely.get_y,
pytest.param(
shapely.get_z,
marks=pytest.mark.skipif(
shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7"
),
),
],
)
@pytest.mark.parametrize("geom", all_types[1:])
def test_get_xyz_no_point(func, geom):
assert np.isnan(func(geom))
def test_get_x():
assert shapely.get_x([point, point_z]).tolist() == [2.0, 2.0]
def test_get_y():
assert shapely.get_y([point, point_z]).tolist() == [3.0, 3.0]
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_get_z():
assert shapely.get_z([point_z]).tolist() == [4.0]
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_get_z_2d():
assert np.isnan(shapely.get_z(point))
@pytest.mark.parametrize("geom", all_types)
def test_new_from_wkt(geom):
actual = shapely.from_wkt(str(geom))
assert_geometries_equal(actual, geom)
def test_adapt_ptr_raises():
point = Point(2, 2)
with pytest.raises(AttributeError):
point._geom += 1
@pytest.mark.parametrize(
"geom", all_types + (shapely.points(np.nan, np.nan), empty_point)
)
def test_hash_same_equal(geom):
assert hash(geom) == hash(shapely.transform(geom, lambda x: x))
@pytest.mark.parametrize("geom", all_types[:-1])
def test_hash_same_not_equal(geom):
assert hash(geom) != hash(shapely.transform(geom, lambda x: x + 1))
@pytest.mark.parametrize("geom", all_types)
def test_eq(geom):
assert geom == shapely.transform(geom, lambda x: x)
@pytest.mark.parametrize("geom", all_types[:-1])
def test_neq(geom):
assert geom != shapely.transform(geom, lambda x: x + 1)
@pytest.mark.parametrize("geom", all_types)
def test_set_unique(geom):
a = {geom, shapely.transform(geom, lambda x: x)}
assert len(a) == 1
def test_set_nan():
# As NaN != NaN, you can have multiple "NaN" points in a set
# set([float("nan"), float("nan")]) also returns a set with 2 elements
with ignore_invalid():
a = set(shapely.linestrings([[[np.nan, np.nan], [np.nan, np.nan]]] * 10))
assert len(a) == 10 # different objects: NaN != NaN
def test_set_nan_same_objects():
# You can't put identical objects in a set.
# x = float("nan"); set([x, x]) also returns a set with 1 element
a = set([line_string_nan] * 10)
assert len(a) == 1
@pytest.mark.parametrize(
"geom",
[
point,
multi_point,
line_string,
multi_line_string,
polygon,
multi_polygon,
geometry_collection,
empty_point,
empty_line_string,
empty_polygon,
empty_geometry_collection,
np.array([None]),
np.empty_like(np.array([None])),
],
)
def test_get_parts(geom):
expected_num_parts = shapely.get_num_geometries(geom)
if expected_num_parts == 0:
expected_parts = []
else:
expected_parts = shapely.get_geometry(geom, range(0, expected_num_parts))
parts = shapely.get_parts(geom)
assert len(parts) == expected_num_parts
assert_geometries_equal(parts, expected_parts)
def test_get_parts_array():
# note: this also verifies that None is handled correctly
# in the mix; internally it returns -1 for count of geometries
geom = np.array([None, empty_line_string, multi_point, point, multi_polygon])
expected_parts = []
for g in geom:
for i in range(0, shapely.get_num_geometries(g)):
expected_parts.append(shapely.get_geometry(g, i))
parts = shapely.get_parts(geom)
assert len(parts) == len(expected_parts)
assert_geometries_equal(parts, expected_parts)
def test_get_parts_geometry_collection_multi():
"""On the first pass, the individual Multi* geometry objects are returned
from the collection. On the second pass, the individual singular geometry
objects within those are returned.
"""
geom = shapely.geometrycollections([multi_point, multi_line_string, multi_polygon])
expected_num_parts = shapely.get_num_geometries(geom)
expected_parts = shapely.get_geometry(geom, range(0, expected_num_parts))
parts = shapely.get_parts(geom)
assert len(parts) == expected_num_parts
assert_geometries_equal(parts, expected_parts)
expected_subparts = []
for g in np.asarray(expected_parts):
for i in range(0, shapely.get_num_geometries(g)):
expected_subparts.append(shapely.get_geometry(g, i))
subparts = shapely.get_parts(parts)
assert len(subparts) == len(expected_subparts)
assert_geometries_equal(subparts, expected_subparts)
def test_get_parts_return_index():
geom = np.array([multi_point, point, multi_polygon])
expected_parts = []
expected_index = []
for i, g in enumerate(geom):
for j in range(0, shapely.get_num_geometries(g)):
expected_parts.append(shapely.get_geometry(g, j))
expected_index.append(i)
parts, index = shapely.get_parts(geom, return_index=True)
assert len(parts) == len(expected_parts)
assert_geometries_equal(parts, expected_parts)
assert np.array_equal(index, expected_index)
@pytest.mark.parametrize(
"geom",
([[None]], [[empty_point]], [[multi_point]], [[multi_point, multi_line_string]]),
)
def test_get_parts_invalid_dimensions(geom):
"""Only 1D inputs are supported"""
with pytest.raises(ValueError, match="Array should be one dimensional"):
shapely.get_parts(geom)
@pytest.mark.parametrize("geom", [point, line_string, polygon])
def test_get_parts_non_multi(geom):
"""Non-multipart geometries should be returned identical to inputs"""
assert_geometries_equal(geom, shapely.get_parts(geom))
@pytest.mark.parametrize("geom", [None, [None], []])
def test_get_parts_None(geom):
assert len(shapely.get_parts(geom)) == 0
@pytest.mark.parametrize("geom", ["foo", ["foo"], 42])
def test_get_parts_invalid_geometry(geom):
with pytest.raises(TypeError, match="One of the arguments is of incorrect type."):
shapely.get_parts(geom)
@pytest.mark.parametrize(
"geom",
[
point,
multi_point,
line_string,
multi_line_string,
polygon,
multi_polygon,
geometry_collection,
empty_point,
empty_line_string,
empty_polygon,
empty_geometry_collection,
None,
],
)
def test_get_rings(geom):
if (shapely.get_type_id(geom) != shapely.GeometryType.POLYGON) or shapely.is_empty(
geom
):
rings = shapely.get_rings(geom)
assert len(rings) == 0
else:
rings = shapely.get_rings(geom)
assert len(rings) == 1
assert rings[0] == shapely.get_exterior_ring(geom)
def test_get_rings_holes():
rings = shapely.get_rings(polygon_with_hole)
assert len(rings) == 2
assert rings[0] == shapely.get_exterior_ring(polygon_with_hole)
assert rings[1] == shapely.get_interior_ring(polygon_with_hole, 0)
def test_get_rings_return_index():
geom = np.array([polygon, None, empty_polygon, polygon_with_hole])
expected_parts = []
expected_index = []
for i, g in enumerate(geom):
if g is None or shapely.is_empty(g):
continue
expected_parts.append(shapely.get_exterior_ring(g))
expected_index.append(i)
for j in range(0, shapely.get_num_interior_rings(g)):
expected_parts.append(shapely.get_interior_ring(g, j))
expected_index.append(i)
parts, index = shapely.get_rings(geom, return_index=True)
assert len(parts) == len(expected_parts)
assert_geometries_equal(parts, expected_parts)
assert np.array_equal(index, expected_index)
@pytest.mark.parametrize("geom", [[[None]], [[polygon]]])
def test_get_rings_invalid_dimensions(geom):
"""Only 1D inputs are supported"""
with pytest.raises(ValueError, match="Array should be one dimensional"):
shapely.get_parts(geom)
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_get_precision():
geometries = all_types + (point_z, empty_point, empty_line_string, empty_polygon)
# default is 0
actual = shapely.get_precision(geometries).tolist()
assert actual == [0] * len(geometries)
geometry = shapely.set_precision(geometries, 1)
actual = shapely.get_precision(geometry).tolist()
assert actual == [1] * len(geometries)
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_get_precision_none():
assert np.all(np.isnan(shapely.get_precision([None])))
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
@pytest.mark.parametrize("mode", ("valid_output", "pointwise", "keep_collapsed"))
def test_set_precision(mode):
initial_geometry = Point(0.9, 0.9)
assert shapely.get_precision(initial_geometry) == 0
geometry = shapely.set_precision(initial_geometry, 0, mode=mode)
assert shapely.get_precision(geometry) == 0
assert_geometries_equal(geometry, initial_geometry)
geometry = shapely.set_precision(initial_geometry, 1, mode=mode)
assert shapely.get_precision(geometry) == 1
assert_geometries_equal(geometry, Point(1, 1))
# original should remain unchanged
assert_geometries_equal(initial_geometry, Point(0.9, 0.9))
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_set_precision_drop_coords():
# setting precision of 0 will not drop duplicated points in original
geometry = shapely.set_precision(LineString([(0, 0), (0, 0), (0, 1), (1, 1)]), 0)
assert_geometries_equal(geometry, LineString([(0, 0), (0, 0), (0, 1), (1, 1)]))
# setting precision will remove duplicated points
geometry = shapely.set_precision(geometry, 1)
assert_geometries_equal(geometry, LineString([(0, 0), (0, 1), (1, 1)]))
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
@pytest.mark.parametrize("mode", ("valid_output", "pointwise", "keep_collapsed"))
def test_set_precision_z(mode):
with warnings.catch_warnings():
warnings.simplefilter("ignore") # GEOS <= 3.9 emits warning for 'pointwise'
geometry = shapely.set_precision(Point(0.9, 0.9, 0.9), 1, mode=mode)
assert shapely.get_precision(geometry) == 1
assert_geometries_equal(geometry, Point(1, 1, 0.9))
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
@pytest.mark.parametrize("mode", ("valid_output", "pointwise", "keep_collapsed"))
def test_set_precision_nan(mode):
with warnings.catch_warnings():
warnings.simplefilter("ignore") # GEOS <= 3.9 emits warning for 'pointwise'
actual = shapely.set_precision(line_string_nan, 1, mode=mode)
assert_geometries_equal(actual, line_string_nan)
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_set_precision_none():
assert shapely.set_precision(None, 0) is None
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_set_precision_grid_size_nan():
assert shapely.set_precision(Point(0.9, 0.9), np.nan) is None
@pytest.mark.parametrize(
"geometry,mode,expected",
[
(
Polygon([(2, 2), (4, 2), (3.2, 3), (4, 4), (2, 4), (2.8, 3), (2, 2)]),
"valid_output",
MultiPolygon(
[
Polygon([(4, 2), (2, 2), (3, 3), (4, 2)]),
Polygon([(2, 4), (4, 4), (3, 3), (2, 4)]),
]
),
),
pytest.param(
Polygon([(2, 2), (4, 2), (3.2, 3), (4, 4), (2, 4), (2.8, 3), (2, 2)]),
"pointwise",
Polygon([(2, 2), (4, 2), (3, 3), (4, 4), (2, 4), (3, 3), (2, 2)]),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0),
reason="pointwise does not work pre-GEOS 3.10",
),
),
(
Polygon([(2, 2), (4, 2), (3.2, 3), (4, 4), (2, 4), (2.8, 3), (2, 2)]),
"keep_collapsed",
MultiPolygon(
[
Polygon([(4, 2), (2, 2), (3, 3), (4, 2)]),
Polygon([(2, 4), (4, 4), (3, 3), (2, 4)]),
]
),
),
(LineString([(0, 0), (0.1, 0.1)]), "valid_output", LineString()),
pytest.param(
LineString([(0, 0), (0.1, 0.1)]),
"pointwise",
LineString([(0, 0), (0, 0)]),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0),
reason="pointwise does not work pre-GEOS 3.10",
),
),
(
LineString([(0, 0), (0.1, 0.1)]),
"keep_collapsed",
LineString([(0, 0), (0, 0)]),
),
pytest.param(
LinearRing([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"valid_output",
LinearRing(),
marks=pytest.mark.skipif(
shapely.geos_version == (3, 10, 0), reason="Segfaults on GEOS 3.10.0"
),
),
pytest.param(
LinearRing([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"pointwise",
LinearRing([(0, 0), (0, 0), (0, 0), (0, 0), (0, 0)]),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0),
reason="pointwise does not work pre-GEOS 3.10",
),
),
pytest.param(
LinearRing([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"keep_collapsed",
# See https://trac.osgeo.org/geos/ticket/1135#comment:5
LineString([(0, 0), (0, 0), (0, 0)]),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0),
reason="this collapsed into an invalid linearring pre-GEOS 3.10",
),
),
(
Polygon([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"valid_output",
Polygon(),
),
pytest.param(
Polygon([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"pointwise",
Polygon([(0, 0), (0, 0), (0, 0), (0, 0), (0, 0)]),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0),
reason="pointwise does not work pre-GEOS 3.10",
),
),
(
Polygon([(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]),
"keep_collapsed",
Polygon(),
),
],
)
def test_set_precision_collapse(geometry, mode, expected):
"""Lines and polygons collapse to empty geometries if vertices are too close"""
actual = shapely.set_precision(geometry, 1, mode=mode)
if shapely.geos_version < (3, 9, 0):
# pre GEOS 3.9 has difficulty comparing empty geometries exactly
# normalize and compare by WKT instead
assert shapely.to_wkt(shapely.normalize(actual)) == shapely.to_wkt(
shapely.normalize(expected)
)
else:
# force to 2D because GEOS 3.10 yields 3D geometries when they are empty.
assert_geometries_equal(shapely.force_2d(actual), expected)
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_set_precision_intersection():
"""Operations should use the most precise presision grid size of the inputs"""
box1 = shapely.normalize(shapely.box(0, 0, 0.9, 0.9))
box2 = shapely.normalize(shapely.box(0.75, 0, 1.75, 0.75))
assert shapely.get_precision(shapely.intersection(box1, box2)) == 0
# GEOS will use and keep the most precise precision grid size
box1 = shapely.set_precision(box1, 0.5)
box2 = shapely.set_precision(box2, 1)
out = shapely.intersection(box1, box2)
assert shapely.get_precision(out) == 0.5
assert_geometries_equal(out, LineString([(1, 1), (1, 0)]))
@pytest.mark.parametrize("preserve_topology", [False, True])
def set_precision_preserve_topology(preserve_topology):
# the preserve_topology kwarg is deprecated (ignored)
with pytest.warns(UserWarning):
actual = shapely.set_precision(
LineString([(0, 0), (0.1, 0.1)]),
1.0,
preserve_topology=preserve_topology,
)
assert_geometries_equal(shapely.force_2d(actual), LineString())
@pytest.mark.skipif(shapely.geos_version >= (3, 10, 0), reason="GEOS >= 3.10")
def set_precision_pointwise_pre_310():
# using 'pointwise' emits a warning
with pytest.warns(UserWarning):
actual = shapely.set_precision(
LineString([(0, 0), (0.1, 0.1)]),
1.0,
mode="pointwise",
)
assert_geometries_equal(shapely.force_2d(actual), LineString())
@pytest.mark.parametrize("flags", [np.array([0, 1]), 4, "foo"])
def set_precision_illegal_flags(flags):
# the preserve_topology kwarg is deprecated (ignored)
with pytest.raises((ValueError, TypeError)):
shapely.lib.set_precision(line_string, 1.0, flags)
def test_empty():
"""Compatibility with empty_like, see GH373"""
g = np.empty_like(np.array([None, None]))
assert shapely.is_missing(g).all()
# corresponding to geometry_collection_z:
geometry_collection_2 = shapely.geometrycollections([point, line_string])
empty_geom_mark = pytest.mark.skipif(
shapely.geos_version < (3, 9, 0),
reason="Empty points don't have a dimensionality before GEOS 3.9",
)
@pytest.mark.parametrize(
"geom,expected",
[
(point, point),
(point_z, point),
pytest.param(empty_point, empty_point, marks=empty_geom_mark),
pytest.param(empty_point_z, empty_point, marks=empty_geom_mark),
(line_string, line_string),
(line_string_z, line_string),
pytest.param(empty_line_string, empty_line_string, marks=empty_geom_mark),
pytest.param(empty_line_string_z, empty_line_string, marks=empty_geom_mark),
(polygon, polygon),
(polygon_z, polygon),
(polygon_with_hole, polygon_with_hole),
(polygon_with_hole_z, polygon_with_hole),
(multi_point, multi_point),
(multi_point_z, multi_point),
(multi_line_string, multi_line_string),
(multi_line_string_z, multi_line_string),
(multi_polygon, multi_polygon),
(multi_polygon_z, multi_polygon),
(geometry_collection_2, geometry_collection_2),
(geometry_collection_z, geometry_collection_2),
],
)
def test_force_2d(geom, expected):
actual = shapely.force_2d(geom)
assert shapely.get_coordinate_dimension(actual) == 2
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize(
"geom,expected",
[
(point, point_z),
(point_z, point_z),
pytest.param(empty_point, empty_point_z, marks=empty_geom_mark),
pytest.param(empty_point_z, empty_point_z, marks=empty_geom_mark),
(line_string, line_string_z),
(line_string_z, line_string_z),
pytest.param(empty_line_string, empty_line_string_z, marks=empty_geom_mark),
pytest.param(empty_line_string_z, empty_line_string_z, marks=empty_geom_mark),
(polygon, polygon_z),
(polygon_z, polygon_z),
(polygon_with_hole, polygon_with_hole_z),
(polygon_with_hole_z, polygon_with_hole_z),
(multi_point, multi_point_z),
(multi_point_z, multi_point_z),
(multi_line_string, multi_line_string_z),
(multi_line_string_z, multi_line_string_z),
(multi_polygon, multi_polygon_z),
(multi_polygon_z, multi_polygon_z),
(geometry_collection_2, geometry_collection_z),
(geometry_collection_z, geometry_collection_z),
],
)
def test_force_3d(geom, expected):
actual = shapely.force_3d(geom, z=4)
assert shapely.get_coordinate_dimension(actual) == 3
assert_geometries_equal(actual, expected)
@@ -0,0 +1,790 @@
import json
import pickle
import struct
import warnings
import numpy as np
import pytest
import shapely
from shapely import GeometryCollection, LineString, Point, Polygon
from shapely.errors import UnsupportedGEOSVersionError
from shapely.testing import assert_geometries_equal
from shapely.tests.common import all_types, empty_point, empty_point_z, point, point_z
# fmt: off
POINT11_WKB = b"\x01\x01\x00\x00\x00" + struct.pack("<2d", 1.0, 1.0)
NAN = struct.pack("<d", float("nan"))
POINT_NAN_WKB = b'\x01\x01\x00\x00\x00' + (NAN * 2)
POINTZ_NAN_WKB = b'\x01\x01\x00\x00\x80' + (NAN * 3)
MULTIPOINT_NAN_WKB = b'\x01\x04\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00' + (NAN * 2)
MULTIPOINTZ_NAN_WKB = b'\x01\x04\x00\x00\x80\x01\x00\x00\x00\x01\x01\x00\x00\x80' + (NAN * 3)
GEOMETRYCOLLECTION_NAN_WKB = b'\x01\x07\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00' + (NAN * 2)
GEOMETRYCOLLECTIONZ_NAN_WKB = b'\x01\x07\x00\x00\x80\x01\x00\x00\x00\x01\x01\x00\x00\x80' + (NAN * 3)
NESTED_COLLECTION_NAN_WKB = b'\x01\x07\x00\x00\x00\x01\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00' + (NAN * 2)
NESTED_COLLECTIONZ_NAN_WKB = b'\x01\x07\x00\x00\x80\x01\x00\x00\x00\x01\x04\x00\x00\x80\x01\x00\x00\x00\x01\x01\x00\x00\x80' + (NAN * 3)
INVALID_WKB = "01030000000100000002000000507daec600b1354100de02498e5e3d41306ea321fcb03541a011a53d905e3d41"
# fmt: on
GEOJSON_GEOMETRY = json.dumps({"type": "Point", "coordinates": [125.6, 10.1]}, indent=4)
GEOJSON_FEATURE = json.dumps(
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [125.6, 10.1]},
"properties": {"name": "Dinagat Islands"},
},
indent=4,
)
GEOJSON_FEATURECOLECTION = json.dumps(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [102.0, 0.6]},
"properties": {"prop0": "value0"},
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0],
[103.0, 1.0],
[104.0, 0.0],
[105.0, 1.0],
],
},
"properties": {"prop1": 0.0, "prop0": "value0"},
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0],
]
],
},
"properties": {"prop1": {"this": "that"}, "prop0": "value0"},
},
],
},
indent=4,
)
GEOJSON_GEOMETRY_EXPECTED = shapely.points(125.6, 10.1)
GEOJSON_COLLECTION_EXPECTED = [
shapely.points([102.0, 0.6]),
shapely.linestrings([[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]),
shapely.polygons(
[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
),
]
def test_from_wkt():
expected = shapely.points(1, 1)
actual = shapely.from_wkt("POINT (1 1)")
assert_geometries_equal(actual, expected)
# also accept bytes
actual = shapely.from_wkt(b"POINT (1 1)")
assert_geometries_equal(actual, expected)
def test_from_wkt_none():
# None propagates
assert shapely.from_wkt(None) is None
def test_from_wkt_exceptions():
with pytest.raises(TypeError, match="Expected bytes or string, got int"):
shapely.from_wkt(1)
with pytest.raises(
shapely.GEOSException, match="Expected word but encountered end of stream"
):
shapely.from_wkt("")
with pytest.raises(shapely.GEOSException, match="Unknown type: 'NOT'"):
shapely.from_wkt("NOT A WKT STRING")
def test_from_wkt_warn_on_invalid():
with pytest.warns(Warning, match="Invalid WKT"):
shapely.from_wkt("", on_invalid="warn")
with pytest.warns(Warning, match="Invalid WKT"):
shapely.from_wkt("NOT A WKT STRING", on_invalid="warn")
def test_from_wkb_ignore_on_invalid():
with warnings.catch_warnings():
warnings.simplefilter("error")
shapely.from_wkt("", on_invalid="ignore")
with warnings.catch_warnings():
warnings.simplefilter("error")
shapely.from_wkt("NOT A WKT STRING", on_invalid="ignore")
def test_from_wkt_on_invalid_unsupported_option():
with pytest.raises(ValueError, match="not a valid option"):
shapely.from_wkt(b"\x01\x01\x00\x00\x00\x00", on_invalid="unsupported_option")
@pytest.mark.parametrize("geom", all_types)
def test_from_wkt_all_types(geom):
wkt = shapely.to_wkt(geom)
actual = shapely.from_wkt(wkt)
assert_geometries_equal(actual, geom)
@pytest.mark.parametrize(
"wkt",
("POINT EMPTY", "LINESTRING EMPTY", "POLYGON EMPTY", "GEOMETRYCOLLECTION EMPTY"),
)
def test_from_wkt_empty(wkt):
geom = shapely.from_wkt(wkt)
assert shapely.is_geometry(geom).all()
assert shapely.is_empty(geom).all()
assert shapely.to_wkt(geom) == wkt
# WKT from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp
@pytest.mark.parametrize(
"wkt",
(
"CIRCULARSTRING(1 3,2 4,3 1)",
"COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))",
"CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0,2 1,2 3,4 3),(4 3,4 5,1 4,0 0)),CIRCULARSTRING(1.7 1,1.4 0.4,1.6 0.4,1.6 0.5,1.7 1))", # noqa: E501
"MULTICURVE((0 0,5 5),COMPOUNDCURVE((-1 -1,0 0),CIRCULARSTRING(0 0,1 1,2 0)),CIRCULARSTRING(4 0,4 4,8 4))", # noqa: E501
"MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))", # noqa: E501
),
)
def test_from_wkt_nonlinear_unsupported(wkt):
if shapely.geos_version >= (3, 13, 0):
with pytest.raises(
NotImplementedError,
match="Nonlinear geometry types are not currently supported",
):
shapely.from_wkt(wkt)
else:
# prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKT
with pytest.raises(shapely.errors.GEOSException, match="Unknown type"):
shapely.from_wkt(wkt)
def test_from_wkb():
expected = shapely.points(1, 1)
actual = shapely.from_wkb(POINT11_WKB)
assert_geometries_equal(actual, expected)
def test_from_wkb_hex():
# HEX form
expected = shapely.points(1, 1)
actual = shapely.from_wkb("0101000000000000000000F03F000000000000F03F")
assert_geometries_equal(actual, expected)
actual = shapely.from_wkb(b"0101000000000000000000F03F000000000000F03F")
assert_geometries_equal(actual, expected)
def test_from_wkb_none():
# None propagates
assert shapely.from_wkb(None) is None
def test_from_wkb_exceptions():
with pytest.raises(TypeError, match="Expected bytes or string, got int"):
shapely.from_wkb(1)
# invalid WKB
with pytest.raises(
shapely.GEOSException,
match=(
"Unexpected EOF parsing WKB|"
"ParseException: Input buffer is smaller than requested object size"
),
):
result = shapely.from_wkb(b"\x01\x01\x00\x00\x00\x00")
assert result is None
# invalid ring in WKB
with pytest.raises(
shapely.GEOSException,
match="Points of LinearRing do not form a closed linestring",
):
result = shapely.from_wkb(INVALID_WKB)
assert result is None
def test_from_wkb_warn_on_invalid_warn():
# invalid WKB
with pytest.warns(Warning, match="Invalid WKB"):
result = shapely.from_wkb(b"\x01\x01\x00\x00\x00\x00", on_invalid="warn")
assert result is None
# invalid ring in WKB
with pytest.warns(Warning, match="Invalid WKB"):
result = shapely.from_wkb(INVALID_WKB, on_invalid="warn")
assert result is None
def test_from_wkb_ignore_on_invalid_ignore():
# invalid WKB
with warnings.catch_warnings():
warnings.simplefilter("error") # no warning
result = shapely.from_wkb(b"\x01\x01\x00\x00\x00\x00", on_invalid="ignore")
assert result is None
# invalid ring in WKB
with warnings.catch_warnings():
warnings.simplefilter("error") # no warning
result = shapely.from_wkb(INVALID_WKB, on_invalid="ignore")
assert result is None
def test_from_wkb_on_invalid_unsupported_option():
with pytest.raises(ValueError, match="not a valid option"):
shapely.from_wkb(b"\x01\x01\x00\x00\x00\x00", on_invalid="unsupported_option")
@pytest.mark.parametrize("geom", all_types)
@pytest.mark.parametrize("use_hex", [False, True])
@pytest.mark.parametrize("byte_order", [0, 1])
def test_from_wkb_all_types(geom, use_hex, byte_order):
if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
pytest.skip("Linearrings are not preserved in WKB")
wkb = shapely.to_wkb(geom, hex=use_hex, byte_order=byte_order)
actual = shapely.from_wkb(wkb)
assert_geometries_equal(actual, geom)
@pytest.mark.parametrize(
"geom",
(Point(), LineString(), Polygon(), GeometryCollection()),
)
def test_from_wkb_empty(geom):
wkb = shapely.to_wkb(geom)
geom = shapely.from_wkb(wkb)
assert shapely.is_geometry(geom).all()
assert shapely.is_empty(geom).all()
assert shapely.to_wkb(geom) == wkb
# WKB from https://github.com/libgeos/geos/blob/main/tests/unit/io/WKBReaderTest.cpp
@pytest.mark.parametrize(
"wkb",
(
# "CIRCULARSTRING(1 3,2 4,3 1)",
"010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F",
# "COMPOUNDCURVE(CIRCULARSTRING(1 3,2 4,3 1),(3 1,0 0))",
"01090000200E16000002000000010800000003000000000000000000F03F0000000000000840000000000000004000000000000010400000000000000840000000000000F03F0102000000020000000000000000000840000000000000F03F00000000000000000000000000000000",
# "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0,2 1,2 3,4 3),(4 3,4 5,1 4,0 0)),CIRCULARSTRING(1.7 1,1.4 0.4,1.6 0.4,1.6 0.5,1.7 1))", # noqa: E501
"010A0000200E1600000200000001090000000200000001080000000500000000000000000000000000000000000000000000000000004000000000000000000000000000000040000000000000F03F00000000000000400000000000000840000000000000104000000000000008400102000000040000000000000000001040000000000000084000000000000010400000000000001440000000000000F03F000000000000104000000000000000000000000000000000010800000005000000333333333333FB3F000000000000F03F666666666666F63F9A9999999999D93F9A9999999999F93F9A9999999999D93F9A9999999999F93F000000000000E03F333333333333FB3F000000000000F03F",
# "MULTICURVE((0 0,5 5),COMPOUNDCURVE((-1 -1,0 0),CIRCULARSTRING(0 0,1 1,2 0)),CIRCULARSTRING(4 0,4 4,8 4))", # noqa: E501
"010B000000030000000102000000020000000000000000000000000000000000000000000000000014400000000000001440010900000002000000010200000002000000000000000000F0BF000000000000F0BF0000000000000000000000000000000001080000000300000000000000000000000000000000000000000000000000F03F000000000000F03F00000000000000400000000000000000010800000003000000000000000000104000000000000000000000000000001040000000000000104000000000000020400000000000001040",
# "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))", # noqa: E501
"010C00000002000000010A000000020000000108000000050000000000000000000000000000000000000000000000000010400000000000000000000000000000104000000000000010400000000000000000000000000000104000000000000000000000000000000000010200000004000000000000000000F03F000000000000F03F000000000000084000000000000008400000000000000840000000000000F03F000000000000F03F000000000000F03F01030000000200000004000000000000000000244000000000000024400000000000002C40000000000000284000000000000026400000000000002440000000000000244000000000000024400400000000000000000026400000000000002640000000000000274000000000000026400000000000002640000000000000274000000000000026400000000000002640",
),
)
def test_from_wkb_nonlinear_unsupported(wkb):
if shapely.geos_version >= (3, 13, 0):
with pytest.raises(
NotImplementedError,
match="Nonlinear geometry types are not currently supported",
):
shapely.from_wkb(wkb)
else:
# prior to GEOS 3.13 nonlinear types were rejected by GEOS on read from WKB
with pytest.raises(shapely.errors.GEOSException, match="Unknown WKB type"):
shapely.from_wkb(wkb)
def test_to_wkt():
point = shapely.points(1, 1)
actual = shapely.to_wkt(point)
assert actual == "POINT (1 1)"
actual = shapely.to_wkt(point, trim=False)
assert actual == "POINT (1.000000 1.000000)"
actual = shapely.to_wkt(point, rounding_precision=3, trim=False)
assert actual == "POINT (1.000 1.000)"
def test_to_wkt_3D():
# 3D points
point_z = shapely.points(1, 1, 1)
actual = shapely.to_wkt(point_z)
assert actual == "POINT Z (1 1 1)"
actual = shapely.to_wkt(point_z, output_dimension=3)
assert actual == "POINT Z (1 1 1)"
actual = shapely.to_wkt(point_z, output_dimension=2)
assert actual == "POINT (1 1)"
actual = shapely.to_wkt(point_z, old_3d=True)
assert actual == "POINT (1 1 1)"
def test_to_wkt_none():
# None propagates
assert shapely.to_wkt(None) is None
def test_to_wkt_array_with_empty_z():
# See GH-2004
empty_wkt = ["POINT Z EMPTY", None, "POLYGON Z EMPTY"]
empty_geoms = shapely.from_wkt(empty_wkt)
if shapely.geos_version < (3, 9, 0):
empty_wkt = ["POINT EMPTY", None, "POLYGON EMPTY"]
assert list(shapely.to_wkt(empty_geoms)) == empty_wkt
def test_to_wkt_exceptions():
with pytest.raises(TypeError):
shapely.to_wkt(1)
with pytest.raises(shapely.GEOSException):
shapely.to_wkt(point, output_dimension=5)
def test_to_wkt_point_empty():
assert shapely.to_wkt(empty_point) == "POINT EMPTY"
@pytest.mark.skipif(
shapely.geos_version < (3, 9, 0),
reason="Empty geometries have no dimensionality on GEOS < 3.9",
)
@pytest.mark.parametrize(
"wkt",
[
"POINT Z EMPTY",
"LINESTRING Z EMPTY",
"LINEARRING Z EMPTY",
"POLYGON Z EMPTY",
],
)
def test_to_wkt_empty_z(wkt):
assert shapely.to_wkt(shapely.from_wkt(wkt)) == wkt
def test_to_wkt_geometrycollection_with_point_empty():
collection = shapely.geometrycollections([empty_point, point])
# do not check the full value as some GEOS versions give
# GEOMETRYCOLLECTION Z (...) and others give GEOMETRYCOLLECTION (...)
assert shapely.to_wkt(collection).endswith("(POINT EMPTY, POINT (2 3))")
@pytest.mark.skipif(
shapely.geos_version < (3, 9, 0),
reason="MULTIPOINT (EMPTY, (2 3)) only works for GEOS >= 3.9",
)
def test_to_wkt_multipoint_with_point_empty():
geom = shapely.multipoints([empty_point, point])
if shapely.geos_version >= (3, 12, 0):
expected = "MULTIPOINT (EMPTY, (2 3))"
else:
# invalid WKT form
expected = "MULTIPOINT (EMPTY, 2 3)"
assert shapely.to_wkt(geom) == expected
@pytest.mark.skipif(
shapely.geos_version >= (3, 9, 0),
reason="MULTIPOINT (EMPTY, 2 3) gives ValueError on GEOS < 3.9",
)
def test_to_wkt_multipoint_with_point_empty_errors():
# test if segfault is prevented
geom = shapely.multipoints([empty_point, point])
with pytest.raises(ValueError):
shapely.to_wkt(geom)
def test_repr():
assert repr(point) == "<POINT (2 3)>"
def test_repr_max_length():
# the repr is limited to 80 characters
geom = shapely.linestrings(np.arange(1000), np.arange(1000))
representation = repr(geom)
assert len(representation) == 80
assert representation.endswith("...>")
@pytest.mark.skipif(
shapely.geos_version >= (3, 9, 0),
reason="MULTIPOINT (EMPTY, 2 3) gives Exception on GEOS < 3.9",
)
def test_repr_multipoint_with_point_empty():
# Test if segfault is prevented
geom = shapely.multipoints([point, empty_point])
assert repr(geom) == "<shapely.MultiPoint Exception in WKT writer>"
@pytest.mark.skipif(
shapely.geos_version < (3, 9, 0),
reason="Empty geometries have no dimensionality on GEOS < 3.9",
)
def test_repr_point_z_empty():
assert repr(empty_point_z) == "<POINT Z EMPTY>"
def test_to_wkb():
point = shapely.points(1, 1)
actual = shapely.to_wkb(point, byte_order=1)
assert actual == POINT11_WKB
def test_to_wkb_hex():
point = shapely.points(1, 1)
actual = shapely.to_wkb(point, hex=True, byte_order=1)
le = "01"
point_type = "01000000"
coord = "000000000000F03F" # 1.0 as double (LE)
assert actual == le + point_type + 2 * coord
def test_to_wkb_3D():
point_z = shapely.points(1, 1, 1)
actual = shapely.to_wkb(point_z, byte_order=1)
# fmt: off
assert actual == b"\x01\x01\x00\x00\x80\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?" # noqa
# fmt: on
actual = shapely.to_wkb(point_z, output_dimension=2, byte_order=1)
assert actual == POINT11_WKB
def test_to_wkb_none():
# None propagates
assert shapely.to_wkb(None) is None
def test_to_wkb_exceptions():
with pytest.raises(TypeError):
shapely.to_wkb(1)
with pytest.raises(shapely.GEOSException):
shapely.to_wkb(point, output_dimension=5)
with pytest.raises(ValueError):
shapely.to_wkb(point, flavor="other")
def test_to_wkb_byte_order():
point = shapely.points(1.0, 1.0)
be = b"\x00"
le = b"\x01"
point_type = b"\x01\x00\x00\x00" # 1 as 32-bit uint (LE)
coord = b"\x00\x00\x00\x00\x00\x00\xf0?" # 1.0 as double (LE)
assert shapely.to_wkb(point, byte_order=1) == le + point_type + 2 * coord
assert (
shapely.to_wkb(point, byte_order=0) == be + point_type[::-1] + 2 * coord[::-1]
)
def test_to_wkb_srid():
# hex representation of POINT (0 0) with SRID=4
ewkb = "01010000200400000000000000000000000000000000000000"
wkb = "010100000000000000000000000000000000000000"
actual = shapely.from_wkb(ewkb)
assert shapely.to_wkt(actual, trim=True) == "POINT (0 0)"
assert shapely.to_wkb(actual, hex=True, byte_order=1) == wkb
assert shapely.to_wkb(actual, hex=True, include_srid=True, byte_order=1) == ewkb
point = shapely.points(1, 1)
point_with_srid = shapely.set_srid(point, np.int32(4326))
result = shapely.to_wkb(point_with_srid, include_srid=True, byte_order=1)
assert np.frombuffer(result[5:9], "<u4").item() == 4326
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
def test_to_wkb_flavor():
# http://libgeos.org/specifications/wkb/#extended-wkb
actual = shapely.to_wkb(point_z, byte_order=1) # default "extended"
assert actual.hex()[2:10] == "01000080"
actual = shapely.to_wkb(point_z, byte_order=1, flavor="extended")
assert actual.hex()[2:10] == "01000080"
actual = shapely.to_wkb(point_z, byte_order=1, flavor="iso")
assert actual.hex()[2:10] == "e9030000"
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10.0")
def test_to_wkb_flavor_srid():
with pytest.raises(ValueError, match="cannot be used together"):
shapely.to_wkb(point_z, include_srid=True, flavor="iso")
@pytest.mark.skipif(shapely.geos_version >= (3, 10, 0), reason="GEOS < 3.10.0")
def test_to_wkb_flavor_unsupported_geos():
with pytest.raises(UnsupportedGEOSVersionError):
shapely.to_wkb(point_z, flavor="iso")
@pytest.mark.parametrize(
"geom,expected",
[
(empty_point, POINT_NAN_WKB),
(empty_point_z, POINT_NAN_WKB),
(shapely.multipoints([empty_point]), MULTIPOINT_NAN_WKB),
(shapely.multipoints([empty_point_z]), MULTIPOINT_NAN_WKB),
(shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB),
(shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTION_NAN_WKB),
(
shapely.geometrycollections([shapely.multipoints([empty_point])]),
NESTED_COLLECTION_NAN_WKB,
),
(
shapely.geometrycollections([shapely.multipoints([empty_point_z])]),
NESTED_COLLECTION_NAN_WKB,
),
],
)
def test_to_wkb_point_empty_2d(geom, expected):
actual = shapely.to_wkb(geom, output_dimension=2, byte_order=1)
# Split 'actual' into header and coordinates
coordinate_length = 16
header_length = len(expected) - coordinate_length
# Check the total length (this checks the correct dimensionality)
assert len(actual) == header_length + coordinate_length
# Check the header
assert actual[:header_length] == expected[:header_length]
# Check the coordinates (using numpy.isnan; there are many byte representations for NaN)
assert np.isnan(struct.unpack("<2d", actual[header_length:])).all()
@pytest.mark.xfail(
shapely.geos_version[:2] == (3, 8), reason="GEOS==3.8 never outputs 3D empty points"
)
@pytest.mark.parametrize(
"geom,expected",
[
(empty_point_z, POINTZ_NAN_WKB),
(shapely.multipoints([empty_point_z]), MULTIPOINTZ_NAN_WKB),
(shapely.geometrycollections([empty_point_z]), GEOMETRYCOLLECTIONZ_NAN_WKB),
(
shapely.geometrycollections([shapely.multipoints([empty_point_z])]),
NESTED_COLLECTIONZ_NAN_WKB,
),
],
)
def test_to_wkb_point_empty_3d(geom, expected):
actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1)
# Split 'actual' into header and coordinates
coordinate_length = 24
header_length = len(expected) - coordinate_length
# Check the total length (this checks the correct dimensionality)
assert len(actual) == header_length + coordinate_length
# Check the header
assert actual[:header_length] == expected[:header_length]
# Check the coordinates (using numpy.isnan; there are many byte representations for NaN)
assert np.isnan(struct.unpack("<3d", actual[header_length:])).all()
@pytest.mark.xfail(
shapely.geos_version < (3, 8, 0),
reason="GEOS<3.8 always outputs 3D empty points if output_dimension=3",
)
@pytest.mark.parametrize(
"geom,expected",
[
(empty_point, POINT_NAN_WKB),
(shapely.multipoints([empty_point]), MULTIPOINT_NAN_WKB),
(shapely.geometrycollections([empty_point]), GEOMETRYCOLLECTION_NAN_WKB),
(
shapely.geometrycollections([shapely.multipoints([empty_point])]),
NESTED_COLLECTION_NAN_WKB,
),
],
)
def test_to_wkb_point_empty_2d_output_dim_3(geom, expected):
actual = shapely.to_wkb(geom, output_dimension=3, byte_order=1)
# Split 'actual' into header and coordinates
coordinate_length = 16
header_length = len(expected) - coordinate_length
# Check the total length (this checks the correct dimensionality)
assert len(actual) == header_length + coordinate_length
# Check the header
assert actual[:header_length] == expected[:header_length]
# Check the coordinates (using numpy.isnan; there are many byte representations for NaN)
assert np.isnan(struct.unpack("<2d", actual[header_length:])).all()
@pytest.mark.parametrize(
"wkb,expected_type,expected_dim",
[
(POINT_NAN_WKB, 0, 2),
(POINTZ_NAN_WKB, 0, 3),
(MULTIPOINT_NAN_WKB, 4, 2),
(MULTIPOINTZ_NAN_WKB, 4, 3),
(GEOMETRYCOLLECTION_NAN_WKB, 7, 2),
(GEOMETRYCOLLECTIONZ_NAN_WKB, 7, 3),
(NESTED_COLLECTION_NAN_WKB, 7, 2),
(NESTED_COLLECTIONZ_NAN_WKB, 7, 3),
],
)
def test_from_wkb_point_empty(wkb, expected_type, expected_dim):
geom = shapely.from_wkb(wkb)
# POINT (nan nan) transforms to an empty point
assert shapely.is_empty(geom)
assert shapely.get_type_id(geom) == expected_type
# The dimensionality (2D/3D) is only read correctly for GEOS >= 3.9.0
if shapely.geos_version >= (3, 9, 0):
assert shapely.get_coordinate_dimension(geom) == expected_dim
def test_to_wkb_point_empty_srid():
expected = shapely.set_srid(empty_point, 4236)
wkb = shapely.to_wkb(expected, include_srid=True)
actual = shapely.from_wkb(wkb)
assert shapely.get_srid(actual) == 4236
@pytest.mark.parametrize("geom", all_types + (point_z, empty_point))
def test_pickle(geom):
pickled = pickle.dumps(geom)
assert_geometries_equal(pickle.loads(pickled), geom, tolerance=0)
@pytest.mark.parametrize("geom", all_types + (point_z, empty_point))
def test_pickle_with_srid(geom):
geom = shapely.set_srid(geom, 4326)
pickled = pickle.dumps(geom)
assert shapely.get_srid(pickle.loads(pickled)) == 4326
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
@pytest.mark.parametrize(
"geojson,expected",
[
(GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED),
(GEOJSON_FEATURE, GEOJSON_GEOMETRY_EXPECTED),
(
GEOJSON_FEATURECOLECTION,
shapely.geometrycollections(GEOJSON_COLLECTION_EXPECTED),
),
([GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2),
(None, None),
([GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, None]),
],
)
def test_from_geojson(geojson, expected):
actual = shapely.from_geojson(geojson)
assert_geometries_equal(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
def test_from_geojson_exceptions():
with pytest.raises(TypeError, match="Expected bytes or string, got int"):
shapely.from_geojson(1)
with pytest.raises(shapely.GEOSException, match="Error parsing JSON"):
shapely.from_geojson("")
with pytest.raises(shapely.GEOSException, match="Unknown geometry type"):
shapely.from_geojson('{"type": "NoGeometry", "coordinates": []}')
with pytest.raises(shapely.GEOSException, match="type must be array, but is null"):
shapely.from_geojson('{"type": "LineString", "coordinates": null}')
# Note: The two below tests are the reason that from_geojson is disabled for
# GEOS 3.10.0 See https://trac.osgeo.org/geos/ticket/1138
with pytest.raises(shapely.GEOSException, match="key 'type' not found"):
shapely.from_geojson('{"geometry": null, "properties": []}')
with pytest.raises(shapely.GEOSException, match="key 'type' not found"):
shapely.from_geojson('{"no": "geojson"}')
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
def test_from_geojson_warn_on_invalid():
with pytest.warns(Warning, match="Invalid GeoJSON"):
assert shapely.from_geojson("", on_invalid="warn") is None
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
def test_from_geojson_ignore_on_invalid():
with warnings.catch_warnings():
warnings.simplefilter("error")
assert shapely.from_geojson("", on_invalid="ignore") is None
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
def test_from_geojson_on_invalid_unsupported_option():
with pytest.raises(ValueError, match="not a valid option"):
shapely.from_geojson(GEOJSON_GEOMETRY, on_invalid="unsupported_option")
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
@pytest.mark.parametrize(
"expected,geometry",
[
(GEOJSON_GEOMETRY, GEOJSON_GEOMETRY_EXPECTED),
([GEOJSON_GEOMETRY] * 2, [GEOJSON_GEOMETRY_EXPECTED] * 2),
(None, None),
([GEOJSON_GEOMETRY, None], [GEOJSON_GEOMETRY_EXPECTED, None]),
],
)
def test_to_geojson(geometry, expected):
actual = shapely.to_geojson(geometry, indent=4)
assert np.all(actual == np.asarray(expected))
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
@pytest.mark.parametrize("indent", [None, 0, 4])
def test_to_geojson_indent(indent):
separators = (",", ":") if indent is None else (",", ": ")
expected = json.dumps(
json.loads(GEOJSON_GEOMETRY), indent=indent, separators=separators
)
actual = shapely.to_geojson(GEOJSON_GEOMETRY_EXPECTED, indent=indent)
assert actual == expected
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
def test_to_geojson_exceptions():
with pytest.raises(TypeError):
shapely.to_geojson(1)
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
@pytest.mark.parametrize(
"geom",
[
empty_point,
shapely.multipoints([empty_point, point]),
shapely.geometrycollections([empty_point, point]),
shapely.geometrycollections(
[shapely.geometrycollections([empty_point]), point]
),
],
)
def test_to_geojson_point_empty(geom):
# Pending GEOS ticket: https://trac.osgeo.org/geos/ticket/1139
with pytest.raises(ValueError):
assert shapely.to_geojson(geom)
@pytest.mark.skipif(shapely.geos_version < (3, 10, 1), reason="GEOS < 3.10.1")
@pytest.mark.parametrize("geom", all_types)
def test_geojson_all_types(geom):
if shapely.get_type_id(geom) == shapely.GeometryType.LINEARRING:
pytest.skip("Linearrings are not preserved in GeoJSON")
geojson = shapely.to_geojson(geom)
actual = shapely.from_geojson(geojson)
assert_geometries_equal(actual, geom)
@@ -0,0 +1,212 @@
import numpy as np
import pytest
import shapely
from shapely import GeometryCollection, LinearRing, LineString, MultiLineString, Point
from shapely.errors import UnsupportedGEOSVersionError
from shapely.testing import assert_geometries_equal
from shapely.tests.common import (
empty_line_string,
empty_point,
line_string,
linear_ring,
multi_line_string,
multi_point,
multi_polygon,
point,
polygon,
)
def test_line_interpolate_point_geom_array():
actual = shapely.line_interpolate_point(
[line_string, linear_ring, multi_line_string], -1
)
assert_geometries_equal(actual[0], Point(1, 0))
assert_geometries_equal(actual[1], Point(0, 1))
assert_geometries_equal(actual[2], Point(0.5528, 1.1056), tolerance=0.001)
def test_line_interpolate_point_geom_array_normalized():
actual = shapely.line_interpolate_point(
[line_string, linear_ring, multi_line_string], 1, normalized=True
)
assert_geometries_equal(actual[0], Point(1, 1))
assert_geometries_equal(actual[1], Point(0, 0))
assert_geometries_equal(actual[2], Point(1, 2))
def test_line_interpolate_point_float_array():
actual = shapely.line_interpolate_point(line_string, [0.2, 1.5, -0.2])
assert_geometries_equal(actual[0], Point(0.2, 0))
assert_geometries_equal(actual[1], Point(1, 0.5))
assert_geometries_equal(actual[2], Point(1, 0.8))
@pytest.mark.parametrize("normalized", [False, True])
@pytest.mark.parametrize(
"geom",
[
LineString(),
LinearRing(),
MultiLineString(),
shapely.from_wkt("MULTILINESTRING (EMPTY, (0 0, 1 1))"),
GeometryCollection(),
GeometryCollection([LineString(), Point(1, 1)]),
],
)
def test_line_interpolate_point_empty(geom, normalized):
# These geometries segfault in some versions of GEOS (in 3.8.0, still
# some of them segfault). Instead, we patched this to return POINT EMPTY.
# This matches GEOS 3.8.0 behavior on simple empty geometries.
assert_geometries_equal(
shapely.line_interpolate_point(geom, 0.2, normalized=normalized), empty_point
)
@pytest.mark.parametrize("normalized", [False, True])
@pytest.mark.parametrize(
"geom",
[
empty_point,
point,
polygon,
multi_point,
multi_polygon,
shapely.geometrycollections([point]),
shapely.geometrycollections([polygon]),
shapely.geometrycollections([multi_line_string]),
shapely.geometrycollections([multi_point]),
shapely.geometrycollections([multi_polygon]),
],
)
def test_line_interpolate_point_invalid_type(geom, normalized):
with pytest.raises(TypeError):
assert shapely.line_interpolate_point(geom, 0.2, normalized=normalized)
def test_line_interpolate_point_none():
assert shapely.line_interpolate_point(None, 0.2) is None
def test_line_interpolate_point_nan():
assert shapely.line_interpolate_point(line_string, np.nan) is None
def test_line_locate_point_geom_array():
point = shapely.points(0, 1)
actual = shapely.line_locate_point([line_string, linear_ring], point)
np.testing.assert_allclose(actual, [0.0, 3.0])
def test_line_locate_point_geom_array2():
points = shapely.points([[0, 0], [1, 0]])
actual = shapely.line_locate_point(line_string, points)
np.testing.assert_allclose(actual, [0.0, 1.0])
@pytest.mark.parametrize("normalized", [False, True])
def test_line_locate_point_none(normalized):
assert np.isnan(shapely.line_locate_point(line_string, None, normalized=normalized))
assert np.isnan(shapely.line_locate_point(None, point, normalized=normalized))
@pytest.mark.parametrize("normalized", [False, True])
def test_line_locate_point_empty(normalized):
assert np.isnan(
shapely.line_locate_point(line_string, empty_point, normalized=normalized)
)
assert np.isnan(
shapely.line_locate_point(empty_line_string, point, normalized=normalized)
)
@pytest.mark.parametrize("normalized", [False, True])
def test_line_locate_point_invalid_geometry(normalized):
with pytest.raises(shapely.GEOSException):
shapely.line_locate_point(line_string, line_string, normalized=normalized)
with pytest.raises(shapely.GEOSException):
shapely.line_locate_point(polygon, point, normalized=normalized)
def test_line_merge_geom_array():
actual = shapely.line_merge([line_string, multi_line_string])
assert_geometries_equal(actual[0], line_string)
assert_geometries_equal(actual[1], LineString([(0, 0), (1, 2)]))
@pytest.mark.skipif(shapely.geos_version < (3, 11, 0), reason="GEOS < 3.11.0")
def test_line_merge_directed():
lines = MultiLineString([[(0, 0), (1, 0)], [(0, 0), (3, 0)]])
# Merge lines without directed, this requires changing the vertex ordering
result = shapely.line_merge(lines)
assert_geometries_equal(result, LineString([(1, 0), (0, 0), (3, 0)]))
# Since the lines can't be merged when directed is specified
# the original geometry is returned
result = shapely.line_merge(lines, directed=True)
assert_geometries_equal(result, lines)
@pytest.mark.skipif(shapely.geos_version >= (3, 11, 0), reason="GEOS >= 3.11.0")
def test_line_merge_error():
lines = MultiLineString([[(0, 0), (1, 0)], [(0, 0), (3, 0)]])
with pytest.raises(UnsupportedGEOSVersionError):
shapely.line_merge(lines, directed=True)
def test_shared_paths_linestring():
g1 = shapely.linestrings([(0, 0), (1, 0), (1, 1)])
g2 = shapely.linestrings([(0, 0), (1, 0)])
actual1 = shapely.shared_paths(g1, g2)
assert_geometries_equal(
shapely.get_geometry(actual1, 0), shapely.multilinestrings([g2])
)
def test_shared_paths_none():
assert shapely.shared_paths(line_string, None) is None
assert shapely.shared_paths(None, line_string) is None
assert shapely.shared_paths(None, None) is None
def test_shared_paths_non_linestring():
g1 = shapely.linestrings([(0, 0), (1, 0), (1, 1)])
g2 = shapely.points(0, 1)
with pytest.raises(shapely.GEOSException):
shapely.shared_paths(g1, g2)
def _prepare_input(geometry, prepare):
"""Prepare without modifying in-place"""
if prepare:
geometry = shapely.transform(geometry, lambda x: x) # makes a copy
shapely.prepare(geometry)
return geometry
else:
return geometry
@pytest.mark.parametrize("prepare", [True, False])
def test_shortest_line(prepare):
g1 = shapely.linestrings([(0, 0), (1, 0), (1, 1)])
g2 = shapely.linestrings([(0, 3), (3, 0)])
actual = shapely.shortest_line(_prepare_input(g1, prepare), g2)
expected = shapely.linestrings([(1, 1), (1.5, 1.5)])
assert shapely.equals(actual, expected)
@pytest.mark.parametrize("prepare", [True, False])
def test_shortest_line_none(prepare):
assert shapely.shortest_line(_prepare_input(line_string, prepare), None) is None
assert shapely.shortest_line(None, line_string) is None
assert shapely.shortest_line(None, None) is None
@pytest.mark.parametrize("prepare", [True, False])
def test_shortest_line_empty(prepare):
g1 = _prepare_input(line_string, prepare)
assert shapely.shortest_line(g1, empty_line_string) is None
g1_empty = _prepare_input(empty_line_string, prepare)
assert shapely.shortest_line(g1_empty, line_string) is None
assert shapely.shortest_line(g1_empty, empty_line_string) is None
@@ -0,0 +1,355 @@
import numpy as np
import pytest
from numpy.testing import assert_allclose, assert_array_equal
import shapely
from shapely import GeometryCollection, LineString, MultiPoint, Point, Polygon
from shapely.tests.common import (
empty,
geometry_collection,
ignore_invalid,
line_string,
linear_ring,
multi_line_string,
multi_point,
multi_polygon,
point,
point_polygon_testdata,
polygon,
polygon_with_hole,
)
@pytest.mark.parametrize(
"geom",
[
point,
line_string,
linear_ring,
multi_point,
multi_line_string,
geometry_collection,
],
)
def test_area_non_polygon(geom):
assert shapely.area(geom) == 0.0
def test_area():
actual = shapely.area([polygon, polygon_with_hole, multi_polygon])
assert actual.tolist() == [4.0, 96.0, 1.01]
def test_distance():
actual = shapely.distance(*point_polygon_testdata)
expected = [2 * 2**0.5, 2**0.5, 0, 0, 0, 2**0.5]
np.testing.assert_allclose(actual, expected)
def test_distance_missing():
actual = shapely.distance(point, None)
assert np.isnan(actual)
def test_distance_duplicated():
a = Point(1, 2)
b = LineString([(0, 0), (0, 0), (1, 1)])
with ignore_invalid(shapely.geos_version < (3, 12, 0)):
# https://github.com/shapely/shapely/issues/1552
# GEOS < 3.12 raises "invalid" floating point errors
actual = shapely.distance(a, b)
assert actual == 1.0
@pytest.mark.parametrize(
"geom,expected",
[
(point, [2, 3, 2, 3]),
([point, multi_point], [[2, 3, 2, 3], [0, 0, 1, 2]]),
(shapely.linestrings([[0, 0], [0, 1]]), [0, 0, 0, 1]),
(shapely.linestrings([[0, 0], [1, 0]]), [0, 0, 1, 0]),
(multi_point, [0, 0, 1, 2]),
(multi_polygon, [0, 0, 2.2, 2.2]),
(geometry_collection, [49, -1, 52, 2]),
(empty, [np.nan, np.nan, np.nan, np.nan]),
(None, [np.nan, np.nan, np.nan, np.nan]),
],
)
def test_bounds(geom, expected):
assert_array_equal(shapely.bounds(geom), expected)
@pytest.mark.parametrize(
"geom,shape",
[
(point, (4,)),
(None, (4,)),
([point, multi_point], (2, 4)),
([[point, multi_point], [polygon, point]], (2, 2, 4)),
([[[point, multi_point]], [[polygon, point]]], (2, 1, 2, 4)),
],
)
def test_bounds_dimensions(geom, shape):
assert shapely.bounds(geom).shape == shape
@pytest.mark.parametrize(
"geom,expected",
[
(point, [2, 3, 2, 3]),
(shapely.linestrings([[0, 0], [0, 1]]), [0, 0, 0, 1]),
(shapely.linestrings([[0, 0], [1, 0]]), [0, 0, 1, 0]),
(multi_point, [0, 0, 1, 2]),
(multi_polygon, [0, 0, 2.2, 2.2]),
(geometry_collection, [49, -1, 52, 2]),
(empty, [np.nan, np.nan, np.nan, np.nan]),
(None, [np.nan, np.nan, np.nan, np.nan]),
([empty, empty, None], [np.nan, np.nan, np.nan, np.nan]),
# mixed missing and non-missing coordinates
([point, None], [2, 3, 2, 3]),
([point, empty], [2, 3, 2, 3]),
([point, empty, None], [2, 3, 2, 3]),
([point, empty, None, multi_point], [0, 0, 2, 3]),
],
)
def test_total_bounds(geom, expected):
assert_array_equal(shapely.total_bounds(geom), expected)
@pytest.mark.parametrize(
"geom",
[
point,
None,
[point, multi_point],
[[point, multi_point], [polygon, point]],
[[[point, multi_point]], [[polygon, point]]],
],
)
def test_total_bounds_dimensions(geom):
assert shapely.total_bounds(geom).shape == (4,)
def test_length():
actual = shapely.length(
[
point,
line_string,
linear_ring,
polygon,
polygon_with_hole,
multi_point,
multi_polygon,
]
)
assert actual.tolist() == [0.0, 2.0, 4.0, 8.0, 48.0, 0.0, 4.4]
def test_length_missing():
actual = shapely.length(None)
assert np.isnan(actual)
def test_hausdorff_distance():
# example from GEOS docs
a = shapely.linestrings([[0, 0], [100, 0], [10, 100], [10, 100]])
b = shapely.linestrings([[0, 100], [0, 10], [80, 10]])
with ignore_invalid(shapely.geos_version < (3, 12, 0)):
# Hausdorff distance emits "invalid value encountered"
# (see https://github.com/libgeos/geos/issues/515)
actual = shapely.hausdorff_distance(a, b)
assert actual == pytest.approx(22.360679775, abs=1e-7)
def test_hausdorff_distance_densify():
# example from GEOS docs
a = shapely.linestrings([[0, 0], [100, 0], [10, 100], [10, 100]])
b = shapely.linestrings([[0, 100], [0, 10], [80, 10]])
with ignore_invalid(shapely.geos_version < (3, 12, 0)):
# Hausdorff distance emits "invalid value encountered"
# (see https://github.com/libgeos/geos/issues/515)
actual = shapely.hausdorff_distance(a, b, densify=0.001)
assert actual == pytest.approx(47.8, abs=0.1)
def test_hausdorff_distance_missing():
actual = shapely.hausdorff_distance(point, None)
assert np.isnan(actual)
actual = shapely.hausdorff_distance(point, None, densify=0.001)
assert np.isnan(actual)
def test_hausdorff_densify_nan():
actual = shapely.hausdorff_distance(point, point, densify=np.nan)
assert np.isnan(actual)
def test_distance_empty():
actual = shapely.distance(point, empty)
assert np.isnan(actual)
def test_hausdorff_distance_empty():
actual = shapely.hausdorff_distance(point, empty)
assert np.isnan(actual)
def test_hausdorff_distance_densify_empty():
actual = shapely.hausdorff_distance(point, empty, densify=0.2)
assert np.isnan(actual)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
@pytest.mark.parametrize(
"geom1, geom2, expected",
[
# identical geometries should have 0 distance
(
shapely.linestrings([[0, 0], [100, 0]]),
shapely.linestrings([[0, 0], [100, 0]]),
0,
),
# example from GEOS docs
(
shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
shapely.linestrings([[0, 200], [200, 150], [0, 100], [200, 50], [0, 0]]),
200,
),
# same geometries but different curve direction results in maximum
# distance between vertices on the lines.
(
shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
shapely.linestrings([[200, 0], [150, 200], [100, 0], [50, 200], [0, 0]]),
200,
),
# another example from GEOS docs
(
shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
shapely.linestrings([[0, 0], [200, 50], [0, 100], [200, 150], [0, 200]]),
282.842712474619,
),
# example from GEOS tests
(
shapely.linestrings([[0, 0], [100, 0]]),
shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
70.7106781186548,
),
],
)
def test_frechet_distance(geom1, geom2, expected):
actual = shapely.frechet_distance(geom1, geom2)
assert actual == pytest.approx(expected, abs=1e-12)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
@pytest.mark.parametrize(
"geom1, geom2, densify, expected",
[
# example from GEOS tests
(
shapely.linestrings([[0, 0], [100, 0]]),
shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
0.001,
50,
)
],
)
def test_frechet_distance_densify(geom1, geom2, densify, expected):
actual = shapely.frechet_distance(geom1, geom2, densify=densify)
assert actual == pytest.approx(expected, abs=1e-12)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
@pytest.mark.parametrize(
"geom1, geom2",
[
(line_string, None),
(None, line_string),
(None, None),
(line_string, empty),
(empty, line_string),
(empty, empty),
],
)
def test_frechet_distance_nan_for_invalid_geometry_inputs(geom1, geom2):
actual = shapely.frechet_distance(geom1, geom2)
assert np.isnan(actual)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_frechet_densify_ndarray():
actual = shapely.frechet_distance(
shapely.linestrings([[0, 0], [100, 0]]),
shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
densify=[0.1, 0.2, 1],
)
expected = np.array([50, 50.99019514, 70.7106781186548])
np.testing.assert_array_almost_equal(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_frechet_densify_nan():
actual = shapely.frechet_distance(line_string, line_string, densify=np.nan)
assert np.isnan(actual)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
@pytest.mark.parametrize("densify", [0, -1, 2])
def test_frechet_densify_invalid_values(densify):
with pytest.raises(shapely.GEOSException, match="Fraction is not in range"):
shapely.frechet_distance(line_string, line_string, densify=densify)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
def test_frechet_distance_densify_empty():
actual = shapely.frechet_distance(line_string, empty, densify=0.2)
assert np.isnan(actual)
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_minimum_clearance():
actual = shapely.minimum_clearance([polygon, polygon_with_hole, multi_polygon])
assert_allclose(actual, [2.0, 2.0, 0.1])
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_minimum_clearance_nonexistent():
actual = shapely.minimum_clearance([point, empty])
assert np.isinf(actual).all()
@pytest.mark.skipif(shapely.geos_version < (3, 6, 0), reason="GEOS < 3.6")
def test_minimum_clearance_missing():
actual = shapely.minimum_clearance(None)
assert np.isnan(actual)
@pytest.mark.skipif(shapely.geos_version < (3, 8, 0), reason="GEOS < 3.8")
@pytest.mark.parametrize(
"geometry, expected",
[
(
Polygon([(0, 5), (5, 10), (10, 5), (5, 0), (0, 5)]),
5,
),
(
LineString([(1, 0), (1, 10)]),
5,
),
(
MultiPoint([(2, 2), (4, 2)]),
1,
),
(
Point(2, 2),
0,
),
(
GeometryCollection(),
0,
),
],
)
def test_minimum_bounding_radius(geometry, expected):
actual = shapely.minimum_bounding_radius(geometry)
assert actual == pytest.approx(expected, abs=1e-12)
@@ -0,0 +1,195 @@
import os
import sys
from inspect import cleandoc
from itertools import chain
from string import ascii_letters, digits
from unittest import mock
import numpy as np
import pytest
import shapely
from shapely.decorators import multithreading_enabled, requires_geos
@pytest.fixture
def mocked_geos_version():
with mock.patch.object(shapely.lib, "geos_version", new=(3, 7, 1)):
yield "3.7.1"
@pytest.fixture
def sphinx_doc_build():
os.environ["SPHINX_DOC_BUILD"] = "1"
yield
del os.environ["SPHINX_DOC_BUILD"]
def test_version():
assert isinstance(shapely.__version__, str)
def test_geos_version():
expected = "{}.{}.{}".format(*shapely.geos_version)
actual = shapely.geos_version_string
# strip any beta / dev qualifiers
if any(c.isalpha() for c in actual):
if actual[-1].isnumeric():
actual = actual.rstrip(digits)
actual = actual.rstrip(ascii_letters)
assert actual == expected
@pytest.mark.skipif(
sys.platform.startswith("win")
and (shapely.geos_version == (3, 6, 6) or shapely.geos_version[:2] == (3, 7)),
reason="GEOS_C_API_VERSION broken for GEOS 3.6.6 and 3.7.x on Windows",
)
def test_geos_capi_version():
expected = "{}.{}.{}-CAPI-{}.{}.{}".format(
*(shapely.geos_version + shapely.geos_capi_version)
)
# split into component parts and strip any beta / dev qualifiers
(
actual_geos_version,
actual_geos_api_version,
) = shapely.geos_capi_version_string.split("-CAPI-")
if any(c.isalpha() for c in actual_geos_version):
if actual_geos_version[-1].isnumeric():
actual_geos_version = actual_geos_version.rstrip(digits)
actual_geos_version = actual_geos_version.rstrip(ascii_letters)
actual_geos_version = actual_geos_version.rstrip(ascii_letters)
assert f"{actual_geos_version}-CAPI-{actual_geos_api_version}" == expected
def func():
"""Docstring that will be mocked.
A multiline.
Some description.
"""
class SomeClass:
def func(self):
"""Docstring that will be mocked.
A multiline.
Some description.
"""
def expected_docstring(**kwds):
doc = """Docstring that will be mocked.
{indent}A multiline.
{indent}.. note:: 'func' requires at least GEOS {version}.
{indent}Some description.
{indent}""".format(
**kwds
)
if sys.version_info[:2] >= (3, 13):
# There are subtle differences between inspect.cleandoc() and
# _PyCompile_CleanDoc(). Most significantly, the latter does not remove
# leading or trailing blank lines.
return cleandoc(doc) + "\n"
return doc
@pytest.mark.parametrize("version", ["3.7.0", "3.7.1", "3.6.2"])
def test_requires_geos_ok(version, mocked_geos_version):
wrapped = requires_geos(version)(func)
assert wrapped is func
@pytest.mark.parametrize("version", ["3.7.2", "3.8.0", "3.8.1"])
def test_requires_geos_not_ok(version, mocked_geos_version):
wrapped = requires_geos(version)(func)
with pytest.raises(shapely.errors.UnsupportedGEOSVersionError):
wrapped()
assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 4)
@pytest.mark.parametrize("version", ["3.6.0", "3.8.0"])
def test_requires_geos_doc_build(version, mocked_geos_version, sphinx_doc_build):
"""The requires_geos decorator always adapts the docstring."""
wrapped = requires_geos(version)(func)
assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 4)
@pytest.mark.parametrize("version", ["3.6.0", "3.8.0"])
def test_requires_geos_method(version, mocked_geos_version, sphinx_doc_build):
"""The requires_geos decorator adjusts methods docstrings correctly"""
wrapped = requires_geos(version)(SomeClass.func)
assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 8)
@multithreading_enabled
def set_first_element(value, *args, **kwargs):
for arg in chain(args, kwargs.values()):
if hasattr(arg, "__setitem__"):
arg[0] = value
return arg
def test_multithreading_enabled_raises_arg():
arr = np.empty((1,), dtype=object)
# set_first_element cannot change the input array
with pytest.raises(ValueError):
set_first_element(42, arr)
# afterwards, we can
arr[0] = 42
assert arr[0] == 42
def test_multithreading_enabled_raises_kwarg():
arr = np.empty((1,), dtype=object)
# set_first_element cannot change the input array
with pytest.raises(ValueError):
set_first_element(42, arr=arr)
# writable flag goes to original state
assert arr.flags.writeable
def test_multithreading_enabled_preserves_flag():
arr = np.empty((1,), dtype=object)
arr.flags.writeable = False
# set_first_element cannot change the input array
with pytest.raises(ValueError):
set_first_element(42, arr)
# writable flag goes to original state
assert not arr.flags.writeable
@pytest.mark.parametrize(
"args,kwargs",
[
((np.empty((1,), dtype=float),), {}), # float-dtype ndarray is untouched
((), {"a": np.empty((1,), dtype=float)}),
(([1],), {}), # non-ndarray is untouched
((), {"a": [1]}),
((), {"out": np.empty((1,), dtype=object)}), # ufunc kwarg 'out' is untouched
(
(),
{"where": np.empty((1,), dtype=object)},
), # ufunc kwarg 'where' is untouched
],
)
def test_multithreading_enabled_ok(args, kwargs):
result = set_first_element(42, *args, **kwargs)
assert result[0] == 42
@@ -0,0 +1,103 @@
import pytest
from numpy.testing import assert_allclose
from shapely import box, get_coordinates, LineString, MultiLineString, Point
from shapely.plotting import patch_from_polygon, plot_line, plot_points, plot_polygon
pytest.importorskip("matplotlib")
def test_patch_from_polygon():
poly = box(0, 0, 1, 1)
artist = patch_from_polygon(poly, facecolor="red", edgecolor="blue", linewidth=3)
assert equal_color(artist.get_facecolor(), "red")
assert equal_color(artist.get_edgecolor(), "blue")
assert artist.get_linewidth() == 3
def test_patch_from_polygon_with_interior():
poly = box(0, 0, 1, 1).difference(box(0.2, 0.2, 0.5, 0.5))
artist = patch_from_polygon(poly, facecolor="red", edgecolor="blue", linewidth=3)
assert equal_color(artist.get_facecolor(), "red")
assert equal_color(artist.get_edgecolor(), "blue")
assert artist.get_linewidth() == 3
def test_patch_from_multipolygon():
poly = box(0, 0, 1, 1).union(box(2, 2, 3, 3))
artist = patch_from_polygon(poly, facecolor="red", edgecolor="blue", linewidth=3)
assert equal_color(artist.get_facecolor(), "red")
assert equal_color(artist.get_edgecolor(), "blue")
assert artist.get_linewidth() == 3
def test_plot_polygon():
poly = box(0, 0, 1, 1)
artist, _ = plot_polygon(poly)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(poly))
# overriding default styling
artist = plot_polygon(poly, add_points=False, color="red", linewidth=3)
assert equal_color(artist.get_facecolor(), "red", alpha=0.3)
assert equal_color(artist.get_edgecolor(), "red", alpha=1.0)
assert artist.get_linewidth() == 3
def test_plot_polygon_with_interior():
poly = box(0, 0, 1, 1).difference(box(0.2, 0.2, 0.5, 0.5))
artist, _ = plot_polygon(poly)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(poly))
def test_plot_multipolygon():
poly = box(0, 0, 1, 1).union(box(2, 2, 3, 3))
artist, _ = plot_polygon(poly)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(poly))
def test_plot_line():
line = LineString([(0, 0), (1, 0), (1, 1)])
artist, _ = plot_line(line)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(line))
# overriding default styling
artist = plot_line(line, add_points=False, color="red", linewidth=3)
assert equal_color(artist.get_edgecolor(), "red")
assert equal_color(artist.get_facecolor(), "none")
assert artist.get_linewidth() == 3
def test_plot_multilinestring():
line = MultiLineString(
[LineString([(0, 0), (1, 0), (1, 1)]), LineString([(2, 2), (3, 3)])]
)
artist, _ = plot_line(line)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(line))
def test_plot_points():
for geom in [Point(0, 0), LineString([(0, 0), (1, 0), (1, 1)]), box(0, 0, 1, 1)]:
artist = plot_points(geom)
plot_coords = artist.get_path().vertices
assert_allclose(plot_coords, get_coordinates(geom))
assert artist.get_linestyle() == "None"
# overriding default styling
geom = Point(0, 0)
artist = plot_points(geom, color="red", marker="+", fillstyle="top")
assert artist.get_color() == "red"
assert artist.get_marker() == "+"
assert artist.get_fillstyle() == "top"
def equal_color(actual, expected, alpha=None):
import matplotlib.colors as colors
conv = colors.colorConverter
return actual == conv.to_rgba(expected, alpha=alpha)
@@ -0,0 +1,331 @@
from functools import partial
import numpy as np
import pytest
import shapely
from shapely import LinearRing, LineString, Point
from shapely.tests.common import (
all_types,
empty,
geometry_collection,
ignore_invalid,
line_string,
linear_ring,
point,
polygon,
)
UNARY_PREDICATES = (
shapely.is_empty,
shapely.is_simple,
shapely.is_ring,
shapely.is_closed,
shapely.is_valid,
shapely.is_missing,
shapely.is_geometry,
shapely.is_valid_input,
shapely.is_prepared,
pytest.param(
shapely.is_ccw,
marks=pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7"),
),
)
BINARY_PREDICATES = (
shapely.disjoint,
shapely.touches,
shapely.intersects,
shapely.crosses,
shapely.within,
shapely.contains,
shapely.contains_properly,
shapely.overlaps,
shapely.covers,
shapely.covered_by,
pytest.param(
partial(shapely.dwithin, distance=1.0),
marks=pytest.mark.skipif(
shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10"
),
),
shapely.equals,
shapely.equals_exact,
)
BINARY_PREPARED_PREDICATES = BINARY_PREDICATES[:-2]
XY_PREDICATES = (
(shapely.contains_xy, shapely.contains),
(shapely.intersects_xy, shapely.intersects),
)
@pytest.mark.parametrize("geometry", all_types)
@pytest.mark.parametrize("func", UNARY_PREDICATES)
def test_unary_array(geometry, func):
actual = func([geometry, geometry])
assert actual.shape == (2,)
assert actual.dtype == np.bool_
@pytest.mark.parametrize("func", UNARY_PREDICATES)
def test_unary_with_kwargs(func):
out = np.empty((), dtype=np.uint8)
actual = func(point, out=out)
assert actual is out
assert actual.dtype == np.uint8
@pytest.mark.parametrize("func", UNARY_PREDICATES)
def test_unary_missing(func):
if func in (shapely.is_valid_input, shapely.is_missing):
assert func(None)
else:
assert not func(None)
@pytest.mark.parametrize("a", all_types)
@pytest.mark.parametrize("func", BINARY_PREDICATES)
def test_binary_array(a, func):
with ignore_invalid(shapely.is_empty(a) and shapely.geos_version < (3, 12, 0)):
# Empty geometries give 'invalid value encountered' in all predicates
# (see https://github.com/libgeos/geos/issues/515)
actual = func([a, a], point)
assert actual.shape == (2,)
assert actual.dtype == np.bool_
@pytest.mark.parametrize("func", BINARY_PREDICATES)
def test_binary_with_kwargs(func):
out = np.empty((), dtype=np.uint8)
actual = func(point, point, out=out)
assert actual is out
assert actual.dtype == np.uint8
@pytest.mark.parametrize("func", BINARY_PREDICATES)
def test_binary_missing(func):
actual = func(np.array([point, None, None]), np.array([None, point, None]))
assert (~actual).all()
def test_binary_empty_result():
a = LineString([(0, 0), (3, 0), (3, 3), (0, 3)])
b = LineString([(5, 1), (6, 1)])
with ignore_invalid(shapely.geos_version < (3, 12, 0)):
# Intersection resulting in empty geometries give 'invalid value encountered'
# (https://github.com/shapely/shapely/issues/1345)
assert shapely.intersection(a, b).is_empty
@pytest.mark.parametrize("a", all_types)
@pytest.mark.parametrize("func, func_bin", XY_PREDICATES)
def test_xy_array(a, func, func_bin):
with ignore_invalid(shapely.is_empty(a) and shapely.geos_version < (3, 12, 0)):
# Empty geometries give 'invalid value encountered' in all predicates
# (see https://github.com/libgeos/geos/issues/515)
actual = func([a, a], 2, 3)
expected = func_bin([a, a], Point(2, 3))
assert actual.shape == (2,)
assert actual.dtype == np.bool_
np.testing.assert_allclose(actual, expected)
@pytest.mark.parametrize("a", all_types)
@pytest.mark.parametrize("func, func_bin", XY_PREDICATES)
def test_xy_array_broadcast(a, func, func_bin):
with ignore_invalid(shapely.is_empty(a) and shapely.geos_version < (3, 12, 0)):
# Empty geometries give 'invalid value encountered' in all predicates
# (see https://github.com/libgeos/geos/issues/515)
actual = func(a, [0, 1, 2], [1, 2, 3])
expected = func_bin(a, [Point(0, 1), Point(1, 2), Point(2, 3)])
np.testing.assert_allclose(actual, expected)
@pytest.mark.parametrize("func", [funcs[0] for funcs in XY_PREDICATES])
def test_xy_array_2D(func):
actual = func(polygon, [0, 1, 2], [1, 2, 3])
expected = func(polygon, [[0, 1], [1, 2], [2, 3]])
np.testing.assert_allclose(actual, expected)
@pytest.mark.parametrize("func, func_bin", XY_PREDICATES)
def test_xy_prepared(func, func_bin):
actual = func(_prepare_with_copy([polygon, line_string]), 2, 3)
expected = func_bin([polygon, line_string], Point(2, 3))
np.testing.assert_allclose(actual, expected)
@pytest.mark.parametrize("func", [funcs[0] for funcs in XY_PREDICATES])
def test_xy_with_kwargs(func):
out = np.empty((), dtype=np.uint8)
actual = func(point, point.x, point.y, out=out)
assert actual is out
assert actual.dtype == np.uint8
@pytest.mark.parametrize("func", [funcs[0] for funcs in XY_PREDICATES])
def test_xy_missing(func):
actual = func(
np.array([point, point, point, None]),
np.array([point.x, np.nan, point.x, point.x]),
np.array([point.y, point.y, np.nan, point.y]),
)
np.testing.assert_allclose(actual, [True, False, False, False])
def test_equals_exact_tolerance():
# specifying tolerance
p1 = shapely.points(50, 4)
p2 = shapely.points(50.1, 4.1)
actual = shapely.equals_exact([p1, p2, None], p1, tolerance=0.05)
np.testing.assert_allclose(actual, [True, False, False])
assert actual.dtype == np.bool_
actual = shapely.equals_exact([p1, p2, None], p1, tolerance=0.2)
np.testing.assert_allclose(actual, [True, True, False])
assert actual.dtype == np.bool_
# default value for tolerance
assert shapely.equals_exact(p1, p1).item() is True
assert shapely.equals_exact(p1, p2).item() is False
# an array of tolerances
actual = shapely.equals_exact(p1, p2, tolerance=[0.05, 0.2, np.nan])
np.testing.assert_allclose(actual, [False, True, False])
@pytest.mark.skipif(shapely.geos_version < (3, 10, 0), reason="GEOS < 3.10")
def test_dwithin():
p1 = shapely.points(50, 4)
p2 = shapely.points(50.1, 4.1)
actual = shapely.dwithin([p1, p2, None], p1, distance=0.05)
np.testing.assert_equal(actual, [True, False, False])
assert actual.dtype == np.bool_
actual = shapely.dwithin([p1, p2, None], p1, distance=0.2)
np.testing.assert_allclose(actual, [True, True, False])
assert actual.dtype == np.bool_
# an array of distances
actual = shapely.dwithin(p1, p2, distance=[0.05, 0.2, np.nan])
np.testing.assert_allclose(actual, [False, True, False])
@pytest.mark.parametrize(
"geometry,expected",
[
(point, False),
(line_string, False),
(linear_ring, True),
(empty, False),
],
)
def test_is_closed(geometry, expected):
assert shapely.is_closed(geometry) == expected
def test_relate():
p1 = shapely.points(0, 0)
p2 = shapely.points(1, 1)
actual = shapely.relate(p1, p2)
assert isinstance(actual, str)
assert actual == "FF0FFF0F2"
@pytest.mark.parametrize("g1, g2", [(point, None), (None, point), (None, None)])
def test_relate_none(g1, g2):
assert shapely.relate(g1, g2) is None
def test_relate_pattern():
g = shapely.linestrings([(0, 0), (1, 0), (1, 1)])
polygon = shapely.box(0, 0, 2, 2)
assert shapely.relate(g, polygon) == "11F00F212"
assert shapely.relate_pattern(g, polygon, "11F00F212")
assert shapely.relate_pattern(g, polygon, "*********")
assert not shapely.relate_pattern(g, polygon, "F********")
def test_relate_pattern_empty():
with ignore_invalid(shapely.geos_version < (3, 12, 0)):
# Empty geometries give 'invalid value encountered' in all predicates
# (see https://github.com/libgeos/geos/issues/515)
assert shapely.relate_pattern(empty, empty, "*" * 9).item() is True
@pytest.mark.parametrize("g1, g2", [(point, None), (None, point), (None, None)])
def test_relate_pattern_none(g1, g2):
assert shapely.relate_pattern(g1, g2, "*" * 9).item() is False
def test_relate_pattern_incorrect_length():
with pytest.raises(shapely.GEOSException, match="Should be length 9"):
shapely.relate_pattern(point, polygon, "**")
with pytest.raises(shapely.GEOSException, match="Should be length 9"):
shapely.relate_pattern(point, polygon, "**********")
@pytest.mark.parametrize("pattern", [b"*********", 10, None])
def test_relate_pattern_non_string(pattern):
with pytest.raises(TypeError, match="expected string"):
shapely.relate_pattern(point, polygon, pattern)
def test_relate_pattern_non_scalar():
with pytest.raises(ValueError, match="only supports scalar"):
shapely.relate_pattern([point] * 2, polygon, ["*********"] * 2)
@pytest.mark.skipif(shapely.geos_version < (3, 7, 0), reason="GEOS < 3.7")
@pytest.mark.parametrize(
"geom, expected",
[
(LinearRing([(0, 0), (0, 1), (1, 1), (0, 0)]), False),
(LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)]), True),
(LineString([(0, 0), (0, 1), (1, 1), (0, 0)]), False),
(LineString([(0, 0), (1, 1), (0, 1), (0, 0)]), True),
(LineString([(0, 0), (1, 1), (0, 1)]), False),
(LineString([(0, 0), (0, 1), (1, 1)]), False),
(point, False),
(polygon, False),
(geometry_collection, False),
(None, False),
],
)
def test_is_ccw(geom, expected):
assert shapely.is_ccw(geom) == expected
def _prepare_with_copy(geometry):
"""Prepare without modifying in-place"""
geometry = shapely.transform(geometry, lambda x: x) # makes a copy
shapely.prepare(geometry)
return geometry
@pytest.mark.parametrize("a", all_types)
@pytest.mark.parametrize("func", BINARY_PREPARED_PREDICATES)
def test_binary_prepared(a, func):
with ignore_invalid(shapely.is_empty(a) and shapely.geos_version < (3, 12, 0)):
# Empty geometries give 'invalid value encountered' in all predicates
# (see https://github.com/libgeos/geos/issues/515)
actual = func(a, point)
result = func(_prepare_with_copy(a), point)
assert actual == result
@pytest.mark.parametrize("geometry", all_types + (empty,))
def test_is_prepared_true(geometry):
assert shapely.is_prepared(_prepare_with_copy(geometry))
@pytest.mark.parametrize("geometry", all_types + (empty, None))
def test_is_prepared_false(geometry):
assert not shapely.is_prepared(geometry)
def test_contains_properly():
# polygon contains itself, but does not properly contains itself
assert shapely.contains(polygon, polygon).item() is True
assert shapely.contains_properly(polygon, polygon).item() is False
@@ -0,0 +1,399 @@
import numpy as np
import pytest
from numpy.testing import assert_allclose
import shapely
from shapely import MultiLineString, MultiPoint, MultiPolygon
from shapely.testing import assert_geometries_equal
from shapely.tests.common import (
empty_line_string,
empty_line_string_z,
geometry_collection,
line_string,
line_string_z,
linear_ring,
multi_line_string,
multi_line_string_z,
multi_point,
multi_point_z,
multi_polygon,
multi_polygon_z,
point,
point_z,
polygon,
polygon_z,
)
all_types = (
point,
line_string,
polygon,
multi_point,
multi_line_string,
multi_polygon,
)
all_types_3d = (
point_z,
line_string_z,
polygon_z,
multi_point_z,
multi_line_string_z,
multi_polygon_z,
)
all_types_not_supported = (
linear_ring,
geometry_collection,
)
@pytest.mark.parametrize("geom", all_types + all_types_3d)
def test_roundtrip(geom):
actual = shapely.from_ragged_array(*shapely.to_ragged_array([geom, geom]))
assert_geometries_equal(actual, [geom, geom])
@pytest.mark.parametrize("geom", all_types)
def test_include_z(geom):
_, coords, _ = shapely.to_ragged_array([geom, geom], include_z=True)
# For 2D geoms, z coords are filled in with NaN
assert np.isnan(coords[:, 2]).all()
@pytest.mark.parametrize("geom", all_types_3d)
def test_include_z_false(geom):
_, coords, _ = shapely.to_ragged_array([geom, geom], include_z=False)
# For 3D geoms, z coords are dropped
assert coords.shape[1] == 2
def test_include_z_default():
# corner cases for inferring dimensionality
# mixed 2D and 3D -> 3D
_, coords, _ = shapely.to_ragged_array([line_string, line_string_z])
assert coords.shape[1] == 3
# only empties -> always 2D
_, coords, _ = shapely.to_ragged_array([empty_line_string])
assert coords.shape[1] == 2
_, coords, _ = shapely.to_ragged_array([empty_line_string_z])
assert coords.shape[1] == 2
# empty collection -> GEOS indicates 2D
_, coords, _ = shapely.to_ragged_array(shapely.from_wkt(["MULTIPOLYGON Z EMPTY"]))
assert coords.shape[1] == 2
@pytest.mark.parametrize("geom", all_types)
def test_read_only_arrays(geom):
# https://github.com/shapely/shapely/pull/1744
typ, coords, offsets = shapely.to_ragged_array([geom, geom])
coords.flags.writeable = False
for arr in offsets:
arr.flags.writeable = False
result = shapely.from_ragged_array(typ, coords, offsets)
assert_geometries_equal(result, [geom, geom])
@pytest.mark.parametrize("geom", all_types_not_supported)
def test_raise_geometry_type(geom):
with pytest.raises(ValueError):
shapely.to_ragged_array([geom, geom])
def test_points():
arr = shapely.from_wkt(
[
"POINT (0 0)",
"POINT (1 1)",
"POINT EMPTY",
"POINT EMPTY",
"POINT (4 4)",
None,
"POINT EMPTY",
]
)
typ, result, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[0, 0],
[1, 1],
[np.nan, np.nan],
[np.nan, np.nan],
[4, 4],
[np.nan, np.nan],
[np.nan, np.nan],
]
)
assert typ == shapely.GeometryType.POINT
assert len(result) == len(arr)
assert_allclose(result, expected)
assert len(offsets) == 0
geoms = shapely.from_ragged_array(typ, result)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("POINT EMPTY")
assert_geometries_equal(geoms, arr)
def test_linestrings():
arr = shapely.from_wkt(
[
"LINESTRING (30 10, 10 30, 40 40)",
"LINESTRING (40 40, 30 30, 40 20, 30 10)",
"LINESTRING EMPTY",
"LINESTRING EMPTY",
"LINESTRING (10 10, 20 20, 10 40)",
None,
"LINESTRING EMPTY",
]
)
typ, coords, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[30.0, 10.0],
[10.0, 30.0],
[40.0, 40.0],
[40.0, 40.0],
[30.0, 30.0],
[40.0, 20.0],
[30.0, 10.0],
[10.0, 10.0],
[20.0, 20.0],
[10.0, 40.0],
]
)
expected_offsets = np.array([0, 3, 7, 7, 7, 10, 10, 10])
assert typ == shapely.GeometryType.LINESTRING
assert_allclose(coords, expected)
assert len(offsets) == 1
assert_allclose(offsets[0], expected_offsets)
result = shapely.from_ragged_array(typ, coords, offsets)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("LINESTRING EMPTY")
assert_geometries_equal(result, arr)
def test_polygons():
arr = shapely.from_wkt(
[
"POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))",
"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))",
"POLYGON EMPTY",
"POLYGON EMPTY",
"POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))",
None,
"POLYGON EMPTY",
]
)
typ, coords, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[30.0, 10.0],
[40.0, 40.0],
[20.0, 40.0],
[10.0, 20.0],
[30.0, 10.0],
[35.0, 10.0],
[45.0, 45.0],
[15.0, 40.0],
[10.0, 20.0],
[35.0, 10.0],
[20.0, 30.0],
[35.0, 35.0],
[30.0, 20.0],
[20.0, 30.0],
[30.0, 10.0],
[40.0, 40.0],
[20.0, 40.0],
[10.0, 20.0],
[30.0, 10.0],
]
)
expected_offsets1 = np.array([0, 5, 10, 14, 19])
expected_offsets2 = np.array([0, 1, 3, 3, 3, 4, 4, 4])
assert typ == shapely.GeometryType.POLYGON
assert_allclose(coords, expected)
assert len(offsets) == 2
assert_allclose(offsets[0], expected_offsets1)
assert_allclose(offsets[1], expected_offsets2)
result = shapely.from_ragged_array(typ, coords, offsets)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("POLYGON EMPTY")
assert_geometries_equal(result, arr)
def test_multipoints():
arr = shapely.from_wkt(
[
"MULTIPOINT (10 40, 40 30, 20 20, 30 10)",
"MULTIPOINT (30 10)",
"MULTIPOINT EMPTY",
"MULTIPOINT EMPTY",
"MULTIPOINT (30 10, 10 30, 40 40)",
None,
"MULTIPOINT EMPTY",
]
)
typ, coords, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[10.0, 40.0],
[40.0, 30.0],
[20.0, 20.0],
[30.0, 10.0],
[30.0, 10.0],
[30.0, 10.0],
[10.0, 30.0],
[40.0, 40.0],
]
)
expected_offsets = np.array([0, 4, 5, 5, 5, 8, 8, 8])
assert typ == shapely.GeometryType.MULTIPOINT
assert_allclose(coords, expected)
assert len(offsets) == 1
assert_allclose(offsets[0], expected_offsets)
result = shapely.from_ragged_array(typ, coords, offsets)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("MULTIPOINT EMPTY")
assert_geometries_equal(result, arr)
def test_multilinestrings():
arr = shapely.from_wkt(
[
"MULTILINESTRING ((30 10, 10 30, 40 40))",
"MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))",
"MULTILINESTRING EMPTY",
"MULTILINESTRING EMPTY",
"MULTILINESTRING ((35 10, 45 45), (15 40, 10 20), (30 10, 10 30, 40 40))",
None,
"MULTILINESTRING EMPTY",
]
)
typ, coords, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[30.0, 10.0],
[10.0, 30.0],
[40.0, 40.0],
[10.0, 10.0],
[20.0, 20.0],
[10.0, 40.0],
[40.0, 40.0],
[30.0, 30.0],
[40.0, 20.0],
[30.0, 10.0],
[35.0, 10.0],
[45.0, 45.0],
[15.0, 40.0],
[10.0, 20.0],
[30.0, 10.0],
[10.0, 30.0],
[40.0, 40.0],
]
)
expected_offsets1 = np.array([0, 3, 6, 10, 12, 14, 17])
expected_offsets2 = np.array([0, 1, 3, 3, 3, 6, 6, 6])
assert typ == shapely.GeometryType.MULTILINESTRING
assert_allclose(coords, expected)
assert len(offsets) == 2
assert_allclose(offsets[0], expected_offsets1)
assert_allclose(offsets[1], expected_offsets2)
result = shapely.from_ragged_array(typ, coords, offsets)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("MULTILINESTRING EMPTY")
assert_geometries_equal(result, arr)
def test_multipolygons():
arr = shapely.from_wkt(
[
"MULTIPOLYGON (((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30)))",
"MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))",
"MULTIPOLYGON EMPTY",
"MULTIPOLYGON EMPTY",
"MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)))",
None,
"MULTIPOLYGON EMPTY",
]
)
typ, coords, offsets = shapely.to_ragged_array(arr)
expected = np.array(
[
[35.0, 10.0],
[45.0, 45.0],
[15.0, 40.0],
[10.0, 20.0],
[35.0, 10.0],
[20.0, 30.0],
[35.0, 35.0],
[30.0, 20.0],
[20.0, 30.0],
[40.0, 40.0],
[20.0, 45.0],
[45.0, 30.0],
[40.0, 40.0],
[20.0, 35.0],
[10.0, 30.0],
[10.0, 10.0],
[30.0, 5.0],
[45.0, 20.0],
[20.0, 35.0],
[30.0, 20.0],
[20.0, 15.0],
[20.0, 25.0],
[30.0, 20.0],
[40.0, 40.0],
[20.0, 45.0],
[45.0, 30.0],
[40.0, 40.0],
]
)
expected_offsets1 = np.array([0, 5, 9, 13, 19, 23, 27])
expected_offsets2 = np.array([0, 2, 3, 5, 6])
expected_offsets3 = np.array([0, 1, 3, 3, 3, 4, 4, 4])
assert typ == shapely.GeometryType.MULTIPOLYGON
assert_allclose(coords, expected)
assert len(offsets) == 3
assert_allclose(offsets[0], expected_offsets1)
assert_allclose(offsets[1], expected_offsets2)
assert_allclose(offsets[2], expected_offsets3)
result = shapely.from_ragged_array(typ, coords, offsets)
# in a roundtrip, missing geometries come back as empty
arr[-2] = shapely.from_wkt("MULTIPOLYGON EMPTY")
assert_geometries_equal(result, arr)
def test_mixture_point_multipoint():
typ, coords, offsets = shapely.to_ragged_array([point, multi_point])
assert typ == shapely.GeometryType.MULTIPOINT
result = shapely.from_ragged_array(typ, coords, offsets)
expected = np.array([MultiPoint([point]), multi_point])
assert_geometries_equal(result, expected)
def test_mixture_linestring_multilinestring():
typ, coords, offsets = shapely.to_ragged_array([line_string, multi_line_string])
assert typ == shapely.GeometryType.MULTILINESTRING
result = shapely.from_ragged_array(typ, coords, offsets)
expected = np.array([MultiLineString([line_string]), multi_line_string])
assert_geometries_equal(result, expected)
def test_mixture_polygon_multipolygon():
typ, coords, offsets = shapely.to_ragged_array([polygon, multi_polygon])
assert typ == shapely.GeometryType.MULTIPOLYGON
result = shapely.from_ragged_array(typ, coords, offsets)
expected = np.array([MultiPolygon([polygon]), multi_polygon])
assert_geometries_equal(result, expected)
@@ -0,0 +1,444 @@
import numpy as np
import pytest
import shapely
from shapely import Geometry, GeometryCollection, Polygon
from shapely.errors import UnsupportedGEOSVersionError
from shapely.testing import assert_geometries_equal
from shapely.tests.common import (
all_types,
empty,
ignore_invalid,
multi_polygon,
point,
polygon,
)
# fixed-precision operations raise GEOS exceptions on mixed dimension geometry collections
all_single_types = [g for g in all_types if not shapely.get_type_id(g) == 7]
SET_OPERATIONS = (
shapely.difference,
shapely.intersection,
shapely.symmetric_difference,
shapely.union,
# shapely.coverage_union is tested separately
)
REDUCE_SET_OPERATIONS = (
(shapely.intersection_all, shapely.intersection),
(shapely.symmetric_difference_all, shapely.symmetric_difference),
(shapely.union_all, shapely.union),
# shapely.coverage_union_all, shapely.coverage_union) is tested separately
)
# operations that support fixed precision
REDUCE_SET_OPERATIONS_PREC = ((shapely.union_all, shapely.union),)
reduce_test_data = [
shapely.box(0, 0, 5, 5),
shapely.box(2, 2, 7, 7),
shapely.box(4, 4, 9, 9),
shapely.box(5, 5, 10, 10),
]
non_polygon_types = [
geom
for geom in all_types
if (not shapely.is_empty(geom) and geom not in (polygon, multi_polygon))
]
@pytest.mark.parametrize("a", all_types)
@pytest.mark.parametrize("func", SET_OPERATIONS)
def test_set_operation_array(a, func):
if (
func is shapely.difference
and a.geom_type == "GeometryCollection"
and shapely.get_num_geometries(a) == 2
and shapely.geos_version == (3, 9, 5)
):
pytest.xfail("GEOS 3.9.5 crashes with mixed collection")
actual = func(a, point)
assert isinstance(actual, Geometry)
actual = func([a, a], point)
assert actual.shape == (2,)
assert isinstance(actual[0], Geometry)
@pytest.mark.skipif(shapely.geos_version >= (3, 9, 0), reason="GEOS >= 3.9")
@pytest.mark.parametrize("func", SET_OPERATIONS)
@pytest.mark.parametrize("grid_size", [0, 1])
def test_set_operations_prec_not_supported(func, grid_size):
with pytest.raises(
UnsupportedGEOSVersionError, match="grid_size parameter requires GEOS >= 3.9.0"
):
func(point, point, grid_size)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("func", SET_OPERATIONS)
def test_set_operation_prec_nonscalar_grid_size(func):
with pytest.raises(
ValueError, match="grid_size parameter only accepts scalar values"
):
func(point, point, grid_size=[1])
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("a", all_single_types)
@pytest.mark.parametrize("func", SET_OPERATIONS)
@pytest.mark.parametrize("grid_size", [0, 1, 2])
def test_set_operation_prec_array(a, func, grid_size):
actual = func([a, a], point, grid_size=grid_size)
assert actual.shape == (2,)
assert isinstance(actual[0], Geometry)
# results should match the operation when the precision is previously set
# to same grid_size
b = shapely.set_precision(a, grid_size=grid_size)
point2 = shapely.set_precision(point, grid_size=grid_size)
expected = func([b, b], point2)
assert shapely.equals(shapely.normalize(actual), shapely.normalize(expected)).all()
@pytest.mark.parametrize("n", range(1, 5))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_1dim(n, func, related_func):
actual = func(reduce_test_data[:n])
# perform the reduction in a python loop and compare
expected = reduce_test_data[0]
for i in range(1, n):
expected = related_func(expected, reduce_test_data[i])
assert shapely.equals(actual, expected)
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_single_geom(func, related_func):
geom = shapely.Point(1, 1)
actual = func([geom, None, None])
assert shapely.equals(actual, geom)
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_axis(func, related_func):
data = [[point] * 2] * 3 # shape = (3, 2)
actual = func(data, axis=None) # default
assert isinstance(actual, Geometry) # scalar output
actual = func(data, axis=0)
assert actual.shape == (2,)
actual = func(data, axis=1)
assert actual.shape == (3,)
actual = func(data, axis=-1)
assert actual.shape == (3,)
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_empty(func, related_func):
assert func(np.empty((0,), dtype=object)) == empty
arr_empty_2D = np.empty((0, 2), dtype=object)
assert func(arr_empty_2D) == empty
assert func(arr_empty_2D, axis=0).tolist() == [empty] * 2
assert func(arr_empty_2D, axis=1).tolist() == []
@pytest.mark.parametrize("none_position", range(3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_one_none(func, related_func, none_position):
# API change: before, intersection_all and symmetric_difference_all returned
# None if any input geometry was None.
# The new behaviour is to ignore None values.
test_data = reduce_test_data[:2]
test_data.insert(none_position, None)
actual = func(test_data)
expected = related_func(reduce_test_data[0], reduce_test_data[1])
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize("none_position", range(3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_two_none(func, related_func, none_position):
test_data = reduce_test_data[:2]
test_data.insert(none_position, None)
test_data.insert(none_position, None)
actual = func(test_data)
expected = related_func(reduce_test_data[0], reduce_test_data[1])
assert_geometries_equal(actual, expected)
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_some_none_len2(func, related_func):
# in a previous implementation, this would take a different code path
# and return wrong result
assert func([empty, None]) == empty
@pytest.mark.parametrize("n", range(1, 3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_all_none(n, func, related_func):
assert_geometries_equal(func([None] * n), GeometryCollection([]))
@pytest.mark.parametrize("n", range(1, 3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS)
def test_set_operation_reduce_all_none_arr(n, func, related_func):
assert func([[None] * n] * 2, axis=1).tolist() == [empty, empty]
assert func([[None] * 2] * n, axis=0).tolist() == [empty, empty]
@pytest.mark.skipif(shapely.geos_version >= (3, 9, 0), reason="GEOS >= 3.9")
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
@pytest.mark.parametrize("grid_size", [0, 1])
def test_set_operation_prec_reduce_not_supported(func, related_func, grid_size):
with pytest.raises(
UnsupportedGEOSVersionError, match="grid_size parameter requires GEOS >= 3.9.0"
):
func([point, point], grid_size)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_nonscalar_grid_size(func, related_func):
with pytest.raises(
ValueError, match="grid_size parameter only accepts scalar values"
):
func([point, point], grid_size=[1])
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_grid_size_nan(func, related_func):
actual = func([point, point], grid_size=np.nan)
assert actual is None
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("n", range(1, 5))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
@pytest.mark.parametrize("grid_size", [0, 1])
def test_set_operation_prec_reduce_1dim(n, func, related_func, grid_size):
actual = func(reduce_test_data[:n], grid_size=grid_size)
# perform the reduction in a python loop and compare
expected = reduce_test_data[0]
for i in range(1, n):
expected = related_func(expected, reduce_test_data[i], grid_size=grid_size)
assert shapely.equals(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_axis(func, related_func):
data = [[point] * 2] * 3 # shape = (3, 2)
actual = func(data, grid_size=1, axis=None) # default
assert isinstance(actual, Geometry) # scalar output
actual = func(data, grid_size=1, axis=0)
assert actual.shape == (2,)
actual = func(data, grid_size=1, axis=1)
assert actual.shape == (3,)
actual = func(data, grid_size=1, axis=-1)
assert actual.shape == (3,)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("none_position", range(3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_one_none(func, related_func, none_position):
test_data = reduce_test_data[:2]
test_data.insert(none_position, None)
actual = func(test_data, grid_size=1)
expected = related_func(reduce_test_data[0], reduce_test_data[1], grid_size=1)
assert_geometries_equal(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("none_position", range(3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_two_none(func, related_func, none_position):
test_data = reduce_test_data[:2]
test_data.insert(none_position, None)
test_data.insert(none_position, None)
actual = func(test_data, grid_size=1)
expected = related_func(reduce_test_data[0], reduce_test_data[1], grid_size=1)
assert_geometries_equal(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize("n", range(1, 3))
@pytest.mark.parametrize("func, related_func", REDUCE_SET_OPERATIONS_PREC)
def test_set_operation_prec_reduce_all_none(n, func, related_func):
assert_geometries_equal(func([None] * n, grid_size=1), GeometryCollection([]))
@pytest.mark.skipif(shapely.geos_version < (3, 8, 0), reason="GEOS < 3.8")
@pytest.mark.parametrize("n", range(1, 4))
def test_coverage_union_reduce_1dim(n):
"""
This is tested separately from other set operations as it differs in two ways:
1. It expects only non-overlapping polygons
2. It expects GEOS 3.8.0+
"""
test_data = [
shapely.box(0, 0, 1, 1),
shapely.box(1, 0, 2, 1),
shapely.box(2, 0, 3, 1),
]
actual = shapely.coverage_union_all(test_data[:n])
# perform the reduction in a python loop and compare
expected = test_data[0]
for i in range(1, n):
expected = shapely.coverage_union(expected, test_data[i])
assert_geometries_equal(actual, expected, normalize=True)
@pytest.mark.skipif(shapely.geos_version < (3, 8, 0), reason="GEOS < 3.8")
def test_coverage_union_reduce_axis():
# shape = (3, 2), all polygons - none of them overlapping
data = [[shapely.box(i, j, i + 1, j + 1) for i in range(2)] for j in range(3)]
actual = shapely.coverage_union_all(data, axis=None) # default
assert isinstance(actual, Geometry)
actual = shapely.coverage_union_all(data, axis=0)
assert actual.shape == (2,)
actual = shapely.coverage_union_all(data, axis=1)
assert actual.shape == (3,)
actual = shapely.coverage_union_all(data, axis=-1)
assert actual.shape == (3,)
@pytest.mark.skipif(shapely.geos_version < (3, 8, 0), reason="GEOS < 3.8")
def test_coverage_union_overlapping_inputs():
polygon = Polygon([(1, 1), (1, 0), (0, 0), (0, 1), (1, 1)])
other = Polygon([(1, 0), (0.9, 1), (2, 1), (2, 0), (1, 0)])
if shapely.geos_version >= (3, 12, 0):
# Return mostly unchanged output
result = shapely.coverage_union(polygon, other)
expected = shapely.multipolygons([polygon, other])
assert_geometries_equal(result, expected, normalize=True)
else:
# Overlapping polygons raise an error
with pytest.raises(
shapely.GEOSException,
match="CoverageUnion cannot process incorrectly noded inputs.",
):
shapely.coverage_union(polygon, other)
@pytest.mark.skipif(shapely.geos_version < (3, 8, 0), reason="GEOS < 3.8")
@pytest.mark.parametrize(
"geom_1, geom_2",
# All possible polygon, non_polygon combinations
[[polygon, non_polygon] for non_polygon in non_polygon_types]
# All possible non_polygon, non_polygon combinations
+ [
[non_polygon_1, non_polygon_2]
for non_polygon_1 in non_polygon_types
for non_polygon_2 in non_polygon_types
],
)
def test_coverage_union_non_polygon_inputs(geom_1, geom_2):
if shapely.geos_version >= (3, 12, 0):
def effective_geom_types(geom):
if hasattr(geom, "geoms") and not geom.is_empty:
gts = set()
for geom in geom.geoms:
gts |= effective_geom_types(geom)
return gts
return {geom.geom_type.lstrip("Multi").replace("LinearRing", "LineString")}
geom_types_1 = effective_geom_types(geom_1)
geom_types_2 = effective_geom_types(geom_2)
if len(geom_types_1) == 1 and geom_types_1 == geom_types_2:
with ignore_invalid():
# these show "invalid value encountered in coverage_union"
result = shapely.coverage_union(geom_1, geom_2)
assert geom_types_1 == effective_geom_types(result)
else:
with pytest.raises(
shapely.GEOSException, match="Overlay input is mixed-dimension"
):
shapely.coverage_union(geom_1, geom_2)
else:
# Non polygon geometries raise an error
with pytest.raises(
shapely.GEOSException, match="Unhandled geometry type in CoverageUnion."
):
shapely.coverage_union(geom_1, geom_2)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
@pytest.mark.parametrize(
"geom,grid_size,expected",
[
# floating point precision, expect no change
(
[shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)],
0,
Polygon(
(
(0, 0.2),
(0, 10),
(5.1, 10),
(5.1, 0.2),
(5, 0.2),
(5, 0.1),
(0.1, 0.1),
(0.1, 0.2),
(0, 0.2),
)
),
),
# grid_size is at effective precision, expect no change
(
[shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)],
0.1,
Polygon(
(
(0, 0.2),
(0, 10),
(5.1, 10),
(5.1, 0.2),
(5, 0.2),
(5, 0.1),
(0.1, 0.1),
(0.1, 0.2),
(0, 0.2),
)
),
),
# grid_size forces rounding to nearest integer
(
[shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)],
1,
Polygon([(0, 5), (0, 10), (5, 10), (5, 5), (5, 0), (0, 0), (0, 5)]),
),
# grid_size much larger than effective precision causes rounding to nearest
# multiple of 10
(
[shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)],
10,
Polygon([(0, 10), (10, 10), (10, 0), (0, 0), (0, 10)]),
),
# grid_size is so large that polygons collapse to empty
(
[shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)],
100,
Polygon(),
),
],
)
def test_union_all_prec(geom, grid_size, expected):
actual = shapely.union_all(geom, grid_size=grid_size)
assert shapely.equals(actual, expected)
@pytest.mark.skipif(shapely.geos_version < (3, 9, 0), reason="GEOS < 3.9")
def test_uary_union_alias():
geoms = [shapely.box(0.1, 0.1, 5, 5), shapely.box(0, 0.2, 5.1, 10)]
actual = shapely.unary_union(geoms, grid_size=1)
expected = shapely.union_all(geoms, grid_size=1)
assert shapely.equals(actual, expected)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
import numpy as np
import pytest
import shapely
from shapely.testing import assert_geometries_equal
from shapely.tests.common import (
all_types,
empty,
empty_line_string,
empty_line_string_z,
empty_point,
empty_point_z,
empty_polygon,
line_string,
line_string_nan,
line_string_z,
point,
)
EMPTY_GEOMS = (
empty_point,
empty_point_z,
empty_line_string,
empty_line_string_z,
empty_polygon,
empty,
)
line_string_reversed = shapely.linestrings([(0, 0), (1, 0), (1, 1)][::-1])
PRE_GEOS_390 = pytest.mark.skipif(
shapely.geos_version < (3, 9, 0),
reason="2D and 3D empty geometries did not have dimensionality before GEOS 3.9",
)
def make_array(left, right, use_array):
if use_array in ("left", "both"):
left = np.array([left] * 3, dtype=object)
if use_array in ("right", "both"):
right = np.array([right] * 3, dtype=object)
return left, right
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
@pytest.mark.parametrize("geom", all_types + EMPTY_GEOMS)
def test_assert_geometries_equal(geom, use_array):
assert_geometries_equal(*make_array(geom, geom, use_array))
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
@pytest.mark.parametrize(
"geom1,geom2",
[
(point, line_string),
(line_string, line_string_z),
(empty_point, empty_polygon),
pytest.param(empty_point, empty_point_z, marks=PRE_GEOS_390),
pytest.param(empty_line_string, empty_line_string_z, marks=PRE_GEOS_390),
],
)
def test_assert_geometries_not_equal(geom1, geom2, use_array):
with pytest.raises(AssertionError):
assert_geometries_equal(*make_array(geom1, geom2, use_array))
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
def test_assert_none_equal(use_array):
assert_geometries_equal(*make_array(None, None, use_array))
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
def test_assert_none_not_equal(use_array):
with pytest.raises(AssertionError):
assert_geometries_equal(*make_array(None, None, use_array), equal_none=False)
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
def test_assert_nan_equal(use_array):
assert_geometries_equal(*make_array(line_string_nan, line_string_nan, use_array))
@pytest.mark.parametrize("use_array", ["none", "left", "right", "both"])
def test_assert_nan_not_equal(use_array):
with pytest.raises(AssertionError):
assert_geometries_equal(
*make_array(line_string_nan, line_string_nan, use_array), equal_nan=False
)
def test_normalize_true():
assert_geometries_equal(line_string_reversed, line_string, normalize=True)
def test_normalize_default():
with pytest.raises(AssertionError):
assert_geometries_equal(line_string_reversed, line_string)
def test_normalize_false():
with pytest.raises(AssertionError):
assert_geometries_equal(line_string_reversed, line_string, normalize=False)