refactor: excel parse
This commit is contained in:
@@ -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]
|
||||
+78
@@ -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))"
|
||||
)
|
||||
+214
@@ -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"] == ()
|
||||
+28
@@ -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
|
||||
+72
@@ -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
|
||||
+40
@@ -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)
|
||||
Reference in New Issue
Block a user