"""See test_creation_indices.py for tests with 'indices' parameter."""
import numpy as np
import pytest
from numpy.testing import assert_array_equal
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,
ignore_invalid,
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, handle_nan="allow")
assert actual.wkt == expected_wkt
@pytest.mark.parametrize(
"coords,handle_nan,expected_wkt",
[
([0, np.nan], "skip", "POINT EMPTY"),
([np.nan, 0], "skip", "POINT EMPTY"),
([0, np.nan, 1], "skip", "POINT Z EMPTY"),
([0, 1, np.nan], "skip", "POINT Z EMPTY"),
([0, 1], "error", "POINT (0 1)"),
([0, 1, 2], "error", "POINT Z (0 1 2)"),
([0, np.inf], "skip", "POINT EMPTY"),
],
)
def test_points_handle_nan(coords, handle_nan, expected_wkt):
actual = shapely.points(coords, handle_nan=handle_nan)
assert actual.wkt in expected_wkt
@pytest.mark.parametrize(
"coords",
[
[0, np.nan],
[np.nan, 0],
[0, 0, np.nan],
[0, np.inf],
[0, -np.inf],
],
)
def test_points_handle_nan_error(coords):
with pytest.raises(ValueError, match=".*NaN.*"):
shapely.points(coords, handle_nan="error")
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_empty():
actual = shapely.linestrings(np.empty((0, 2)))
assert actual.is_empty
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)
@pytest.mark.parametrize(
"coords",
[
[[0, 1], [2, float("nan")]],
[[0, 1, float("nan")], [2, 2, float("nan")]],
[[0, 1, 2], [2, 2, float("nan")]],
[[0, 1, float("nan")], [2, 2, 3]],
[[float("nan"), float("nan")], [float("nan"), float("nan")]],
],
)
def test_linestrings_handle_nan_allow(coords):
with ignore_invalid():
actual = shapely.linestrings(coords, handle_nan="allow")
actual = shapely.get_coordinates(actual, include_z=len(coords[0]) == 3)
assert_array_equal(actual, coords)
@pytest.mark.parametrize(
"coords",
[
[[2, float("nan")], [0, 1], [2, 3]],
[[0, 1], [2, float("nan")], [2, 3]],
[[0, 1], [2, 3], [2, float("nan")]],
],
)
def test_linestrings_handle_nan_skip(coords):
actual = shapely.linestrings(coords, handle_nan="skip")
assert_geometries_equal(actual, LineString([(0, 1), (2, 3)]))
def test_linestrings_handle_nan_skip_invalid():
with pytest.raises(shapely.GEOSException):
shapely.linestrings([[0, 1], [2, float("nan")]], handle_nan="skip")
def test_linestrings_handle_nan_skip_only_nan():
# all-nan becomes an empty linestring
actual = shapely.linestrings(np.full((3, 2), fill_value=np.nan), handle_nan="skip")
assert actual.is_empty
def test_linestrings_handle_nan_error():
with pytest.raises(ValueError, match=".*NaN.*"):
shapely.linestrings([[0, 1], [2, float("nan")], [2, 3]], handle_nan="error")
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_empty():
actual = shapely.linearrings(np.empty((0, 2)))
assert actual.is_empty
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.linearrings(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])
@pytest.mark.parametrize(
"coords",
[
[[0, 2], [1, float("nan")], [1, 3], [0, 2]],
[[0, 2], [float("nan"), 0], [1, 3], [0, 2]],
[[0, 2, 5], [float("nan"), 2, 5], [1, 3, 5], [0, 2, 5]],
[[0, 2, 5], [1, 2, float("nan")], [1, 3, 5], [0, 2, 5]],
],
)
def test_linearrings_handle_nan_allow(coords):
with ignore_invalid():
actual = shapely.linearrings(coords, handle_nan="allow")
actual = shapely.get_coordinates(actual, include_z=len(coords[0]) == 3)
assert_array_equal(actual, coords)
@pytest.mark.parametrize(
"x,y",
[
([0, 1, float("nan"), 2, 0], [3, 4, 5, 5, 3]),
([0, 1, 1, 2, 0], [3, 4, float("nan"), 5, 3]),
([0, 1, 2, 0, float("nan")], [3, 4, 5, 3, 3]),
([float("nan"), 0, 1, 2, 0], [3, 3, 4, 5, 3]),
],
)
def test_linearrings_handle_nan_skip(x, y):
actual = shapely.linearrings(x, y, handle_nan="skip")
assert_geometries_equal(actual, LinearRing([(0, 3), (1, 4), (2, 5), (0, 3)]))
def test_linearrings_handle_nan_skip_invalid():
with pytest.raises(ValueError):
shapely.linearrings([0, float("nan"), 0], [3, 4, 3], handle_nan="skip")
def test_linearrings_handle_nan_skip_only_nan():
actual = shapely.linearrings(np.full((5, 2), fill_value=np.nan), handle_nan="skip")
assert actual.is_empty
def test_linearrings_handle_nan_error():
with pytest.raises(ValueError, match=".*NaN.*"):
shapely.linearrings(
[0, 1, float("nan"), 2, 0], [3, 4, 5, 5, 3], handle_nan="error"
)
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_box_deprecate_positional():
with pytest.deprecated_call(
match="positional argument `ccw` for `box` is deprecated"
):
shapely.box(0, 0, 1, 1, True)
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()