415 lines
13 KiB
Python
415 lines
13 KiB
Python
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,
|
|
all_types_m,
|
|
all_types_z,
|
|
all_types_zm,
|
|
empty,
|
|
geometry_collection,
|
|
ignore_invalid,
|
|
line_string,
|
|
linear_ring,
|
|
point,
|
|
polygon,
|
|
)
|
|
|
|
UNARY_PREDICATES = (
|
|
shapely.has_z,
|
|
pytest.param(
|
|
shapely.has_m,
|
|
marks=pytest.mark.skipif(
|
|
shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12"
|
|
),
|
|
),
|
|
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,
|
|
shapely.is_ccw,
|
|
)
|
|
|
|
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,
|
|
shapely.equals_identical,
|
|
)
|
|
|
|
BINARY_PREPARED_PREDICATES = BINARY_PREDICATES[:-2]
|
|
|
|
XY_PREDICATES = (
|
|
(shapely.contains_xy, shapely.contains),
|
|
(shapely.intersects_xy, shapely.intersects),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("geometry", all_types + all_types_z)
|
|
@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):
|
|
a2 = shapely.transform(a, lambda x: x) # makes a copy
|
|
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(a2, [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):
|
|
polygon2 = shapely.transform(polygon, lambda x: x) # makes a copy
|
|
actual = func(polygon2, [0, 1, 2], [1, 2, 3])
|
|
expected = func(polygon2, [[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)
|
|
point2 = shapely.transform(point, lambda x: x) # makes a copy
|
|
actual = func(point2, point2.x, point2.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])
|
|
|
|
|
|
def test_equals_exact_normalize():
|
|
l1 = LineString([(0, 0), (1, 1)])
|
|
l2 = LineString([(1, 1), (0, 0)])
|
|
# default requires same order of coordinates
|
|
assert not shapely.equals_exact(l1, l2)
|
|
assert shapely.equals_exact(l1, l2, normalize=True)
|
|
|
|
|
|
def test_equals_identical():
|
|
# more elaborate tests are done at the Geometry.__eq__ level
|
|
# requires same order of coordinates
|
|
l1 = LineString([(0, 0), (1, 1)])
|
|
l2 = LineString([(1, 1), (0, 0)])
|
|
assert not shapely.equals_identical(l1, l2)
|
|
|
|
# checks z-dimension (in contrast to equals_exact)
|
|
l1 = LineString([(0, 0, 0), (1, 1, 0)])
|
|
l2 = LineString([(0, 0, 1), (1, 1, 1)])
|
|
assert not shapely.equals_identical(l1, l2)
|
|
assert shapely.equals_exact(l1, l2)
|
|
|
|
# NaNs in same place are equal (in contrast to equals_exact)
|
|
with ignore_invalid():
|
|
l1 = LineString([(0, np.nan), (1, 1)])
|
|
l2 = LineString([(0, np.nan), (1, 1)])
|
|
assert shapely.equals_identical(l1, l2)
|
|
assert not shapely.equals_exact(l1, l2)
|
|
|
|
|
|
@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", all_types)
|
|
def test_has_z_has_m_all_types(geometry):
|
|
assert not shapely.has_z(geometry)
|
|
if shapely.geos_version >= (3, 12, 0):
|
|
assert not shapely.has_m(geometry)
|
|
|
|
|
|
# The next few tests skip has_z/has_m with empty geometries
|
|
# See https://github.com/libgeos/geos/issues/888
|
|
|
|
|
|
@pytest.mark.parametrize("geometry", all_types_z)
|
|
def test_has_z_has_m_all_types_z(geometry):
|
|
if shapely.is_empty(geometry):
|
|
pytest.skip("GEOSHasZ with EMPTY geometries is inconsistent")
|
|
assert shapely.has_z(geometry)
|
|
if shapely.geos_version >= (3, 12, 0):
|
|
assert not shapely.has_m(geometry)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
shapely.geos_version < (3, 12, 0),
|
|
reason="M coordinates not supported with GEOS < 3.12",
|
|
)
|
|
@pytest.mark.parametrize("geometry", all_types_m)
|
|
def test_has_m_all_types_m(geometry):
|
|
if shapely.is_empty(geometry):
|
|
pytest.skip("GEOSHasM with EMPTY geometries is inconsistent")
|
|
assert not shapely.has_z(geometry)
|
|
assert shapely.has_m(geometry)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
shapely.geos_version < (3, 12, 0),
|
|
reason="M coordinates not supported with GEOS < 3.12",
|
|
)
|
|
@pytest.mark.parametrize("geometry", all_types_zm)
|
|
def test_has_z_has_m_all_types_zm(geometry):
|
|
if shapely.is_empty(geometry):
|
|
pytest.skip("GEOSHasZ with EMPTY geometries is inconsistent")
|
|
assert shapely.has_z(geometry)
|
|
assert shapely.has_m(geometry)
|
|
|
|
|
|
@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.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)
|
|
def test_is_prepared_true(geometry):
|
|
assert shapely.is_prepared(_prepare_with_copy(geometry))
|
|
|
|
|
|
@pytest.mark.parametrize("geometry", all_types + (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
|