structure saas with tools
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
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,308 @@
|
||||
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)
|
||||
|
||||
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,173 @@
|
||||
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_deprecated_resolution():
|
||||
point = geometry.Point(0, 0)
|
||||
with pytest.deprecated_call(match="Use 'quad_segs' instead"):
|
||||
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, Polygon, orient, 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,119 @@
|
||||
"""
|
||||
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, loads as load_wkt
|
||||
|
||||
|
||||
def test_point_outside():
|
||||
"""Point outside"""
|
||||
geom1 = load_wkt("POINT (0 0)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_point_inside():
|
||||
"""Point inside"""
|
||||
geom1 = load_wkt("POINT (15 15)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "POINT (15 15)"
|
||||
|
||||
|
||||
def test_point_on_boundary():
|
||||
"""Point on boundary"""
|
||||
geom1 = load_wkt("POINT (15 10)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_outside():
|
||||
"""Line outside"""
|
||||
geom1 = load_wkt("LINESTRING (0 0, -5 5)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_inside():
|
||||
"""Line inside"""
|
||||
geom1 = load_wkt("LINESTRING (15 15, 16 15)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 15, 16 15)"
|
||||
|
||||
|
||||
def test_line_on_boundary():
|
||||
"""Line on boundary"""
|
||||
geom1 = load_wkt("LINESTRING (10 15, 10 10, 15 10)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_splitting_rectangle():
|
||||
"""Line splitting rectangle"""
|
||||
geom1 = load_wkt("LINESTRING (10 5, 25 20)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 10, 20 15)"
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="TODO issue to CCW")
|
||||
def test_polygon_shell_ccw_fully_on_rectangle_boundary():
|
||||
"""Polygon shell (CCW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt("POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="TODO issue to CW")
|
||||
def test_polygon_shell_cc_fully_on_rectangle_boundary():
|
||||
"""Polygon shell (CW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt("POLYGON ((10 10, 10 20, 20 20, 20 10, 10 10))")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
|
||||
|
||||
def polygon_hole_ccw_fully_on_rectangle_boundary():
|
||||
"""Polygon hole (CCW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt(
|
||||
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def polygon_hole_cw_fully_on_rectangle_boundary():
|
||||
"""Polygon hole (CW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt(
|
||||
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 10 20, 20 20, 20 10, 10 10))"
|
||||
)
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def polygon_fully_within_rectangle():
|
||||
"""Polygon fully within rectangle"""
|
||||
wkt = "POLYGON ((1 1, 1 30, 30 30, 30 1, 1 1), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
geom1 = load_wkt(wkt)
|
||||
geom2 = clip_by_rect(geom1, 0, 0, 40, 40)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == wkt
|
||||
|
||||
|
||||
def polygon_overlapping_rectangle():
|
||||
"""Polygon overlapping rectangle"""
|
||||
wkt = "POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
geom1 = load_wkt(wkt)
|
||||
geom2 = clip_by_rect(geom1, 5, 5, 15, 15)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((5 5, 5 15, 10 15, 10 10, 15 10, 15 5, 5 5))"
|
||||
)
|
||||
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
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
|
||||
|
||||
import shapely
|
||||
from shapely import wkt
|
||||
from shapely.errors import GEOSException
|
||||
from shapely.geometry import LineString, Polygon, shape
|
||||
|
||||
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 shapely.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,24 @@
|
||||
from shapely import Point, Polygon
|
||||
|
||||
|
||||
def test_equals_exact():
|
||||
p1 = Point(1.0, 1.0)
|
||||
p2 = Point(2.0, 2.0)
|
||||
p3 = Point(1.0, 1.0 + 1e-7)
|
||||
assert not p1.equals(p2)
|
||||
assert not p1.equals_exact(p2, 0.001)
|
||||
assert not p1.equals_exact(p3)
|
||||
assert p1.equals_exact(p3, 1e-6)
|
||||
|
||||
# test polygons
|
||||
shell = [(10, 10), (10, -10), (-10, -10), (-10, 10)]
|
||||
holes = [[(1, 1), (1, -1), (-1, -1), (-1, 1)]]
|
||||
p1 = Polygon(shell, holes)
|
||||
p2 = Polygon(shell, holes=[holes[0][::-1]])
|
||||
assert p1.equals(p2)
|
||||
assert not p1.equals_exact(p2, 1e-5)
|
||||
assert p1.equals_exact(p2, 1e-5, normalize=True)
|
||||
|
||||
hole2 = [(1, 1), (1, -1), (-1, -1), (-1, 1.01)]
|
||||
p3 = Polygon(shell, holes=[hole2])
|
||||
assert not p1.equals_exact(p3, 1e-5)
|
||||
@@ -0,0 +1,118 @@
|
||||
import unittest
|
||||
|
||||
from shapely import wkt
|
||||
from shapely.geometry import shape
|
||||
from shapely.geometry.linestring import LineString
|
||||
from shapely.geometry.multilinestring import MultiLineString
|
||||
from shapely.geometry.multipoint import MultiPoint
|
||||
from shapely.geometry.multipolygon import MultiPolygon
|
||||
from shapely.geometry.polygon import LinearRing, Polygon
|
||||
|
||||
|
||||
class GeoThing:
|
||||
def __init__(self, d):
|
||||
self.__geo_interface__ = d
|
||||
|
||||
|
||||
class GeoInterfaceTestCase(unittest.TestCase):
|
||||
def test_geointerface(self):
|
||||
# Convert a dictionary
|
||||
d = {"type": "Point", "coordinates": (0.0, 0.0)}
|
||||
geom = shape(d)
|
||||
assert geom.geom_type == "Point"
|
||||
assert tuple(geom.coords) == ((0.0, 0.0),)
|
||||
|
||||
# Convert an object that implements the geo protocol
|
||||
geom = None
|
||||
thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)})
|
||||
geom = shape(thing)
|
||||
assert geom.geom_type == "Point"
|
||||
assert tuple(geom.coords) == ((0.0, 0.0),)
|
||||
|
||||
# Check line string
|
||||
geom = shape({"type": "LineString", "coordinates": ((-1.0, -1.0), (1.0, 1.0))})
|
||||
assert isinstance(geom, LineString)
|
||||
assert tuple(geom.coords) == ((-1.0, -1.0), (1.0, 1.0))
|
||||
|
||||
# Check linearring
|
||||
geom = shape(
|
||||
{
|
||||
"type": "LinearRing",
|
||||
"coordinates": (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
),
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, LinearRing)
|
||||
assert tuple(geom.coords) == (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
)
|
||||
|
||||
# polygon
|
||||
geom = shape(
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": (
|
||||
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)),
|
||||
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
|
||||
),
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, Polygon)
|
||||
assert tuple(geom.exterior.coords) == (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
)
|
||||
assert len(geom.interiors) == 1
|
||||
|
||||
# multi point
|
||||
geom = shape({"type": "MultiPoint", "coordinates": ((1.0, 2.0), (3.0, 4.0))})
|
||||
assert isinstance(geom, MultiPoint)
|
||||
assert len(geom.geoms) == 2
|
||||
|
||||
# multi line string
|
||||
geom = shape(
|
||||
{"type": "MultiLineString", "coordinates": (((0.0, 0.0), (1.0, 2.0)),)}
|
||||
)
|
||||
assert isinstance(geom, MultiLineString)
|
||||
assert len(geom.geoms) == 1
|
||||
|
||||
# multi polygon
|
||||
geom = shape(
|
||||
{
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": [
|
||||
(
|
||||
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
|
||||
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, MultiPolygon)
|
||||
assert len(geom.geoms) == 1
|
||||
|
||||
|
||||
def test_empty_wkt_polygon():
|
||||
"""Confirm fix for issue #450"""
|
||||
g = wkt.loads("POLYGON EMPTY")
|
||||
assert g.__geo_interface__["type"] == "Polygon"
|
||||
assert g.__geo_interface__["coordinates"] == ()
|
||||
|
||||
|
||||
def test_empty_polygon():
|
||||
"""Confirm fix for issue #450"""
|
||||
g = Polygon()
|
||||
assert g.__geo_interface__["type"] == "Polygon"
|
||||
assert g.__geo_interface__["coordinates"] == ()
|
||||
@@ -0,0 +1,26 @@
|
||||
"""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)
|
||||
@@ -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,43 @@
|
||||
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,54 @@
|
||||
"""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,15 @@
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.validation import make_valid
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 Point, Polygon, mapping
|
||||
|
||||
|
||||
class MappingTestCase(unittest.TestCase):
|
||||
def test_point(self):
|
||||
m = mapping(Point(0, 0))
|
||||
assert m["type"] == "Point"
|
||||
assert m["coordinates"] == (0.0, 0.0)
|
||||
|
||||
def test_empty_polygon(self):
|
||||
"""Empty polygons will round trip without error"""
|
||||
assert mapping(Polygon()) is not None
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Tests for the minimum clearance property.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
|
||||
def test_point():
|
||||
point = load_wkt("POINT (0 0)")
|
||||
assert point.minimum_clearance == math.inf
|
||||
|
||||
|
||||
def test_linestring():
|
||||
line = load_wkt("LINESTRING (0 0, 1 1, 2 2)")
|
||||
assert round(line.minimum_clearance, 6) == 1.414214
|
||||
|
||||
|
||||
def test_simple_polygon():
|
||||
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
|
||||
assert poly.minimum_clearance == 1.0
|
||||
|
||||
|
||||
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,124 @@
|
||||
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, quad_segs=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,92 @@
|
||||
import unittest
|
||||
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
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_empty_polygon(self):
|
||||
polygon = Polygon()
|
||||
assert orient(polygon) == polygon
|
||||
|
||||
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])
|
||||
|
||||
def test_polygon_with_holes(self):
|
||||
ring_cw = LinearRing([(0, 0), (0, 1), (1, 1), (0, 0)])
|
||||
ring_cw2 = LinearRing([(0, 0), (0, 3), (3, 3), (0, 0)])
|
||||
ring_ccw = LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)])
|
||||
ring_ccw2 = LinearRing([(0, 0), (2, 2), (0, 2), (0, 0)])
|
||||
|
||||
polygon_with_holes_mixed = Polygon(
|
||||
ring_ccw, [ring_cw, ring_ccw2, ring_cw2, ring_ccw]
|
||||
)
|
||||
polygon_with_holes_ccw = Polygon(
|
||||
ring_ccw, [ring_cw, ring_ccw2.reverse(), ring_cw2, ring_ccw.reverse()]
|
||||
)
|
||||
|
||||
assert_array_equal(orient(polygon_with_holes_ccw, 1), polygon_with_holes_ccw)
|
||||
assert_array_equal(
|
||||
orient(polygon_with_holes_ccw, -1), polygon_with_holes_ccw.reverse()
|
||||
)
|
||||
assert_array_equal(orient(polygon_with_holes_mixed, 1), polygon_with_holes_ccw)
|
||||
assert_array_equal(
|
||||
orient(polygon_with_holes_mixed, -1), polygon_with_holes_ccw.reverse()
|
||||
)
|
||||
@@ -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,48 @@
|
||||
"""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,81 @@
|
||||
import pathlib
|
||||
import pickle
|
||||
import warnings
|
||||
from pickle import HIGHEST_PROTOCOL, dumps, loads
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely import wkt
|
||||
from shapely.geometry import (
|
||||
GeometryCollection,
|
||||
LinearRing,
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
box,
|
||||
)
|
||||
|
||||
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, match="may be removed in a future version"):
|
||||
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_version)
|
||||
|
||||
for name, geom in TEST_DATA.items():
|
||||
with open(datadir / f"{name}_{shapely_version}.pickle", "wb") as f:
|
||||
pickle.dump(geom, f)
|
||||
@@ -0,0 +1,43 @@
|
||||
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,82 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely.algorithms.polylabel import polylabel
|
||||
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=0.001)
|
||||
expected = Point(59.733, 111.330)
|
||||
assert expected.equals_exact(label, 1e-3)
|
||||
|
||||
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)
|
||||
if shapely.geos_version >= (3, 12, 0):
|
||||
# recent GEOS corrects for this
|
||||
assert label.coords[:] == [(32.722025, -117.201875)]
|
||||
else:
|
||||
# older versions not
|
||||
assert label.coords[:] == [(32.722025, -117.208595)]
|
||||
|
||||
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)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
shapely.geos_version < (3, 12, 0), reason="Fails with GEOS < 3.12"
|
||||
)
|
||||
def test_polygon_infinite_loop(self):
|
||||
# https://github.com/shapely/shapely/issues/1836
|
||||
# corner case that caused an infinite loop in the old custom implemetation
|
||||
polygon = shapely.from_wkt(
|
||||
"POLYGON ((536520.0679737709 5438764.374763639, 536520.0679737704 5438764.374763602, 536520.0679737709 5438764.374763642, 536520.0679737709 5438764.374763639))" # noqa: E501
|
||||
)
|
||||
label = polylabel(polygon)
|
||||
assert label.x == pytest.approx(536520.068)
|
||||
assert label.y == pytest.approx(5438764.375)
|
||||
@@ -0,0 +1,95 @@
|
||||
"""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,65 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import Point, Polygon
|
||||
from shapely.prepared import PreparedGeometry, prep
|
||||
|
||||
|
||||
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.contains_properly(
|
||||
geom2
|
||||
) == prepared_polygon1.contains_properly(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,63 @@
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import MultiLineString, Point, 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)
|
||||
|
||||
|
||||
def test_feature_from_geo_interface():
|
||||
# https://github.com/shapely/shapely/issues/1814
|
||||
class Feature:
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [0, 0]},
|
||||
}
|
||||
|
||||
expected = Point([0, 0])
|
||||
result = shape(Feature())
|
||||
assert result == expected
|
||||
@@ -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,24 @@
|
||||
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,285 @@
|
||||
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 polygons
|
||||
splitter = LineString([(1, 3), (1, -3)])
|
||||
self.helper(self.poly_simple, splitter, 2)
|
||||
self.helper(self.poly_hole, splitter, 2)
|
||||
|
||||
# crossing twice with one linestring --> return 3 polygons
|
||||
splitter = LineString([(1, 3), (1, -3), (1.7, -3), (1.7, 3)])
|
||||
self.helper(self.poly_simple, splitter, 3)
|
||||
self.helper(self.poly_hole, splitter, 3)
|
||||
|
||||
# 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_multiline(self):
|
||||
# crossing twice with a multilinestring --> return 3 polygons
|
||||
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(1.7, -3), (1.7, 3)]])
|
||||
self.helper(self.poly_simple, splitter, 3)
|
||||
self.helper(self.poly_hole, splitter, 3)
|
||||
|
||||
# crossing twice with a cross multilinestring --> return 4 polygons
|
||||
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(-3, 1), (3, 1)]])
|
||||
self.helper(self.poly_simple, splitter, 4)
|
||||
self.helper(self.poly_hole, splitter, 4)
|
||||
|
||||
# cross once, touch the boundary once --> return 2 polygons
|
||||
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(0, 2), (5, 2)]])
|
||||
self.helper(self.poly_simple, splitter, 2)
|
||||
self.helper(self.poly_hole, splitter, 2)
|
||||
|
||||
# cross once, inside the polygon once --> return 2 polygons
|
||||
splitter = MultiLineString(
|
||||
[[(0.2, 3), (0.2, -3)], [(1.2, 1.2), (1.7, 1.7), (3, 2)]]
|
||||
)
|
||||
self.helper(self.poly_simple, splitter, 2)
|
||||
self.helper(self.poly_hole, splitter, 2)
|
||||
|
||||
# cross once, outside the polygon once --> return 2 polygons
|
||||
splitter = MultiLineString([[(0.2, 3), (0.2, -3)], [(0, 3), (3, 3), (3, 0)]])
|
||||
self.helper(self.poly_simple, splitter, 2)
|
||||
self.helper(self.poly_hole, splitter, 2)
|
||||
|
||||
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,66 @@
|
||||
import random
|
||||
import unittest
|
||||
from functools import partial
|
||||
from itertools import islice
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import MultiPolygon, Point
|
||||
from shapely.ops import 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_unary_union_partial(self):
|
||||
# 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
|
||||
u = unary_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,108 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import MultiPolygon, Point, box
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:The 'shapely.vectorized:")
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:The 'shapely.vectorized:")
|
||||
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,129 @@
|
||||
"""
|
||||
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.ops import voronoi_diagram
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_two_regions():
|
||||
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
|
||||
regions = voronoi_diagram(mp)
|
||||
|
||||
assert len(regions.geoms) == 2
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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,176 @@
|
||||
import binascii
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely import wkt
|
||||
from shapely.geometry import Point
|
||||
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) 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) as file_pointer:
|
||||
restored = load(file_pointer, hex=True)
|
||||
assert some_point != restored
|
||||
return
|
||||
|
||||
with pytest.raises((UnicodeEncodeError, UnicodeDecodeError)):
|
||||
with open(file) as file_pointer:
|
||||
load(file_pointer, hex=True)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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) 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) 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(f"{num}: Running shapely tests on wkb")
|
||||
import shapely.geos
|
||||
|
||||
print(f"{num} GEOS Handle: {shapely.geos.lgeos.geos_handle}")
|
||||
import shapely.wkb
|
||||
import shapely.wkt
|
||||
|
||||
p = shapely.wkt.loads("POINT (0 0)")
|
||||
print(f"{num} WKT: {shapely.wkt.dumps(p)}")
|
||||
wkb = shapely.wkb.dumps(p)
|
||||
print(f"{num} WKB: {b2a_hex(wkb)}")
|
||||
|
||||
for i in range(10):
|
||||
shapely.wkb.loads(wkb)
|
||||
|
||||
print(f"{num} GEOS Handle: {shapely.geos.lgeos.geos_handle}")
|
||||
print(f"Done {num}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user