refactor: excel parse
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import sys
|
||||
|
||||
import numpy
|
||||
|
||||
from shapely.geos import geos_version_string
|
||||
|
||||
# Show some diagnostic information; handy for CI
|
||||
print("Python version: " + sys.version.replace("\n", " "))
|
||||
print("GEOS version: " + geos_version_string)
|
||||
print("Numpy version: " + numpy.version.version)
|
||||
@@ -0,0 +1,21 @@
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
from shapely.geos import geos_version
|
||||
|
||||
requires_geos_38 = pytest.mark.skipif(
|
||||
geos_version < (3, 8, 0), reason="GEOS >= 3.8.0 is required."
|
||||
)
|
||||
requires_geos_342 = pytest.mark.skipif(
|
||||
geos_version < (3, 4, 2), reason="GEOS > 3.4.2 is required."
|
||||
)
|
||||
|
||||
shapely20_todo = pytest.mark.xfail(
|
||||
strict=True, reason="Not yet implemented for Shapely 2.0"
|
||||
)
|
||||
shapely20_wontfix = pytest.mark.xfail(strict=True, reason="Will fail for Shapely 2.0")
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
"""Header for pytest."""
|
||||
return f"dependencies: numpy-{numpy.__version__}"
|
||||
@@ -0,0 +1,311 @@
|
||||
import unittest
|
||||
from math import pi
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from shapely import affinity
|
||||
from shapely.geometry import Point
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
|
||||
class AffineTestCase(unittest.TestCase):
|
||||
def test_affine_params(self):
|
||||
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
affinity.affine_transform(g, None)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
affinity.affine_transform(g, [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
affinity.affine_transform(None, [1, 2, 3, 4, 5, 6])
|
||||
|
||||
def test_affine_geom_types(self):
|
||||
|
||||
# identity matrices, which should result with no transformation
|
||||
matrix2d = (1, 0, 0, 1, 0, 0)
|
||||
matrix3d = (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
||||
# empty in, empty out
|
||||
empty2d = load_wkt("MULTIPOLYGON EMPTY")
|
||||
assert affinity.affine_transform(empty2d, matrix2d).is_empty
|
||||
|
||||
def test_geom(g2, g3=None):
|
||||
assert not g2.has_z
|
||||
a2 = affinity.affine_transform(g2, matrix2d)
|
||||
assert not a2.has_z
|
||||
assert g2.equals(a2)
|
||||
if g3 is not None:
|
||||
assert g3.has_z
|
||||
a3 = affinity.affine_transform(g3, matrix3d)
|
||||
assert a3.has_z
|
||||
assert g3.equals(a3)
|
||||
return
|
||||
|
||||
pt2d = load_wkt("POINT(12.3 45.6)")
|
||||
pt3d = load_wkt("POINT(12.3 45.6 7.89)")
|
||||
test_geom(pt2d, pt3d)
|
||||
ls2d = load_wkt("LINESTRING(0.9 3.4, 0.7 2, 2.5 2.7)")
|
||||
ls3d = load_wkt("LINESTRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5)")
|
||||
test_geom(ls2d, ls3d)
|
||||
lr2d = load_wkt("LINEARRING(0.9 3.4, 0.7 2, 2.5 2.7, 0.9 3.4)")
|
||||
lr3d = load_wkt("LINEARRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5, 0.9 3.4 3.3)")
|
||||
test_geom(lr2d, lr3d)
|
||||
test_geom(
|
||||
load_wkt(
|
||||
"POLYGON((0.9 2.3, 0.5 1.1, 2.4 0.8, 0.9 2.3), "
|
||||
"(1.1 1.7, 0.9 1.3, 1.4 1.2, 1.1 1.7), "
|
||||
"(1.6 1.3, 1.7 1, 1.9 1.1, 1.6 1.3))"
|
||||
)
|
||||
)
|
||||
test_geom(
|
||||
load_wkt("MULTIPOINT ((-300 300), (700 300), (-800 -1100), (200 -300))")
|
||||
)
|
||||
test_geom(
|
||||
load_wkt(
|
||||
"MULTILINESTRING((0 0, -0.7 -0.7, 0.6 -1), "
|
||||
"(-0.5 0.5, 0.7 0.6, 0 -0.6))"
|
||||
)
|
||||
)
|
||||
test_geom(
|
||||
load_wkt(
|
||||
"MULTIPOLYGON(((900 4300, -1100 -400, 900 -800, 900 4300)), "
|
||||
"((1200 4300, 2300 4400, 1900 1000, 1200 4300)))"
|
||||
)
|
||||
)
|
||||
test_geom(
|
||||
load_wkt(
|
||||
"GEOMETRYCOLLECTION(POINT(20 70),"
|
||||
" POLYGON((60 70, 13 35, 60 -30, 60 70)),"
|
||||
" LINESTRING(60 70, 50 100, 80 100))"
|
||||
)
|
||||
)
|
||||
|
||||
def test_affine_2d(self):
|
||||
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
|
||||
# custom scale and translate
|
||||
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
|
||||
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
|
||||
a2 = affinity.affine_transform(g, matrix2d)
|
||||
assert a2.equals_exact(expected2d, 1e-6)
|
||||
assert not a2.has_z
|
||||
# Make sure a 3D matrix does not make a 3D shape from a 2D input
|
||||
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 10, -5, 4.1, 100)
|
||||
a3 = affinity.affine_transform(g, matrix3d)
|
||||
assert a3.equals_exact(expected2d, 1e-6)
|
||||
assert not a3.has_z
|
||||
|
||||
def test_affine_3d(self):
|
||||
g2 = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
|
||||
g3 = load_wkt("LINESTRING(2.4 4.1 100.2, 2.4 3 132.8, 3 3 128.6)")
|
||||
# custom scale and translate
|
||||
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
|
||||
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 0.3048, -5, 4.1, 100)
|
||||
# Combinations of 2D and 3D geometries and matrices
|
||||
a22 = affinity.affine_transform(g2, matrix2d)
|
||||
a23 = affinity.affine_transform(g2, matrix3d)
|
||||
a32 = affinity.affine_transform(g3, matrix2d)
|
||||
a33 = affinity.affine_transform(g3, matrix3d)
|
||||
# Check dimensions
|
||||
assert not a22.has_z
|
||||
assert not a23.has_z
|
||||
assert a32.has_z
|
||||
assert a33.has_z
|
||||
|
||||
# 2D equality checks
|
||||
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
|
||||
expected3d = load_wkt(
|
||||
"LINESTRING(-0.2 14.35 130.54096, " "-0.2 11.6 140.47744, 1 11.6 139.19728)"
|
||||
)
|
||||
expected32 = load_wkt(
|
||||
"LINESTRING(-0.2 14.35 100.2, " "-0.2 11.6 132.8, 1 11.6 128.6)"
|
||||
)
|
||||
assert a22.equals_exact(expected2d, 1e-6)
|
||||
assert a23.equals_exact(expected2d, 1e-6)
|
||||
# Do explicit 3D check of coordinate values
|
||||
for a, e in zip(a32.coords, expected32.coords):
|
||||
for ap, ep in zip(a, e):
|
||||
self.assertAlmostEqual(ap, ep)
|
||||
for a, e in zip(a33.coords, expected3d.coords):
|
||||
for ap, ep in zip(a, e):
|
||||
self.assertAlmostEqual(ap, ep)
|
||||
|
||||
|
||||
class TransformOpsTestCase(unittest.TestCase):
|
||||
def test_rotate(self):
|
||||
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
|
||||
# counter-clockwise degrees
|
||||
rls = affinity.rotate(ls, 90)
|
||||
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
|
||||
assert rls.equals(els)
|
||||
# retest with named parameters for the same result
|
||||
rls = affinity.rotate(geom=ls, angle=90, origin="center")
|
||||
assert rls.equals(els)
|
||||
# clockwise radians
|
||||
rls = affinity.rotate(ls, -pi / 2, use_radians=True)
|
||||
els = load_wkt("LINESTRING(320 380, 220 380, 220 320)")
|
||||
assert rls.equals(els)
|
||||
## other `origin` parameters
|
||||
# around the centroid
|
||||
rls = affinity.rotate(ls, 90, origin="centroid")
|
||||
els = load_wkt("LINESTRING(182.5 320, 282.5 320, 282.5 380)")
|
||||
assert rls.equals(els)
|
||||
# around the second coordinate tuple
|
||||
rls = affinity.rotate(ls, 90, origin=ls.coords[1])
|
||||
els = load_wkt("LINESTRING(140 300, 240 300, 240 360)")
|
||||
assert rls.equals(els)
|
||||
# around the absolute Point of origin
|
||||
rls = affinity.rotate(ls, 90, origin=Point(0, 0))
|
||||
els = load_wkt("LINESTRING(-400 240, -300 240, -300 300)")
|
||||
assert rls.equals(els)
|
||||
|
||||
def test_rotate_empty(self):
|
||||
rls = affinity.rotate(load_wkt("LINESTRING EMPTY"), 90)
|
||||
els = load_wkt("LINESTRING EMPTY")
|
||||
assert rls.equals(els)
|
||||
|
||||
def test_rotate_angle_array(self):
|
||||
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
|
||||
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
|
||||
# check with degrees
|
||||
theta = np.array(90.0)
|
||||
rls = affinity.rotate(ls, theta)
|
||||
assert theta.item() == 90.0
|
||||
assert rls.equals(els)
|
||||
# check with radians
|
||||
theta = np.array(pi / 2)
|
||||
rls = affinity.rotate(ls, theta, use_radians=True)
|
||||
assert theta.item() == pi / 2
|
||||
assert rls.equals(els)
|
||||
|
||||
def test_scale(self):
|
||||
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
|
||||
# test defaults of 1.0
|
||||
sls = affinity.scale(ls)
|
||||
assert sls.equals(ls)
|
||||
# different scaling in different dimensions
|
||||
sls = affinity.scale(ls, 2, 3, 0.5)
|
||||
els = load_wkt("LINESTRING(210 500 5, 210 200 15, 330 200 10)")
|
||||
assert sls.equals(els)
|
||||
# Do explicit 3D check of coordinate values
|
||||
for a, b in zip(sls.coords, els.coords):
|
||||
for ap, bp in zip(a, b):
|
||||
self.assertEqual(ap, bp)
|
||||
# retest with named parameters for the same result
|
||||
sls = affinity.scale(geom=ls, xfact=2, yfact=3, zfact=0.5, origin="center")
|
||||
assert sls.equals(els)
|
||||
## other `origin` parameters
|
||||
# around the centroid
|
||||
sls = affinity.scale(ls, 2, 3, 0.5, origin="centroid")
|
||||
els = load_wkt("LINESTRING(228.75 537.5, 228.75 237.5, 348.75 237.5)")
|
||||
assert sls.equals(els)
|
||||
# around the second coordinate tuple
|
||||
sls = affinity.scale(ls, 2, 3, 0.5, origin=ls.coords[1])
|
||||
els = load_wkt("LINESTRING(240 600, 240 300, 360 300)")
|
||||
assert sls.equals(els)
|
||||
# around some other 3D Point of origin
|
||||
sls = affinity.scale(ls, 2, 3, 0.5, origin=Point(100, 200, 1000))
|
||||
els = load_wkt("LINESTRING(380 800 505, 380 500 515, 500 500 510)")
|
||||
assert sls.equals(els)
|
||||
# Do explicit 3D check of coordinate values
|
||||
for a, b in zip(sls.coords, els.coords):
|
||||
for ap, bp in zip(a, b):
|
||||
assert ap == bp
|
||||
|
||||
def test_scale_empty(self):
|
||||
sls = affinity.scale(load_wkt("LINESTRING EMPTY"))
|
||||
els = load_wkt("LINESTRING EMPTY")
|
||||
assert sls.equals(els)
|
||||
|
||||
def test_skew(self):
|
||||
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
|
||||
# test default shear angles of 0.0
|
||||
sls = affinity.skew(ls)
|
||||
assert sls.equals(ls)
|
||||
# different shearing in x- and y-directions
|
||||
sls = affinity.skew(ls, 15, -30)
|
||||
els = load_wkt(
|
||||
"LINESTRING (253.39745962155615 417.3205080756888, "
|
||||
"226.60254037844385 317.3205080756888, "
|
||||
"286.60254037844385 282.67949192431126)"
|
||||
)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
# retest with radians for the same result
|
||||
sls = affinity.skew(ls, pi / 12, -pi / 6, use_radians=True)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
# retest with named parameters for the same result
|
||||
sls = affinity.skew(geom=ls, xs=15, ys=-30, origin="center", use_radians=False)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
## other `origin` parameters
|
||||
# around the centroid
|
||||
sls = affinity.skew(ls, 15, -30, origin="centroid")
|
||||
els = load_wkt(
|
||||
"LINESTRING(258.42150697963973 406.49519052838332, "
|
||||
"231.6265877365273980 306.4951905283833185, "
|
||||
"291.6265877365274264 271.8541743770057337)"
|
||||
)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
# around the second coordinate tuple
|
||||
sls = affinity.skew(ls, 15, -30, origin=ls.coords[1])
|
||||
els = load_wkt(
|
||||
"LINESTRING(266.7949192431123038 400, 240 300, " "300 265.3589838486224153)"
|
||||
)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
# around the absolute Point of origin
|
||||
sls = affinity.skew(ls, 15, -30, origin=Point(0, 0))
|
||||
els = load_wkt(
|
||||
"LINESTRING(347.179676972449101 261.435935394489832, "
|
||||
"320.3847577293367976 161.4359353944898317, "
|
||||
"380.3847577293367976 126.7949192431122754)"
|
||||
)
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
|
||||
def test_skew_empty(self):
|
||||
sls = affinity.skew(load_wkt("LINESTRING EMPTY"))
|
||||
els = load_wkt("LINESTRING EMPTY")
|
||||
assert sls.equals(els)
|
||||
|
||||
def test_skew_xs_ys_array(self):
|
||||
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
|
||||
els = load_wkt(
|
||||
"LINESTRING (253.39745962155615 417.3205080756888, "
|
||||
"226.60254037844385 317.3205080756888, "
|
||||
"286.60254037844385 282.67949192431126)"
|
||||
)
|
||||
# check with degrees
|
||||
xs_ys = np.array([15.0, -30.0])
|
||||
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...])
|
||||
assert xs_ys[0] == 15.0
|
||||
assert xs_ys[1] == -30.0
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
# check with radians
|
||||
xs_ys = np.array([pi / 12, -pi / 6])
|
||||
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...], use_radians=True)
|
||||
assert xs_ys[0] == pi / 12
|
||||
assert xs_ys[1] == -pi / 6
|
||||
assert sls.equals_exact(els, 1e-6)
|
||||
|
||||
def test_translate(self):
|
||||
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
|
||||
# test default offset of 0.0
|
||||
tls = affinity.translate(ls)
|
||||
assert tls.equals(ls)
|
||||
# test all offsets
|
||||
tls = affinity.translate(ls, 100, 400, -10)
|
||||
els = load_wkt("LINESTRING(340 800 0, 340 700 20, 400 700 10)")
|
||||
assert tls.equals(els)
|
||||
# Do explicit 3D check of coordinate values
|
||||
for a, b in zip(tls.coords, els.coords):
|
||||
for ap, bp in zip(a, b):
|
||||
assert ap == bp
|
||||
# retest with named parameters for the same result
|
||||
tls = affinity.translate(geom=ls, xoff=100, yoff=400, zoff=-10)
|
||||
assert tls.equals(els)
|
||||
|
||||
def test_translate_empty(self):
|
||||
tls = affinity.translate(load_wkt("LINESTRING EMPTY"))
|
||||
els = load_wkt("LINESTRING EMPTY")
|
||||
self.assertTrue(tls.equals(els))
|
||||
assert tls.equals(els)
|
||||
@@ -0,0 +1,20 @@
|
||||
import unittest
|
||||
|
||||
from shapely import geometry
|
||||
|
||||
|
||||
class BoxTestCase(unittest.TestCase):
|
||||
def test_ccw(self):
|
||||
b = geometry.box(0, 0, 1, 1, ccw=True)
|
||||
assert b.exterior.coords[0] == (1.0, 0.0)
|
||||
assert b.exterior.coords[1] == (1.0, 1.0)
|
||||
|
||||
def test_ccw_default(self):
|
||||
b = geometry.box(0, 0, 1, 1)
|
||||
assert b.exterior.coords[0] == (1.0, 0.0)
|
||||
assert b.exterior.coords[1] == (1.0, 1.0)
|
||||
|
||||
def test_cw(self):
|
||||
b = geometry.box(0, 0, 1, 1, ccw=False)
|
||||
assert b.exterior.coords[0] == (0.0, 0.0)
|
||||
assert b.exterior.coords[1] == (0.0, 1.0)
|
||||
@@ -0,0 +1,172 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely import geometry
|
||||
from shapely.constructive import BufferCapStyle, BufferJoinStyle
|
||||
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("distance", [float("nan"), float("inf")])
|
||||
def test_non_finite_distance(distance):
|
||||
g = geometry.Point(0, 0)
|
||||
with pytest.raises(ValueError, match="distance must be finite"):
|
||||
g.buffer(distance)
|
||||
|
||||
|
||||
class BufferTests(unittest.TestCase):
|
||||
"""Test Buffer Point/Line/Polygon with and without single_sided params"""
|
||||
|
||||
def test_empty(self):
|
||||
g = geometry.Point(0, 0)
|
||||
h = g.buffer(0)
|
||||
assert h.is_empty
|
||||
|
||||
def test_point(self):
|
||||
g = geometry.Point(0, 0)
|
||||
h = g.buffer(1, quad_segs=1)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_point_single_sidedd(self):
|
||||
g = geometry.Point(0, 0)
|
||||
h = g.buffer(1, quad_segs=1, single_sided=True)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_line(self):
|
||||
g = geometry.LineString([[0, 0], [0, 1]])
|
||||
h = g.buffer(1, quad_segs=1)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [
|
||||
(-1.0, 1.0),
|
||||
(0, 2.0),
|
||||
(1.0, 1.0),
|
||||
(1.0, 0.0),
|
||||
(0, -1.0),
|
||||
(-1.0, 0.0),
|
||||
(-1.0, 1.0),
|
||||
]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_line_single_sideded_left(self):
|
||||
g = geometry.LineString([[0, 0], [0, 1]])
|
||||
h = g.buffer(1, quad_segs=1, single_sided=True)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [(0.0, 1.0), (0.0, 0.0), (-1.0, 0.0), (-1.0, 1.0), (0.0, 1.0)]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_line_single_sideded_right(self):
|
||||
g = geometry.LineString([[0, 0], [0, 1]])
|
||||
h = g.buffer(-1, quad_segs=1, single_sided=True)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_polygon(self):
|
||||
g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
|
||||
h = g.buffer(1, quad_segs=1)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [
|
||||
(-1.0, 0.0),
|
||||
(-1.0, 1.0),
|
||||
(0.0, 2.0),
|
||||
(1.0, 2.0),
|
||||
(2.0, 1.0),
|
||||
(2.0, 0.0),
|
||||
(1.0, -1.0),
|
||||
(0.0, -1.0),
|
||||
(-1.0, 0.0),
|
||||
]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_polygon_single_sideded(self):
|
||||
g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
|
||||
h = g.buffer(1, quad_segs=1, single_sided=True)
|
||||
assert h.geom_type == "Polygon"
|
||||
expected_coord = [
|
||||
(-1.0, 0.0),
|
||||
(-1.0, 1.0),
|
||||
(0.0, 2.0),
|
||||
(1.0, 2.0),
|
||||
(2.0, 1.0),
|
||||
(2.0, 0.0),
|
||||
(1.0, -1.0),
|
||||
(0.0, -1.0),
|
||||
(-1.0, 0.0),
|
||||
]
|
||||
for index, coord in enumerate(h.exterior.coords):
|
||||
assert coord[0] == pytest.approx(expected_coord[index][0])
|
||||
assert coord[1] == pytest.approx(expected_coord[index][1])
|
||||
|
||||
def test_enum_values(self):
|
||||
assert CAP_STYLE.round == 1
|
||||
assert CAP_STYLE.round == BufferCapStyle.round
|
||||
assert CAP_STYLE.flat == 2
|
||||
assert CAP_STYLE.flat == BufferCapStyle.flat
|
||||
assert CAP_STYLE.square == 3
|
||||
assert CAP_STYLE.square == BufferCapStyle.square
|
||||
|
||||
assert JOIN_STYLE.round == 1
|
||||
assert JOIN_STYLE.round == BufferJoinStyle.round
|
||||
assert JOIN_STYLE.mitre == 2
|
||||
assert JOIN_STYLE.mitre == BufferJoinStyle.mitre
|
||||
assert JOIN_STYLE.bevel == 3
|
||||
assert JOIN_STYLE.bevel == BufferJoinStyle.bevel
|
||||
|
||||
def test_cap_style(self):
|
||||
g = geometry.LineString([[0, 0], [1, 0]])
|
||||
h = g.buffer(1, cap_style=BufferCapStyle.round)
|
||||
assert h == g.buffer(1, cap_style=CAP_STYLE.round)
|
||||
assert h == g.buffer(1, cap_style="round")
|
||||
|
||||
h = g.buffer(1, cap_style=BufferCapStyle.flat)
|
||||
assert h == g.buffer(1, cap_style=CAP_STYLE.flat)
|
||||
assert h == g.buffer(1, cap_style="flat")
|
||||
|
||||
h = g.buffer(1, cap_style=BufferCapStyle.square)
|
||||
assert h == g.buffer(1, cap_style=CAP_STYLE.square)
|
||||
assert h == g.buffer(1, cap_style="square")
|
||||
|
||||
def test_buffer_style(self):
|
||||
g = geometry.LineString([[0, 0], [1, 0]])
|
||||
h = g.buffer(1, join_style=BufferJoinStyle.round)
|
||||
assert h == g.buffer(1, join_style=JOIN_STYLE.round)
|
||||
assert h == g.buffer(1, join_style="round")
|
||||
|
||||
h = g.buffer(1, join_style=BufferJoinStyle.mitre)
|
||||
assert h == g.buffer(1, join_style=JOIN_STYLE.mitre)
|
||||
assert h == g.buffer(1, join_style="mitre")
|
||||
|
||||
h = g.buffer(1, join_style=BufferJoinStyle.bevel)
|
||||
assert h == g.buffer(1, join_style=JOIN_STYLE.bevel)
|
||||
assert h == g.buffer(1, join_style="bevel")
|
||||
|
||||
|
||||
def test_deprecated_quadsegs():
|
||||
point = geometry.Point(0, 0)
|
||||
with pytest.warns(FutureWarning):
|
||||
result = point.buffer(1, quadsegs=1)
|
||||
expected = point.buffer(1, quad_segs=1)
|
||||
assert result.equals(expected)
|
||||
|
||||
|
||||
def test_resolution_alias():
|
||||
point = geometry.Point(0, 0)
|
||||
result = point.buffer(1, resolution=1)
|
||||
expected = point.buffer(1, quad_segs=1)
|
||||
assert result.equals(expected)
|
||||
@@ -0,0 +1,51 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geometry.polygon import LinearRing, orient, Polygon, signed_area
|
||||
|
||||
|
||||
class SignedAreaTestCase(unittest.TestCase):
|
||||
def test_triangle(self):
|
||||
tri = LinearRing([(0, 0), (2, 5), (7, 0)])
|
||||
assert signed_area(tri) == pytest.approx(-7 * 5 / 2)
|
||||
|
||||
def test_square(self):
|
||||
xmin, xmax = (-1, 1)
|
||||
ymin, ymax = (-2, 3)
|
||||
rect = LinearRing(
|
||||
[(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)]
|
||||
)
|
||||
assert signed_area(rect) == pytest.approx(10.0)
|
||||
|
||||
|
||||
class RingOrientationTestCase(unittest.TestCase):
|
||||
def test_ccw(self):
|
||||
ring = LinearRing([(1, 0), (0, 1), (0, 0)])
|
||||
assert ring.is_ccw
|
||||
|
||||
def test_cw(self):
|
||||
ring = LinearRing([(0, 0), (0, 1), (1, 0)])
|
||||
assert not ring.is_ccw
|
||||
|
||||
|
||||
class PolygonOrienterTestCase(unittest.TestCase):
|
||||
def test_no_holes(self):
|
||||
ring = LinearRing([(0, 0), (0, 1), (1, 0)])
|
||||
polygon = Polygon(ring)
|
||||
assert not polygon.exterior.is_ccw
|
||||
polygon = orient(polygon, 1)
|
||||
assert polygon.exterior.is_ccw
|
||||
|
||||
def test_holes(self):
|
||||
# fmt: off
|
||||
polygon = Polygon(
|
||||
[(0, 0), (0, 1), (1, 0)],
|
||||
[[(0.5, 0.25), (0.25, 0.5), (0.25, 0.25)]]
|
||||
)
|
||||
# fmt: on
|
||||
assert not polygon.exterior.is_ccw
|
||||
assert polygon.interiors[0].is_ccw
|
||||
polygon = orient(polygon, 1)
|
||||
assert polygon.exterior.is_ccw
|
||||
assert not polygon.interiors[0].is_ccw
|
||||
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Tests for GEOSClipByRect based on unit tests from libgeos.
|
||||
|
||||
There are some expected differences due to Shapely's handling of empty
|
||||
geometries.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.ops import clip_by_rect
|
||||
from shapely.wkt import dumps as dump_wkt
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
|
||||
def test_point_outside():
|
||||
"""Point outside"""
|
||||
geom1 = load_wkt("POINT (0 0)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_point_inside():
|
||||
"""Point inside"""
|
||||
geom1 = load_wkt("POINT (15 15)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "POINT (15 15)"
|
||||
|
||||
|
||||
def test_point_on_boundary():
|
||||
"""Point on boundary"""
|
||||
geom1 = load_wkt("POINT (15 10)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_outside():
|
||||
"""Line outside"""
|
||||
geom1 = load_wkt("LINESTRING (0 0, -5 5)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_inside():
|
||||
"""Line inside"""
|
||||
geom1 = load_wkt("LINESTRING (15 15, 16 15)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 15, 16 15)"
|
||||
|
||||
|
||||
def test_line_on_boundary():
|
||||
"""Line on boundary"""
|
||||
geom1 = load_wkt("LINESTRING (10 15, 10 10, 15 10)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def test_line_splitting_rectangle():
|
||||
"""Line splitting rectangle"""
|
||||
geom1 = load_wkt("LINESTRING (10 5, 25 20)")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "LINESTRING (15 10, 20 15)"
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="TODO issue to CCW")
|
||||
def test_polygon_shell_ccw_fully_on_rectangle_boundary():
|
||||
"""Polygon shell (CCW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt("POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="TODO issue to CW")
|
||||
def test_polygon_shell_cc_fully_on_rectangle_boundary():
|
||||
"""Polygon shell (CW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt("POLYGON ((10 10, 10 20, 20 20, 20 10, 10 10))")
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
|
||||
|
||||
def polygon_hole_ccw_fully_on_rectangle_boundary():
|
||||
"""Polygon hole (CCW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt(
|
||||
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
)
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def polygon_hole_cw_fully_on_rectangle_boundary():
|
||||
"""Polygon hole (CW) fully on rectangle boundary"""
|
||||
geom1 = load_wkt(
|
||||
"POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 10 20, 20 20, 20 10, 10 10))"
|
||||
)
|
||||
geom2 = clip_by_rect(geom1, 10, 10, 20, 20)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == "GEOMETRYCOLLECTION EMPTY"
|
||||
|
||||
|
||||
def polygon_fully_within_rectangle():
|
||||
"""Polygon fully within rectangle"""
|
||||
wkt = "POLYGON ((1 1, 1 30, 30 30, 30 1, 1 1), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
geom1 = load_wkt(wkt)
|
||||
geom2 = clip_by_rect(geom1, 0, 0, 40, 40)
|
||||
assert dump_wkt(geom2, rounding_precision=0) == wkt
|
||||
|
||||
|
||||
def polygon_overlapping_rectangle():
|
||||
"""Polygon overlapping rectangle"""
|
||||
wkt = "POLYGON ((0 0, 0 30, 30 30, 30 0, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))"
|
||||
geom1 = load_wkt(wkt)
|
||||
geom2 = clip_by_rect(geom1, 5, 5, 15, 15)
|
||||
assert (
|
||||
dump_wkt(geom2, rounding_precision=0)
|
||||
== "POLYGON ((5 5, 5 15, 10 15, 10 10, 15 10, 15 5, 5 5))"
|
||||
)
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
When a "context" passed to shape/asShape has a coordinate
|
||||
which is missing a dimension we should raise a descriptive error.
|
||||
|
||||
When we use mixed dimensions in a WKT geometry, the parser strips
|
||||
any dimension which is not present in every coordinate.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely import wkt
|
||||
from shapely.errors import GEOSException
|
||||
from shapely.geometry import LineString, Polygon, shape
|
||||
from shapely.geos import geos_version
|
||||
|
||||
geojson_cases = [
|
||||
{"type": "LineString", "coordinates": [[1, 1, 1], [2, 2]]},
|
||||
# Specific test case from #869
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[55.12916764533149, 24.980385694214384, 2.5],
|
||||
[55.13098248044217, 24.979828079961905],
|
||||
[55.13966519231666, 24.97801442415322],
|
||||
[55.13966563924936, 24.97801442415322],
|
||||
[55.14139286840762, 24.982307444496097],
|
||||
[55.14169331277646, 24.983717465495562],
|
||||
[55.14203489144224, 24.985419446276566, 2.5],
|
||||
[55.14180327151276, 24.98428602667792, 2.5],
|
||||
[55.14170091915952, 24.984242720177235, 2.5],
|
||||
[55.14122966992623, 24.984954809433702, 2.5],
|
||||
[55.14134021791831, 24.985473928648396, 2.5],
|
||||
[55.141405876161286, 24.986090184809793, 2.5],
|
||||
[55.141361358941225, 24.986138101357326, 2.5],
|
||||
[55.14093322994411, 24.986218753894093, 2.5],
|
||||
[55.140897653420964, 24.986214283545635, 2.5],
|
||||
[55.14095492976058, 24.9863027591922, 2.5],
|
||||
[55.140900447388745, 24.98628436557094, 2.5],
|
||||
[55.140867059473706, 24.98628869622101, 2.5],
|
||||
[55.14089155325796, 24.986402364143782, 2.5],
|
||||
[55.14090938808566, 24.986479011993385, 2.5],
|
||||
[55.140943893587824, 24.986471188883584, 2.5],
|
||||
[55.1410161176551, 24.9864174050037, 2.5],
|
||||
[55.140996932409635, 24.986521806266644, 2.5],
|
||||
[55.14163554031332, 24.986910400619593, 2.5],
|
||||
[55.14095781686062, 24.987033474900578, 2.5],
|
||||
[55.14058258698692, 24.98693261266349, 2.5],
|
||||
[55.14032624044253, 24.98747538747211, 2.5],
|
||||
[55.14007240846915, 24.988001119077232, 2.5],
|
||||
[55.14013122149105, 24.98831115636925, 2.5],
|
||||
[55.13991827457961, 24.98834356639557, 2.5],
|
||||
[55.139779460946755, 24.988254625087706, 2.5],
|
||||
[55.13974742344948, 24.988261377176524, 2.5],
|
||||
[55.139515198160304, 24.98841811876934, 2.5],
|
||||
[55.13903617238334, 24.98817914139135, 2.5],
|
||||
[55.1391330764994, 24.988660542040925, 2.5],
|
||||
[55.13914369357698, 24.989438289540374, 2.5],
|
||||
[55.136431216517785, 24.98966711550207, 2.0],
|
||||
[55.13659028641709, 24.99041706302204, 2.0],
|
||||
[55.1355852030721, 24.990933481401207, 2.5],
|
||||
[55.13535549235394, 24.99110470506038, 2.5],
|
||||
[55.13512578163577, 24.99127592871955, 2.5],
|
||||
[55.129969653784556, 24.991440074326995, 2.5],
|
||||
[55.130221623112746, 24.988070688875112, 2.5],
|
||||
[55.130451333830905, 24.98789946521594, 2.5],
|
||||
[55.13089208224919, 24.98742639990359, 2.5],
|
||||
[55.132177586827666, 24.989003408454433, 2.5],
|
||||
[55.13238862452779, 24.988701566801254, 2.5],
|
||||
[55.132482594977674, 24.988501518707757, 2.5],
|
||||
[55.132525994610624, 24.988048802794115, 2.5],
|
||||
[55.13249018525683, 24.987180623870653, 2.5],
|
||||
[55.13253358488978, 24.986727907957015, 2.5],
|
||||
[55.1322761673244, 24.985827132742713, 2.5],
|
||||
[55.13163341503516, 24.98503862846729, 2.5],
|
||||
[55.131514764536504, 24.984469124700183, 2.5],
|
||||
[55.131275600894, 24.983796337257242, 2.0],
|
||||
[55.13066865795855, 24.98387601190528, 2.0],
|
||||
[55.13026930682963, 24.981537228037503, 2.0],
|
||||
[55.130260412698846, 24.981495691049748, 2.0],
|
||||
[55.13025151856806, 24.981454154061993, 2.0],
|
||||
[55.13022925995803, 24.98096497686874, 2.5],
|
||||
[55.12984453059386, 24.9804285816199, 2.5],
|
||||
[55.129998291954365, 24.98021419115843, 2.5],
|
||||
[55.12916764533149, 24.980385694214384, 2.5],
|
||||
]
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
direct_cases = [
|
||||
(LineString, [[[0, 0, 0], [1, 1]]]),
|
||||
(Polygon, [[[0, 0, 0], [1, 1, 0], [1, 1], [0, 1, 0], [0, 0, 0]]]),
|
||||
# Specific test case from #869
|
||||
(
|
||||
Polygon,
|
||||
[
|
||||
[
|
||||
[55.12916764533149, 24.980385694214384, 2.5],
|
||||
[55.13098248044217, 24.979828079961905],
|
||||
[55.13966519231666, 24.97801442415322],
|
||||
[55.13966563924936, 24.97801442415322],
|
||||
[55.14139286840762, 24.982307444496097],
|
||||
[55.14169331277646, 24.983717465495562],
|
||||
[55.14203489144224, 24.985419446276566, 2.5],
|
||||
[55.14180327151276, 24.98428602667792, 2.5],
|
||||
[55.14170091915952, 24.984242720177235, 2.5],
|
||||
[55.14122966992623, 24.984954809433702, 2.5],
|
||||
[55.14134021791831, 24.985473928648396, 2.5],
|
||||
[55.141405876161286, 24.986090184809793, 2.5],
|
||||
[55.141361358941225, 24.986138101357326, 2.5],
|
||||
[55.14093322994411, 24.986218753894093, 2.5],
|
||||
[55.140897653420964, 24.986214283545635, 2.5],
|
||||
[55.14095492976058, 24.9863027591922, 2.5],
|
||||
[55.140900447388745, 24.98628436557094, 2.5],
|
||||
[55.140867059473706, 24.98628869622101, 2.5],
|
||||
[55.14089155325796, 24.986402364143782, 2.5],
|
||||
[55.14090938808566, 24.986479011993385, 2.5],
|
||||
[55.140943893587824, 24.986471188883584, 2.5],
|
||||
[55.1410161176551, 24.9864174050037, 2.5],
|
||||
[55.140996932409635, 24.986521806266644, 2.5],
|
||||
[55.14163554031332, 24.986910400619593, 2.5],
|
||||
[55.14095781686062, 24.987033474900578, 2.5],
|
||||
[55.14058258698692, 24.98693261266349, 2.5],
|
||||
[55.14032624044253, 24.98747538747211, 2.5],
|
||||
[55.14007240846915, 24.988001119077232, 2.5],
|
||||
[55.14013122149105, 24.98831115636925, 2.5],
|
||||
[55.13991827457961, 24.98834356639557, 2.5],
|
||||
[55.139779460946755, 24.988254625087706, 2.5],
|
||||
[55.13974742344948, 24.988261377176524, 2.5],
|
||||
[55.139515198160304, 24.98841811876934, 2.5],
|
||||
[55.13903617238334, 24.98817914139135, 2.5],
|
||||
[55.1391330764994, 24.988660542040925, 2.5],
|
||||
[55.13914369357698, 24.989438289540374, 2.5],
|
||||
[55.136431216517785, 24.98966711550207, 2.0],
|
||||
[55.13659028641709, 24.99041706302204, 2.0],
|
||||
[55.1355852030721, 24.990933481401207, 2.5],
|
||||
[55.13535549235394, 24.99110470506038, 2.5],
|
||||
[55.13512578163577, 24.99127592871955, 2.5],
|
||||
[55.129969653784556, 24.991440074326995, 2.5],
|
||||
[55.130221623112746, 24.988070688875112, 2.5],
|
||||
[55.130451333830905, 24.98789946521594, 2.5],
|
||||
[55.13089208224919, 24.98742639990359, 2.5],
|
||||
[55.132177586827666, 24.989003408454433, 2.5],
|
||||
[55.13238862452779, 24.988701566801254, 2.5],
|
||||
[55.132482594977674, 24.988501518707757, 2.5],
|
||||
[55.132525994610624, 24.988048802794115, 2.5],
|
||||
[55.13249018525683, 24.987180623870653, 2.5],
|
||||
[55.13253358488978, 24.986727907957015, 2.5],
|
||||
[55.1322761673244, 24.985827132742713, 2.5],
|
||||
[55.13163341503516, 24.98503862846729, 2.5],
|
||||
[55.131514764536504, 24.984469124700183, 2.5],
|
||||
[55.131275600894, 24.983796337257242, 2.0],
|
||||
[55.13066865795855, 24.98387601190528, 2.0],
|
||||
[55.13026930682963, 24.981537228037503, 2.0],
|
||||
[55.130260412698846, 24.981495691049748, 2.0],
|
||||
[55.13025151856806, 24.981454154061993, 2.0],
|
||||
[55.13022925995803, 24.98096497686874, 2.5],
|
||||
[55.12984453059386, 24.9804285816199, 2.5],
|
||||
[55.129998291954365, 24.98021419115843, 2.5],
|
||||
[55.12916764533149, 24.980385694214384, 2.5],
|
||||
]
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
wkt_cases = [
|
||||
# preserve 3rd dimension
|
||||
("MULTIPOINT (1 1 1, 2 2)", "MULTIPOINT Z (1 1 1, 2 2 0)"),
|
||||
("MULTIPOINT (1 1, 2 2 2)", "MULTIPOINT Z (1 1 0, 2 2 2)"),
|
||||
("LINESTRING (1 1 1, 2 2)", "LINESTRING Z (1 1 1, 2 2 0)"),
|
||||
(
|
||||
"POLYGON ((0 0 0, 1 0 0, 1 1, 0 1 0, 0 0 0))",
|
||||
"POLYGON Z ((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))",
|
||||
),
|
||||
# drop 3rd dimension
|
||||
("LINESTRING (1 1, 2 2 2)", "LINESTRING (1 1, 2 2)"),
|
||||
("POLYGON ((0 0, 1 0 1, 1 1, 0 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Creating an ndarray from ragged nested sequences:")
|
||||
@pytest.mark.parametrize("geojson", geojson_cases)
|
||||
def test_create_from_geojson(geojson):
|
||||
# exact error depends on numpy version
|
||||
with pytest.raises((ValueError, TypeError)) as exc:
|
||||
shape(geojson).wkt
|
||||
assert exc.match(
|
||||
"Inconsistent coordinate dimensionality|Input operand 0 does not have enough dimensions|ufunc 'linestrings' not supported for the input types|setting an array element with a sequence. The requested array has an inhomogeneous shape"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Creating an ndarray from ragged nested sequences:")
|
||||
@pytest.mark.parametrize("constructor, args", direct_cases)
|
||||
def test_create_directly(constructor, args):
|
||||
with pytest.raises((ValueError, TypeError)) as exc:
|
||||
constructor(*args)
|
||||
assert exc.match(
|
||||
"Inconsistent coordinate dimensionality|Input operand 0 does not have enough dimensions|ufunc 'linestrings' not supported for the input types|setting an array element with a sequence. The requested array has an inhomogeneous shape"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wkt_geom,expected", wkt_cases)
|
||||
def test_create_from_wkt(wkt_geom, expected):
|
||||
if geos_version >= (3, 12, 0):
|
||||
# https://github.com/shapely/shapely/issues/1541
|
||||
with pytest.raises(GEOSException):
|
||||
wkt.loads(wkt_geom)
|
||||
else:
|
||||
geom = wkt.loads(wkt_geom)
|
||||
assert geom.wkt == expected
|
||||
@@ -0,0 +1,32 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString, Point, Polygon
|
||||
from shapely.ops import triangulate
|
||||
|
||||
|
||||
class DelaunayTriangulation(unittest.TestCase):
|
||||
"""
|
||||
Only testing the number of triangles and their type here.
|
||||
This doesn't actually test the points in the resulting geometries.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.p = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
|
||||
def test_polys(self):
|
||||
polys = triangulate(self.p)
|
||||
assert len(polys) == 2
|
||||
for p in polys:
|
||||
assert isinstance(p, Polygon)
|
||||
|
||||
def test_lines(self):
|
||||
polys = triangulate(self.p, edges=True)
|
||||
assert len(polys) == 5
|
||||
for p in polys:
|
||||
assert isinstance(p, LineString)
|
||||
|
||||
def test_point(self):
|
||||
p = Point(1, 1)
|
||||
polys = triangulate(p)
|
||||
assert len(polys) == 0
|
||||
@@ -0,0 +1,22 @@
|
||||
from shapely.geometry import MultiPolygon, Point, Polygon
|
||||
|
||||
|
||||
def test_empty_polygon():
|
||||
"""No constructor arg makes an empty polygon geometry."""
|
||||
assert Polygon().is_empty
|
||||
|
||||
|
||||
def test_empty_multipolygon():
|
||||
"""No constructor arg makes an empty multipolygon geometry."""
|
||||
assert MultiPolygon().is_empty
|
||||
|
||||
|
||||
def test_multipolygon_empty_polygon():
|
||||
"""An empty polygon passed to MultiPolygon() makes an empty
|
||||
multipolygon geometry."""
|
||||
assert MultiPolygon([Polygon()]).is_empty
|
||||
|
||||
|
||||
def test_multipolygon_empty_among_polygon():
|
||||
"""An empty polygon passed to MultiPolygon() is ignored."""
|
||||
assert len(MultiPolygon([Point(0, 0).buffer(1.0), Polygon()]).geoms) == 1
|
||||
@@ -0,0 +1,32 @@
|
||||
import pytest
|
||||
|
||||
from shapely import Point
|
||||
from shapely.errors import ShapelyDeprecationWarning
|
||||
|
||||
|
||||
def test_equals_exact():
|
||||
p1 = Point(1.0, 1.0)
|
||||
p2 = Point(2.0, 2.0)
|
||||
assert not p1.equals(p2)
|
||||
assert not p1.equals_exact(p2, 0.001)
|
||||
assert p1.equals_exact(p2, 1.42)
|
||||
|
||||
|
||||
def test_almost_equals_default():
|
||||
p1 = Point(1.0, 1.0)
|
||||
p2 = Point(1.0 + 1e-7, 1.0 + 1e-7) # almost equal to 6 places
|
||||
p3 = Point(1.0 + 1e-6, 1.0 + 1e-6) # not almost equal
|
||||
with pytest.warns(ShapelyDeprecationWarning):
|
||||
assert p1.almost_equals(p2)
|
||||
with pytest.warns(ShapelyDeprecationWarning):
|
||||
assert not p1.almost_equals(p3)
|
||||
|
||||
|
||||
def test_almost_equals():
|
||||
p1 = Point(1.0, 1.0)
|
||||
p2 = Point(1.1, 1.1)
|
||||
assert not p1.equals(p2)
|
||||
with pytest.warns(ShapelyDeprecationWarning):
|
||||
assert p1.almost_equals(p2, 0)
|
||||
with pytest.warns(ShapelyDeprecationWarning):
|
||||
assert not p1.almost_equals(p2, 1)
|
||||
@@ -0,0 +1,118 @@
|
||||
import unittest
|
||||
|
||||
from shapely import wkt
|
||||
from shapely.geometry import shape
|
||||
from shapely.geometry.linestring import LineString
|
||||
from shapely.geometry.multilinestring import MultiLineString
|
||||
from shapely.geometry.multipoint import MultiPoint
|
||||
from shapely.geometry.multipolygon import MultiPolygon
|
||||
from shapely.geometry.polygon import LinearRing, Polygon
|
||||
|
||||
|
||||
class GeoThing:
|
||||
def __init__(self, d):
|
||||
self.__geo_interface__ = d
|
||||
|
||||
|
||||
class GeoInterfaceTestCase(unittest.TestCase):
|
||||
def test_geointerface(self):
|
||||
# Convert a dictionary
|
||||
d = {"type": "Point", "coordinates": (0.0, 0.0)}
|
||||
geom = shape(d)
|
||||
assert geom.geom_type == "Point"
|
||||
assert tuple(geom.coords) == ((0.0, 0.0),)
|
||||
|
||||
# Convert an object that implements the geo protocol
|
||||
geom = None
|
||||
thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)})
|
||||
geom = shape(thing)
|
||||
assert geom.geom_type == "Point"
|
||||
assert tuple(geom.coords) == ((0.0, 0.0),)
|
||||
|
||||
# Check line string
|
||||
geom = shape({"type": "LineString", "coordinates": ((-1.0, -1.0), (1.0, 1.0))})
|
||||
assert isinstance(geom, LineString)
|
||||
assert tuple(geom.coords) == ((-1.0, -1.0), (1.0, 1.0))
|
||||
|
||||
# Check linearring
|
||||
geom = shape(
|
||||
{
|
||||
"type": "LinearRing",
|
||||
"coordinates": (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
),
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, LinearRing)
|
||||
assert tuple(geom.coords) == (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
)
|
||||
|
||||
# polygon
|
||||
geom = shape(
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": (
|
||||
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)),
|
||||
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
|
||||
),
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, Polygon)
|
||||
assert tuple(geom.exterior.coords) == (
|
||||
(0.0, 0.0),
|
||||
(0.0, 1.0),
|
||||
(1.0, 1.0),
|
||||
(2.0, -1.0),
|
||||
(0.0, 0.0),
|
||||
)
|
||||
assert len(geom.interiors) == 1
|
||||
|
||||
# multi point
|
||||
geom = shape({"type": "MultiPoint", "coordinates": ((1.0, 2.0), (3.0, 4.0))})
|
||||
assert isinstance(geom, MultiPoint)
|
||||
assert len(geom.geoms) == 2
|
||||
|
||||
# multi line string
|
||||
geom = shape(
|
||||
{"type": "MultiLineString", "coordinates": (((0.0, 0.0), (1.0, 2.0)),)}
|
||||
)
|
||||
assert isinstance(geom, MultiLineString)
|
||||
assert len(geom.geoms) == 1
|
||||
|
||||
# multi polygon
|
||||
geom = shape(
|
||||
{
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": [
|
||||
(
|
||||
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
|
||||
((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)),
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
assert isinstance(geom, MultiPolygon)
|
||||
assert len(geom.geoms) == 1
|
||||
|
||||
|
||||
def test_empty_wkt_polygon():
|
||||
"""Confirm fix for issue #450"""
|
||||
g = wkt.loads("POLYGON EMPTY")
|
||||
assert g.__geo_interface__["type"] == "Polygon"
|
||||
assert g.__geo_interface__["coordinates"] == ()
|
||||
|
||||
|
||||
def test_empty_polygon():
|
||||
"""Confirm fix for issue #450"""
|
||||
g = Polygon()
|
||||
assert g.__geo_interface__["type"] == "Polygon"
|
||||
assert g.__geo_interface__["coordinates"] == ()
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
"""Test recovery from operation on invalid geometries
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely.errors import TopologicalError
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
|
||||
class InvalidGeometriesTestCase(unittest.TestCase):
|
||||
def test_invalid_intersection(self):
|
||||
# Make a self-intersecting polygon
|
||||
polygon_invalid = Polygon([(0, 0), (1, 1), (1, -1), (0, 1), (0, 0)])
|
||||
assert not polygon_invalid.is_valid
|
||||
|
||||
# Intersect with a valid polygon
|
||||
polygon = Polygon([(-0.5, -0.5), (-0.5, 0.5), (0.5, 0.5), (0.5, -5)])
|
||||
assert polygon.is_valid
|
||||
assert polygon_invalid.intersects(polygon)
|
||||
|
||||
with pytest.raises((TopologicalError, shapely.GEOSException)):
|
||||
polygon_invalid.intersection(polygon)
|
||||
with pytest.raises((TopologicalError, shapely.GEOSException)):
|
||||
polygon.intersection(polygon_invalid)
|
||||
return
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely.geometry import LineString, MultiLineString, Point
|
||||
|
||||
|
||||
class LinearReferencingTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.point = Point(1, 1)
|
||||
self.line1 = LineString([(0, 0), (2, 0)])
|
||||
self.line2 = LineString([(3, 0), (3, 6)])
|
||||
self.multiline = MultiLineString(
|
||||
[list(self.line1.coords), list(self.line2.coords)]
|
||||
)
|
||||
|
||||
def test_line1_project(self):
|
||||
assert self.line1.project(self.point) == 1.0
|
||||
assert self.line1.project(self.point, normalized=True) == 0.5
|
||||
|
||||
def test_alias_project(self):
|
||||
assert self.line1.line_locate_point(self.point) == 1.0
|
||||
assert self.line1.line_locate_point(self.point, normalized=True) == 0.5
|
||||
|
||||
def test_line2_project(self):
|
||||
assert self.line2.project(self.point) == 1.0
|
||||
assert self.line2.project(self.point, normalized=True) == pytest.approx(
|
||||
0.16666666666, 8
|
||||
)
|
||||
|
||||
def test_multiline_project(self):
|
||||
assert self.multiline.project(self.point) == 1.0
|
||||
assert self.multiline.project(self.point, normalized=True) == 0.125
|
||||
|
||||
def test_not_supported_project(self):
|
||||
with pytest.raises(shapely.GEOSException, match="IllegalArgumentException"):
|
||||
self.point.buffer(1.0).project(self.point)
|
||||
|
||||
def test_not_on_line_project(self):
|
||||
# Points that aren't on the line project to 0.
|
||||
assert self.line1.project(Point(-10, -10)) == 0.0
|
||||
|
||||
def test_line1_interpolate(self):
|
||||
assert self.line1.interpolate(0.5).equals(Point(0.5, 0.0))
|
||||
assert self.line1.interpolate(-0.5).equals(Point(1.5, 0.0))
|
||||
assert self.line1.interpolate(0.5, normalized=True).equals(Point(1, 0))
|
||||
assert self.line1.interpolate(-0.5, normalized=True).equals(Point(1, 0))
|
||||
|
||||
def test_alias_interpolate(self):
|
||||
assert self.line1.line_interpolate_point(0.5).equals(Point(0.5, 0.0))
|
||||
assert self.line1.line_interpolate_point(-0.5).equals(Point(1.5, 0.0))
|
||||
assert self.line1.line_interpolate_point(0.5, normalized=True).equals(
|
||||
Point(1, 0)
|
||||
)
|
||||
assert self.line1.line_interpolate_point(-0.5, normalized=True).equals(
|
||||
Point(1, 0)
|
||||
)
|
||||
|
||||
def test_line2_interpolate(self):
|
||||
assert self.line2.interpolate(0.5).equals(Point(3.0, 0.5))
|
||||
assert self.line2.interpolate(0.5, normalized=True).equals(Point(3, 3))
|
||||
|
||||
def test_multiline_interpolate(self):
|
||||
assert self.multiline.interpolate(0.5).equals(Point(0.5, 0))
|
||||
assert self.multiline.interpolate(0.5, normalized=True).equals(Point(3.0, 2.0))
|
||||
|
||||
def test_line_ends_interpolate(self):
|
||||
# Distances greater than length of the line or less than
|
||||
# zero yield the line's ends.
|
||||
assert self.line1.interpolate(-1000).equals(Point(0.0, 0.0))
|
||||
assert self.line1.interpolate(1000).equals(Point(2.0, 0.0))
|
||||
@@ -0,0 +1,44 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString, MultiLineString
|
||||
from shapely.ops import linemerge
|
||||
|
||||
|
||||
class LineMergeTestCase(unittest.TestCase):
|
||||
def test_linemerge(self):
|
||||
|
||||
lines = MultiLineString([[(0, 0), (1, 1)], [(2, 0), (2, 1), (1, 1)]])
|
||||
result = linemerge(lines)
|
||||
assert isinstance(result, LineString)
|
||||
assert not result.is_ring
|
||||
assert len(result.coords) == 4
|
||||
assert result.coords[0] == (0.0, 0.0)
|
||||
assert result.coords[3] == (2.0, 0.0)
|
||||
|
||||
lines2 = MultiLineString([((0, 0), (1, 1)), ((0, 0), (2, 0), (2, 1), (1, 1))])
|
||||
result = linemerge(lines2)
|
||||
assert result.is_ring
|
||||
assert len(result.coords) == 5
|
||||
|
||||
lines3 = [
|
||||
LineString([(0, 0), (1, 1)]),
|
||||
LineString([(0, 0), (0, 1)]),
|
||||
]
|
||||
result = linemerge(lines3)
|
||||
assert not result.is_ring
|
||||
assert len(result.coords) == 3
|
||||
assert result.coords[0] == (0.0, 1.0)
|
||||
assert result.coords[2] == (1.0, 1.0)
|
||||
|
||||
lines4 = [
|
||||
[(0, 0), (1, 1)],
|
||||
[(0, 0), (0, 1)],
|
||||
]
|
||||
assert result.equals(linemerge(lines4))
|
||||
|
||||
lines5 = [
|
||||
((0, 0), (1, 1)),
|
||||
((1, 0), (0, 1)),
|
||||
]
|
||||
result = linemerge(lines5)
|
||||
assert result.geom_type == "MultiLineString"
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Test locale independence of WKT
|
||||
"""
|
||||
import locale
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from shapely.wkt import dumps, loads
|
||||
|
||||
# Set locale to one that uses a comma as decimal separator
|
||||
# TODO: try a few other common locales
|
||||
if sys.platform == "win32":
|
||||
test_locales = {"Portuguese": "portuguese_brazil", "Italian": "italian_italy"}
|
||||
else:
|
||||
test_locales = {
|
||||
"Portuguese": "pt_BR.UTF-8",
|
||||
"Italian": "it_IT.UTF-8",
|
||||
}
|
||||
|
||||
do_test_locale = False
|
||||
|
||||
|
||||
def setUpModule():
|
||||
global do_test_locale
|
||||
for name in test_locales:
|
||||
try:
|
||||
test_locale = test_locales[name]
|
||||
locale.setlocale(locale.LC_ALL, test_locale)
|
||||
do_test_locale = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not do_test_locale:
|
||||
raise unittest.SkipTest("test locale not found")
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
if sys.platform == "win32" or sys.version_info[0:2] >= (3, 11):
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
else:
|
||||
# Deprecated since version 3.11, will be removed in version 3.13
|
||||
locale.resetlocale()
|
||||
|
||||
|
||||
class LocaleTestCase(unittest.TestCase):
|
||||
|
||||
# @unittest.skipIf(not do_test_locale, 'test locale not found')
|
||||
|
||||
def test_wkt_locale(self):
|
||||
|
||||
# Test reading and writing
|
||||
p = loads("POINT (0.0 0.0)")
|
||||
assert p.x == 0.0
|
||||
assert p.y == 0.0
|
||||
wkt = dumps(p)
|
||||
assert wkt.startswith("POINT")
|
||||
assert "," not in wkt
|
||||
@@ -0,0 +1,18 @@
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.tests.legacy.conftest import requires_geos_38
|
||||
from shapely.validation import make_valid
|
||||
|
||||
|
||||
@requires_geos_38
|
||||
def test_make_valid_invalid_input():
|
||||
geom = Polygon([(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)])
|
||||
valid = make_valid(geom)
|
||||
assert len(valid.geoms) == 2
|
||||
assert all(geom.geom_type == "Polygon" for geom in valid.geoms)
|
||||
|
||||
|
||||
@requires_geos_38
|
||||
def test_make_valid_input():
|
||||
geom = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
|
||||
valid = make_valid(geom)
|
||||
assert id(valid) == id(geom)
|
||||
@@ -0,0 +1,14 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import mapping, Point, Polygon
|
||||
|
||||
|
||||
class MappingTestCase(unittest.TestCase):
|
||||
def test_point(self):
|
||||
m = mapping(Point(0, 0))
|
||||
assert m["type"] == "Point"
|
||||
assert m["coordinates"] == (0.0, 0.0)
|
||||
|
||||
def test_empty_polygon(self):
|
||||
"""Empty polygons will round trip without error"""
|
||||
assert mapping(Polygon()) is not None
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Tests for the minimum clearance property.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geos import geos_version
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
requires_geos_36 = pytest.mark.skipif(
|
||||
geos_version < (3, 6, 0), reason="GEOS >= 3.6.0 is required."
|
||||
)
|
||||
|
||||
|
||||
@requires_geos_36
|
||||
def test_point():
|
||||
point = load_wkt("POINT (0 0)")
|
||||
assert point.minimum_clearance == math.inf
|
||||
|
||||
|
||||
@requires_geos_36
|
||||
def test_linestring():
|
||||
line = load_wkt("LINESTRING (0 0, 1 1, 2 2)")
|
||||
assert round(line.minimum_clearance, 6) == 1.414214
|
||||
|
||||
|
||||
@requires_geos_36
|
||||
def test_simple_polygon():
|
||||
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
|
||||
assert poly.minimum_clearance == 1.0
|
||||
|
||||
|
||||
@requires_geos_36
|
||||
def test_more_complicated_polygon():
|
||||
poly = load_wkt(
|
||||
"POLYGON ((20 20, 34 124, 70 140, 130 130, 70 100, 110 70, 170 20, 90 10, 20 20))"
|
||||
)
|
||||
assert round(poly.minimum_clearance, 6) == 35.777088
|
||||
@@ -0,0 +1,42 @@
|
||||
# Tests of support for Numpy ndarrays. See
|
||||
# https://github.com/sgillies/shapely/issues/26 for discussion.
|
||||
|
||||
import unittest
|
||||
from functools import reduce
|
||||
|
||||
import numpy as np
|
||||
|
||||
from shapely import geometry
|
||||
|
||||
|
||||
class TransposeTestCase(unittest.TestCase):
|
||||
def test_multipoint(self):
|
||||
arr = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
|
||||
tarr = arr.T
|
||||
shape = geometry.MultiPoint(tarr)
|
||||
coords = reduce(lambda x, y: x + y, [list(g.coords) for g in shape.geoms])
|
||||
assert coords == [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)]
|
||||
|
||||
def test_linestring(self):
|
||||
a = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
|
||||
t = a.T
|
||||
s = geometry.LineString(t)
|
||||
assert list(s.coords) == [
|
||||
(1.0, 3.0),
|
||||
(1.0, 4.0),
|
||||
(2.0, 4.0),
|
||||
(2.0, 3.0),
|
||||
(1.0, 3.0),
|
||||
]
|
||||
|
||||
def test_polygon(self):
|
||||
a = np.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]])
|
||||
t = a.T
|
||||
s = geometry.Polygon(t)
|
||||
assert list(s.exterior.coords) == [
|
||||
(1.0, 3.0),
|
||||
(1.0, 4.0),
|
||||
(2.0, 4.0),
|
||||
(2.0, 3.0),
|
||||
(1.0, 3.0),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import Point
|
||||
from shapely.ops import nearest_points
|
||||
|
||||
|
||||
class Nearest(unittest.TestCase):
|
||||
def test_nearest(self):
|
||||
first, second = nearest_points(
|
||||
Point(0, 0).buffer(1.0),
|
||||
Point(3, 0).buffer(1.0),
|
||||
)
|
||||
assert first.x == pytest.approx(1.0)
|
||||
assert second.x == pytest.approx(2.0)
|
||||
assert first.y == pytest.approx(0.0)
|
||||
assert second.y == pytest.approx(0.0)
|
||||
@@ -0,0 +1,122 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely import geos_version
|
||||
from shapely.errors import TopologicalError
|
||||
from shapely.geometry import GeometryCollection, LineString, MultiPoint, Point, Polygon
|
||||
from shapely.wkt import loads
|
||||
|
||||
|
||||
class OperationsTestCase(unittest.TestCase):
|
||||
def test_operations(self):
|
||||
point = Point(0.0, 0.0)
|
||||
|
||||
# General geometry
|
||||
assert point.area == 0.0
|
||||
assert point.length == 0.0
|
||||
assert point.distance(Point(-1.0, -1.0)) == pytest.approx(1.4142135623730951)
|
||||
|
||||
# Topology operations
|
||||
|
||||
# Envelope
|
||||
assert isinstance(point.envelope, Point)
|
||||
|
||||
# Intersection
|
||||
assert point.intersection(Point(-1, -1)).is_empty
|
||||
|
||||
# Buffer
|
||||
assert isinstance(point.buffer(10.0), Polygon)
|
||||
assert isinstance(point.buffer(10.0, 32), Polygon)
|
||||
|
||||
# Simplify
|
||||
p = loads(
|
||||
"POLYGON ((120 120, 140 199, 160 200, 180 199, 220 120, 122 122, 121 121, 120 120))"
|
||||
)
|
||||
expected = loads(
|
||||
"POLYGON ((120 120, 140 199, 160 200, 180 199, 220 120, 120 120))"
|
||||
)
|
||||
s = p.simplify(10.0, preserve_topology=False)
|
||||
assert s.equals_exact(expected, 0.001)
|
||||
|
||||
p = loads(
|
||||
"POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),"
|
||||
"(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))"
|
||||
)
|
||||
expected = loads(
|
||||
"POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),"
|
||||
"(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))"
|
||||
)
|
||||
s = p.simplify(10.0, preserve_topology=True)
|
||||
assert s.equals_exact(expected, 0.001)
|
||||
|
||||
# Convex Hull
|
||||
assert isinstance(point.convex_hull, Point)
|
||||
|
||||
# Differences
|
||||
assert isinstance(point.difference(Point(-1, 1)), Point)
|
||||
|
||||
assert isinstance(point.symmetric_difference(Point(-1, 1)), MultiPoint)
|
||||
|
||||
# Boundary
|
||||
assert isinstance(point.boundary, GeometryCollection)
|
||||
|
||||
# Union
|
||||
assert isinstance(point.union(Point(-1, 1)), MultiPoint)
|
||||
|
||||
assert isinstance(point.representative_point(), Point)
|
||||
assert isinstance(point.point_on_surface(), Point)
|
||||
assert point.representative_point() == point.point_on_surface()
|
||||
|
||||
assert isinstance(point.centroid, Point)
|
||||
|
||||
def test_relate(self):
|
||||
# Relate
|
||||
assert Point(0, 0).relate(Point(-1, -1)) == "FF0FFF0F2"
|
||||
|
||||
# issue #294: should raise TopologicalError on exception
|
||||
invalid_polygon = loads(
|
||||
"POLYGON ((40 100, 80 100, 80 60, 40 60, 40 100), (60 60, 80 60, 80 40, 60 40, 60 60))"
|
||||
)
|
||||
assert not invalid_polygon.is_valid
|
||||
if geos_version < (3, 13, 0):
|
||||
with pytest.raises((TopologicalError, shapely.GEOSException)):
|
||||
invalid_polygon.relate(invalid_polygon)
|
||||
else: # resolved with RelateNG
|
||||
assert invalid_polygon.relate(invalid_polygon) == "2FFF1FFF2"
|
||||
|
||||
def test_hausdorff_distance(self):
|
||||
point = Point(1, 1)
|
||||
line = LineString([(2, 0), (2, 4), (3, 4)])
|
||||
|
||||
distance = point.hausdorff_distance(line)
|
||||
assert distance == point.distance(Point(3, 4))
|
||||
|
||||
def test_interpolate(self):
|
||||
# successful interpolation
|
||||
test_line = LineString([(1, 1), (1, 2)])
|
||||
known_point = Point(1, 1.5)
|
||||
interpolated_point = test_line.interpolate(0.5, normalized=True)
|
||||
assert interpolated_point == known_point
|
||||
|
||||
# Issue #653; should nog segfault for empty geometries
|
||||
empty_line = loads("LINESTRING EMPTY")
|
||||
assert empty_line.is_empty
|
||||
interpolated_point = empty_line.interpolate(0.5, normalized=True)
|
||||
assert interpolated_point.is_empty
|
||||
|
||||
# invalid geometry should raise TypeError on exception
|
||||
polygon = loads("POLYGON EMPTY")
|
||||
with pytest.raises(TypeError, match="incorrect geometry type"):
|
||||
polygon.interpolate(0.5, normalized=True)
|
||||
|
||||
def test_normalize(self):
|
||||
point = Point(1, 1)
|
||||
result = point.normalize()
|
||||
assert result == point
|
||||
|
||||
line = loads("MULTILINESTRING ((1 1, 0 0), (1 1, 1 2))")
|
||||
result = line.normalize()
|
||||
expected = loads("MULTILINESTRING ((1 1, 1 2), (0 0, 1 1))")
|
||||
assert result == expected
|
||||
@@ -0,0 +1,60 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString, MultiPoint, Point, Polygon
|
||||
|
||||
|
||||
class OperatorsTestCase(unittest.TestCase):
|
||||
def test_point(self):
|
||||
point = Point(0, 0)
|
||||
point2 = Point(-1, 1)
|
||||
assert point.union(point2).equals(point | point2)
|
||||
assert (point & point2).is_empty
|
||||
assert point.equals(point - point2)
|
||||
assert point.symmetric_difference(point2).equals(point ^ point2)
|
||||
assert point != point2
|
||||
point_dupe = Point(0, 0)
|
||||
assert point, point_dupe
|
||||
|
||||
def test_multipoint(self):
|
||||
mp1 = MultiPoint([(0, 0), (1, 1)])
|
||||
mp1_dup = MultiPoint([(0, 0), (1, 1)])
|
||||
mp1_rev = MultiPoint([(1, 1), (0, 0)])
|
||||
mp2 = MultiPoint([(0, 0), (1, 1), (2, 2)])
|
||||
mp3 = MultiPoint([(0, 0), (1, 1), (2, 3)])
|
||||
|
||||
assert mp1 == mp1_dup
|
||||
assert mp1 != mp1_rev
|
||||
assert mp1 != mp2
|
||||
assert mp2 != mp3
|
||||
|
||||
p = Point(0, 0)
|
||||
mp = MultiPoint([(0, 0)])
|
||||
assert p != mp
|
||||
assert mp != p
|
||||
|
||||
def test_polygon(self):
|
||||
shell = ((0, 0), (3, 0), (3, 3), (0, 3))
|
||||
hole = ((1, 1), (2, 1), (2, 2), (1, 2))
|
||||
p_solid = Polygon(shell)
|
||||
p2_solid = Polygon(shell)
|
||||
p_hole = Polygon(shell, holes=[hole])
|
||||
p2_hole = Polygon(shell, holes=[hole])
|
||||
|
||||
assert p_solid == p2_solid
|
||||
assert p_hole == p2_hole
|
||||
assert p_solid != p_hole
|
||||
|
||||
shell2 = ((-5, 2), (10.5, 3), (7, 3))
|
||||
p3_hole = Polygon(shell2, holes=[hole])
|
||||
assert p_hole != p3_hole
|
||||
|
||||
def test_linestring(self):
|
||||
line1 = LineString([(0, 0), (1, 1), (2, 2)])
|
||||
line2 = LineString([(0, 0), (2, 2)])
|
||||
line2_dup = LineString([(0, 0), (2, 2)])
|
||||
# .equals() indicates these are the same
|
||||
assert line1.equals(line2)
|
||||
# but != indicates these are different
|
||||
assert line1 != line2
|
||||
# but dupes are the same with ==
|
||||
assert line2 == line2_dup
|
||||
@@ -0,0 +1,64 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import (
|
||||
GeometryCollection,
|
||||
LinearRing,
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
)
|
||||
from shapely.ops import orient
|
||||
|
||||
|
||||
class OrientTestCase(unittest.TestCase):
|
||||
def test_point(self):
|
||||
point = Point(0, 0)
|
||||
assert orient(point, 1) == point
|
||||
assert orient(point, -1) == point
|
||||
|
||||
def test_multipoint(self):
|
||||
multipoint = MultiPoint([(0, 0), (1, 1)])
|
||||
assert orient(multipoint, 1) == multipoint
|
||||
assert orient(multipoint, -1) == multipoint
|
||||
|
||||
def test_linestring(self):
|
||||
linestring = LineString([(0, 0), (1, 1)])
|
||||
assert orient(linestring, 1) == linestring
|
||||
assert orient(linestring, -1) == linestring
|
||||
|
||||
def test_multilinestring(self):
|
||||
multilinestring = MultiLineString([[(0, 0), (1, 1)], [(1, 0), (0, 1)]])
|
||||
assert orient(multilinestring, 1) == multilinestring
|
||||
assert orient(multilinestring, -1) == multilinestring
|
||||
|
||||
def test_linearring(self):
|
||||
linearring = LinearRing([(0, 0), (0, 1), (1, 0)])
|
||||
assert orient(linearring, 1) == linearring
|
||||
assert orient(linearring, -1) == linearring
|
||||
|
||||
def test_polygon(self):
|
||||
polygon = Polygon([(0, 0), (0, 1), (1, 0)])
|
||||
polygon_reversed = Polygon(polygon.exterior.coords[::-1])
|
||||
assert (orient(polygon, 1)) == polygon_reversed
|
||||
assert (orient(polygon, -1)) == polygon
|
||||
|
||||
def test_multipolygon(self):
|
||||
polygon1 = Polygon([(0, 0), (0, 1), (1, 0)])
|
||||
polygon2 = Polygon([(1, 0), (2, 0), (2, 1)])
|
||||
polygon1_reversed = Polygon(polygon1.exterior.coords[::-1])
|
||||
polygon2_reversed = Polygon(polygon2.exterior.coords[::-1])
|
||||
multipolygon = MultiPolygon([polygon1, polygon2])
|
||||
assert not polygon1.exterior.is_ccw
|
||||
assert polygon2.exterior.is_ccw
|
||||
assert orient(multipolygon, 1) == MultiPolygon([polygon1_reversed, polygon2])
|
||||
assert orient(multipolygon, -1) == MultiPolygon([polygon1, polygon2_reversed])
|
||||
|
||||
def test_geometrycollection(self):
|
||||
polygon = Polygon([(0, 0), (0, 1), (1, 0)])
|
||||
polygon_reversed = Polygon(polygon.exterior.coords[::-1])
|
||||
collection = GeometryCollection([polygon])
|
||||
assert orient(collection, 1) == GeometryCollection([polygon_reversed])
|
||||
assert orient(collection, -1) == GeometryCollection([polygon])
|
||||
@@ -0,0 +1,60 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import LinearRing, LineString
|
||||
from shapely.testing import assert_geometries_equal
|
||||
|
||||
|
||||
@pytest.mark.parametrize("distance", [float("nan"), float("inf")])
|
||||
def test_non_finite_distance(distance):
|
||||
g = LineString([(0, 0), (10, 0)])
|
||||
with pytest.raises(ValueError, match="distance must be finite"):
|
||||
g.parallel_offset(distance)
|
||||
|
||||
|
||||
class OperationsTestCase(unittest.TestCase):
|
||||
def test_parallel_offset_linestring(self):
|
||||
line1 = LineString([(0, 0), (10, 0)])
|
||||
left = line1.parallel_offset(5, "left")
|
||||
assert_geometries_equal(left, LineString([(0, 5), (10, 5)]))
|
||||
right = line1.parallel_offset(5, "right")
|
||||
assert_geometries_equal(right, LineString([(10, -5), (0, -5)]), normalize=True)
|
||||
right = line1.parallel_offset(-5, "left")
|
||||
assert_geometries_equal(right, LineString([(10, -5), (0, -5)]), normalize=True)
|
||||
left = line1.parallel_offset(-5, "right")
|
||||
assert_geometries_equal(left, LineString([(0, 5), (10, 5)]))
|
||||
|
||||
# by default, parallel_offset is right-handed
|
||||
assert_geometries_equal(line1.parallel_offset(5), right)
|
||||
|
||||
line2 = LineString([(0, 0), (5, 0), (5, -5)])
|
||||
assert_geometries_equal(
|
||||
line2.parallel_offset(2, "left", join_style=3),
|
||||
LineString([(0, 2), (5, 2), (7, 0), (7, -5)]),
|
||||
)
|
||||
assert_geometries_equal(
|
||||
line2.parallel_offset(2, "left", join_style=2),
|
||||
LineString([(0, 2), (7, 2), (7, -5)]),
|
||||
)
|
||||
# offset_curve alias
|
||||
assert_geometries_equal(
|
||||
line1.offset_curve(2, quad_segs=10),
|
||||
line1.parallel_offset(2, "left", resolution=10),
|
||||
)
|
||||
assert_geometries_equal(
|
||||
line1.offset_curve(-2, join_style="mitre"),
|
||||
line1.parallel_offset(2, "right", join_style=2),
|
||||
)
|
||||
|
||||
def test_parallel_offset_linear_ring(self):
|
||||
lr1 = LinearRing([(0, 0), (5, 0), (5, 5), (0, 5), (0, 0)])
|
||||
assert_geometries_equal(
|
||||
lr1.parallel_offset(2, "left", resolution=1),
|
||||
LineString([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)]),
|
||||
)
|
||||
# offset_curve alias
|
||||
assert_geometries_equal(
|
||||
lr1.offset_curve(2, quad_segs=1),
|
||||
lr1.parallel_offset(2, "left", resolution=1),
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Persistence tests
|
||||
"""
|
||||
import pickle
|
||||
import struct
|
||||
import unittest
|
||||
|
||||
from shapely import wkb, wkt
|
||||
from shapely.geometry import Point
|
||||
|
||||
|
||||
class PersistTestCase(unittest.TestCase):
|
||||
def test_pickle(self):
|
||||
|
||||
p = Point(0.0, 0.0)
|
||||
data = pickle.dumps(p)
|
||||
q = pickle.loads(data)
|
||||
assert q.equals(p)
|
||||
|
||||
def test_wkb(self):
|
||||
|
||||
p = Point(0.0, 0.0)
|
||||
wkb_big_endian = wkb.dumps(p, big_endian=True)
|
||||
wkb_little_endian = wkb.dumps(p, big_endian=False)
|
||||
# Regardless of byte order, loads ought to correctly recover the
|
||||
# geometry
|
||||
assert p.equals(wkb.loads(wkb_big_endian))
|
||||
assert p.equals(wkb.loads(wkb_little_endian))
|
||||
|
||||
def test_wkb_dumps_endianness(self):
|
||||
|
||||
p = Point(0.5, 2.0)
|
||||
wkb_big_endian = wkb.dumps(p, big_endian=True)
|
||||
wkb_little_endian = wkb.dumps(p, big_endian=False)
|
||||
assert wkb_big_endian != wkb_little_endian
|
||||
# According to WKB specification in section 3.3 of OpenGIS
|
||||
# Simple Features Specification for SQL, revision 1.1, the
|
||||
# first byte of a WKB representation indicates byte order.
|
||||
# Big-endian is 0, little-endian is 1.
|
||||
assert wkb_big_endian[0] == 0
|
||||
assert wkb_little_endian[0] == 1
|
||||
# Check that the doubles (0.5, 2.0) are in correct byte order
|
||||
double_size = struct.calcsize("d")
|
||||
assert wkb_big_endian[(-2 * double_size) :] == struct.pack(">2d", p.x, p.y)
|
||||
assert wkb_little_endian[(-2 * double_size) :] == struct.pack("<2d", p.x, p.y)
|
||||
|
||||
def test_wkt(self):
|
||||
p = Point(0.0, 0.0)
|
||||
text = wkt.dumps(p)
|
||||
assert text.startswith("POINT")
|
||||
pt = wkt.loads(text)
|
||||
assert pt.equals(p)
|
||||
@@ -0,0 +1,84 @@
|
||||
import pathlib
|
||||
import pickle
|
||||
import warnings
|
||||
from pickle import dumps, HIGHEST_PROTOCOL, loads
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely import wkt
|
||||
from shapely.geometry import (
|
||||
box,
|
||||
GeometryCollection,
|
||||
LinearRing,
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
)
|
||||
|
||||
HERE = pathlib.Path(__file__).parent
|
||||
|
||||
|
||||
TEST_DATA = {
|
||||
"point2d": Point([(1.0, 2.0)]),
|
||||
"point3d": Point([(1.0, 2.0, 3.0)]),
|
||||
"linestring": LineString([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]),
|
||||
"linearring": LinearRing([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
|
||||
"polygon": Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
|
||||
"multipoint": MultiPoint([(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)]),
|
||||
"multilinestring": MultiLineString(
|
||||
[[(0.0, 0.0), (1.0, 1.0)], [(1.0, 2.0), (3.0, 3.0)]]
|
||||
),
|
||||
"multipolygon": MultiPolygon([box(0, 0, 1, 1), box(2, 2, 3, 3)]),
|
||||
"geometrycollection": GeometryCollection([Point(1.0, 2.0), box(0, 0, 1, 1)]),
|
||||
"emptypoint": wkt.loads("POINT EMPTY"),
|
||||
"emptypolygon": wkt.loads("POLYGON EMPTY"),
|
||||
}
|
||||
TEST_NAMES, TEST_GEOMS = zip(*TEST_DATA.items())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("geom1", TEST_GEOMS, ids=TEST_NAMES)
|
||||
def test_pickle_round_trip(geom1):
|
||||
data = dumps(geom1, HIGHEST_PROTOCOL)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
geom2 = loads(data)
|
||||
assert geom2.has_z == geom1.has_z
|
||||
assert type(geom2) is type(geom1)
|
||||
assert geom2.geom_type == geom1.geom_type
|
||||
assert geom2.wkt == geom1.wkt
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fname", (HERE / "data").glob("*.pickle"), ids=lambda fname: fname.name
|
||||
)
|
||||
def test_unpickle_pre_20(fname):
|
||||
from shapely.testing import assert_geometries_equal
|
||||
|
||||
geom_type = fname.name.split("_")[0]
|
||||
expected = TEST_DATA[geom_type]
|
||||
|
||||
with open(fname, "rb") as f:
|
||||
with pytest.warns(UserWarning):
|
||||
result = pickle.load(f)
|
||||
|
||||
assert_geometries_equal(result, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
datadir = HERE / "data"
|
||||
datadir.mkdir(exist_ok=True)
|
||||
|
||||
shapely_version = shapely.__version__
|
||||
print(shapely_version)
|
||||
print(shapely.geos.geos_version)
|
||||
|
||||
for name, geom in TEST_DATA.items():
|
||||
if name == "emptypoint" and shapely.geos.geos_version < (3, 9, 0):
|
||||
# Empty Points cannot be represented in WKB
|
||||
continue
|
||||
with open(datadir / f"{name}_{shapely_version}.pickle", "wb") as f:
|
||||
pickle.dump(geom, f)
|
||||
@@ -0,0 +1,44 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString, Point, Polygon
|
||||
from shapely.geometry.base import dump_coords
|
||||
from shapely.ops import polygonize, polygonize_full
|
||||
|
||||
|
||||
class PolygonizeTestCase(unittest.TestCase):
|
||||
def test_polygonize(self):
|
||||
lines = [
|
||||
LineString([(0, 0), (1, 1)]),
|
||||
LineString([(0, 0), (0, 1)]),
|
||||
LineString([(0, 1), (1, 1)]),
|
||||
LineString([(1, 1), (1, 0)]),
|
||||
LineString([(1, 0), (0, 0)]),
|
||||
LineString([(5, 5), (6, 6)]),
|
||||
Point(0, 0),
|
||||
]
|
||||
result = list(polygonize(lines))
|
||||
assert all(isinstance(x, Polygon) for x in result)
|
||||
|
||||
def test_polygonize_full(self):
|
||||
|
||||
lines2 = [
|
||||
[(0, 0), (1, 1)],
|
||||
[(0, 0), (0, 1)],
|
||||
[(0, 1), (1, 1)],
|
||||
[(1, 1), (1, 0)],
|
||||
[(1, 0), (0, 0)],
|
||||
[(5, 5), (6, 6)],
|
||||
[(1, 1), (100, 100)],
|
||||
]
|
||||
|
||||
result2, cuts, dangles, invalids = polygonize_full(lines2)
|
||||
assert len(result2.geoms) == 2
|
||||
assert all(isinstance(x, Polygon) for x in result2.geoms)
|
||||
assert list(cuts.geoms) == []
|
||||
assert all(isinstance(x, LineString) for x in dangles.geoms)
|
||||
|
||||
assert dump_coords(dangles) == [
|
||||
[(1.0, 1.0), (100.0, 100.0)],
|
||||
[(5.0, 5.0), (6.0, 6.0)],
|
||||
]
|
||||
assert list(invalids.geoms) == []
|
||||
@@ -0,0 +1,97 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.algorithms.polylabel import Cell, polylabel
|
||||
from shapely.errors import TopologicalError
|
||||
from shapely.geometry import LineString, Point, Polygon
|
||||
|
||||
|
||||
class PolylabelTestCase(unittest.TestCase):
|
||||
def test_polylabel(self):
|
||||
"""
|
||||
Finds pole of inaccessibility for a polygon with a tolerance of 10
|
||||
|
||||
"""
|
||||
polygon = LineString(
|
||||
[(0, 0), (50, 200), (100, 100), (20, 50), (-100, -20), (-150, -200)]
|
||||
).buffer(100)
|
||||
label = polylabel(polygon, tolerance=10)
|
||||
expected = Point(59.35615556364569, 121.8391962974644)
|
||||
assert expected.equals_exact(label, 1e-6)
|
||||
|
||||
def test_invalid_polygon(self):
|
||||
"""
|
||||
Makes sure that the polylabel function throws an exception when provided
|
||||
an invalid polygon.
|
||||
|
||||
"""
|
||||
bowtie_polygon = Polygon(
|
||||
[(0, 0), (0, 20), (10, 10), (20, 20), (20, 0), (10, 10), (0, 0)]
|
||||
)
|
||||
with pytest.raises(TopologicalError):
|
||||
polylabel(bowtie_polygon)
|
||||
|
||||
def test_cell_sorting(self):
|
||||
"""
|
||||
Tests rich comparison operators of Cells for use in the polylabel
|
||||
minimum priority queue.
|
||||
|
||||
"""
|
||||
polygon = Point(0, 0).buffer(100)
|
||||
cell1 = Cell(0, 0, 50, polygon) # closest
|
||||
cell2 = Cell(50, 50, 50, polygon) # furthest
|
||||
assert cell1 < cell2
|
||||
assert cell1 <= cell2
|
||||
assert (cell2 <= cell1) is False
|
||||
assert cell1 == cell1
|
||||
assert (cell1 == cell2) is False
|
||||
assert cell1 != cell2
|
||||
assert (cell1 != cell1) is False
|
||||
assert cell2 > cell1
|
||||
assert (cell1 > cell2) is False
|
||||
assert cell2 >= cell1
|
||||
assert (cell1 >= cell2) is False
|
||||
|
||||
def test_concave_polygon(self):
|
||||
"""
|
||||
Finds pole of inaccessibility for a concave polygon and ensures that
|
||||
the point is inside.
|
||||
|
||||
"""
|
||||
concave_polygon = LineString([(500, 0), (0, 0), (0, 500), (500, 500)]).buffer(
|
||||
100
|
||||
)
|
||||
label = polylabel(concave_polygon)
|
||||
assert concave_polygon.contains(label)
|
||||
|
||||
def test_rectangle_special_case(self):
|
||||
"""
|
||||
The centroid algorithm used is vulnerable to floating point errors
|
||||
and can give unexpected results for rectangular polygons. Test
|
||||
that this special case is handled correctly.
|
||||
https://github.com/mapbox/polylabel/issues/3
|
||||
"""
|
||||
polygon = Polygon(
|
||||
[
|
||||
(32.71997, -117.19310),
|
||||
(32.71997, -117.21065),
|
||||
(32.72408, -117.21065),
|
||||
(32.72408, -117.19310),
|
||||
]
|
||||
)
|
||||
label = polylabel(polygon)
|
||||
assert label.coords[:] == [(32.722025, -117.201875)]
|
||||
|
||||
def test_polygon_with_hole(self):
|
||||
"""
|
||||
Finds pole of inaccessibility for a polygon with a hole
|
||||
https://github.com/shapely/shapely/issues/817
|
||||
"""
|
||||
polygon = Polygon(
|
||||
shell=[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
|
||||
holes=[[(2, 2), (6, 2), (6, 6), (2, 6), (2, 2)]],
|
||||
)
|
||||
label = polylabel(polygon, 0.05)
|
||||
assert label.x == pytest.approx(7.65625)
|
||||
assert label.y == pytest.approx(7.65625)
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Test GEOS predicates
|
||||
"""
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
import shapely
|
||||
from shapely import geos_version
|
||||
from shapely.geometry import Point, Polygon
|
||||
|
||||
|
||||
class PredicatesTestCase(unittest.TestCase):
|
||||
def test_binary_predicates(self):
|
||||
|
||||
point = Point(0.0, 0.0)
|
||||
point2 = Point(2.0, 2.0)
|
||||
|
||||
assert point.disjoint(Point(-1.0, -1.0))
|
||||
assert not point.touches(Point(-1.0, -1.0))
|
||||
assert not point.crosses(Point(-1.0, -1.0))
|
||||
assert not point.within(Point(-1.0, -1.0))
|
||||
assert not point.contains(Point(-1.0, -1.0))
|
||||
assert not point.equals(Point(-1.0, -1.0))
|
||||
assert not point.touches(Point(-1.0, -1.0))
|
||||
assert point.equals(Point(0.0, 0.0))
|
||||
assert point.covers(Point(0.0, 0.0))
|
||||
assert point.covered_by(Point(0.0, 0.0))
|
||||
assert not point.covered_by(point2)
|
||||
assert not point2.covered_by(point)
|
||||
assert not point.covers(Point(-1.0, -1.0))
|
||||
|
||||
def test_unary_predicates(self):
|
||||
|
||||
point = Point(0.0, 0.0)
|
||||
|
||||
assert not point.is_empty
|
||||
assert point.is_valid
|
||||
assert point.is_simple
|
||||
assert not point.is_ring
|
||||
assert not point.has_z
|
||||
|
||||
def test_binary_predicate_exceptions(self):
|
||||
|
||||
p1 = [
|
||||
(339, 346),
|
||||
(459, 346),
|
||||
(399, 311),
|
||||
(340, 277),
|
||||
(399, 173),
|
||||
(280, 242),
|
||||
(339, 415),
|
||||
(280, 381),
|
||||
(460, 207),
|
||||
(339, 346),
|
||||
]
|
||||
p2 = [
|
||||
(339, 207),
|
||||
(280, 311),
|
||||
(460, 138),
|
||||
(399, 242),
|
||||
(459, 277),
|
||||
(459, 415),
|
||||
(399, 381),
|
||||
(519, 311),
|
||||
(520, 242),
|
||||
(519, 173),
|
||||
(399, 450),
|
||||
(339, 207),
|
||||
]
|
||||
|
||||
g1 = Polygon(p1)
|
||||
g2 = Polygon(p2)
|
||||
assert not g1.is_valid
|
||||
assert not g2.is_valid
|
||||
if geos_version < (3, 13, 0):
|
||||
with pytest.raises(shapely.GEOSException):
|
||||
g1.within(g2)
|
||||
else: # resolved with RelateNG
|
||||
assert not g1.within(g2)
|
||||
|
||||
def test_relate_pattern(self):
|
||||
|
||||
# a pair of partially overlapping polygons, and a nearby point
|
||||
g1 = Polygon([(0, 0), (0, 1), (3, 1), (3, 0), (0, 0)])
|
||||
g2 = Polygon([(1, -1), (1, 2), (2, 2), (2, -1), (1, -1)])
|
||||
g3 = Point(5, 5)
|
||||
|
||||
assert g1.relate(g2) == "212101212"
|
||||
assert g1.relate_pattern(g2, "212101212")
|
||||
assert g1.relate_pattern(g2, "*********")
|
||||
assert g1.relate_pattern(g2, "2********")
|
||||
assert g1.relate_pattern(g2, "T********")
|
||||
assert not g1.relate_pattern(g2, "112101212")
|
||||
assert not g1.relate_pattern(g2, "1********")
|
||||
assert g1.relate_pattern(g3, "FF2FF10F2")
|
||||
|
||||
# an invalid pattern should raise an exception
|
||||
with pytest.raises(shapely.GEOSException, match="IllegalArgumentException"):
|
||||
g1.relate_pattern(g2, "fail")
|
||||
@@ -0,0 +1,62 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import Point, Polygon
|
||||
from shapely.prepared import prep, PreparedGeometry
|
||||
|
||||
|
||||
def test_prepared_geometry():
|
||||
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
p = PreparedGeometry(polygon)
|
||||
assert p.contains(Point(0.5, 0.5))
|
||||
assert not p.contains(Point(0.5, 1.5))
|
||||
|
||||
|
||||
def test_prep():
|
||||
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
p = prep(polygon)
|
||||
assert p.contains(Point(0.5, 0.5))
|
||||
assert not p.contains(Point(0.5, 1.5))
|
||||
|
||||
|
||||
def test_op_not_allowed():
|
||||
p = PreparedGeometry(Point(0.0, 0.0).buffer(1.0))
|
||||
with pytest.raises(TypeError):
|
||||
Point(0.0, 0.0).union(p)
|
||||
|
||||
|
||||
def test_predicate_not_allowed():
|
||||
p = PreparedGeometry(Point(0.0, 0.0).buffer(1.0))
|
||||
with pytest.raises(TypeError):
|
||||
Point(0.0, 0.0).contains(p)
|
||||
|
||||
|
||||
def test_prepared_predicates():
|
||||
# check prepared predicates give the same result as regular predicates
|
||||
polygon1 = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
|
||||
polygon2 = Polygon([(0.5, 0.5), (1.5, 0.5), (1.0, 1.0), (0.5, 0.5)])
|
||||
point2 = Point(0.5, 0.5)
|
||||
polygon_empty = Polygon()
|
||||
prepared_polygon1 = PreparedGeometry(polygon1)
|
||||
for geom2 in (polygon2, point2, polygon_empty):
|
||||
with np.errstate(invalid="ignore"):
|
||||
assert polygon1.disjoint(geom2) == prepared_polygon1.disjoint(geom2)
|
||||
assert polygon1.touches(geom2) == prepared_polygon1.touches(geom2)
|
||||
assert polygon1.intersects(geom2) == prepared_polygon1.intersects(geom2)
|
||||
assert polygon1.crosses(geom2) == prepared_polygon1.crosses(geom2)
|
||||
assert polygon1.within(geom2) == prepared_polygon1.within(geom2)
|
||||
assert polygon1.contains(geom2) == prepared_polygon1.contains(geom2)
|
||||
assert polygon1.overlaps(geom2) == prepared_polygon1.overlaps(geom2)
|
||||
|
||||
|
||||
def test_prepare_already_prepared():
|
||||
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
prepared = prep(polygon)
|
||||
# attempt to prepare an already prepared geometry with `prep`
|
||||
result = prep(prepared)
|
||||
assert isinstance(result, PreparedGeometry)
|
||||
assert result.context is polygon
|
||||
# attempt to prepare an already prepared geometry with `PreparedGeometry`
|
||||
result = PreparedGeometry(prepared)
|
||||
assert isinstance(result, PreparedGeometry)
|
||||
assert result.context is polygon
|
||||
@@ -0,0 +1,13 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString
|
||||
|
||||
|
||||
class ProductZTestCase(unittest.TestCase):
|
||||
def test_line_intersection(self):
|
||||
line1 = LineString([(0, 0, 0), (1, 1, 1)])
|
||||
line2 = LineString([(0, 1, 1), (1, 0, 0)])
|
||||
interxn = line1.intersection(line2)
|
||||
assert interxn.has_z
|
||||
assert interxn._ndim == 3
|
||||
assert 0.0 <= interxn.z <= 1.0
|
||||
@@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import MultiLineString, Polygon, shape
|
||||
from shapely.geometry.geo import _is_coordinates_empty
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"geom",
|
||||
[{"type": "Polygon", "coordinates": None}, {"type": "Polygon", "coordinates": []}],
|
||||
)
|
||||
def test_polygon_no_coords(geom):
|
||||
assert shape(geom) == Polygon()
|
||||
|
||||
|
||||
def test_polygon_empty_np_array():
|
||||
np = pytest.importorskip("numpy")
|
||||
geom = {"type": "Polygon", "coordinates": np.array([])}
|
||||
assert shape(geom) == Polygon()
|
||||
|
||||
|
||||
def test_polygon_with_coords_list():
|
||||
geom = {"type": "Polygon", "coordinates": [[[5, 10], [10, 10], [10, 5]]]}
|
||||
obj = shape(geom)
|
||||
assert obj == Polygon([(5, 10), (10, 10), (10, 5)])
|
||||
|
||||
|
||||
def test_polygon_not_empty_np_array():
|
||||
np = pytest.importorskip("numpy")
|
||||
geom = {"type": "Polygon", "coordinates": np.array([[[5, 10], [10, 10], [10, 5]]])}
|
||||
obj = shape(geom)
|
||||
assert obj == Polygon([(5, 10), (10, 10), (10, 5)])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"geom",
|
||||
[
|
||||
{"type": "MultiLineString", "coordinates": []},
|
||||
{"type": "MultiLineString", "coordinates": [[]]},
|
||||
{"type": "MultiLineString", "coordinates": None},
|
||||
],
|
||||
)
|
||||
def test_multilinestring_empty(geom):
|
||||
assert shape(geom) == MultiLineString()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("coords", [[], [[]], [[], []], None, [[[]]]])
|
||||
def test_is_coordinates_empty(coords):
|
||||
assert _is_coordinates_empty(coords)
|
||||
@@ -0,0 +1,45 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.errors import GeometryTypeError
|
||||
from shapely.geometry import GeometryCollection, LineString, MultiLineString, Point
|
||||
from shapely.ops import shared_paths
|
||||
|
||||
|
||||
class SharedPaths(unittest.TestCase):
|
||||
def test_shared_paths_forward(self):
|
||||
g1 = LineString([(0, 0), (10, 0), (10, 5), (20, 5)])
|
||||
g2 = LineString([(5, 0), (15, 0)])
|
||||
result = shared_paths(g1, g2)
|
||||
|
||||
assert isinstance(result, GeometryCollection)
|
||||
assert len(result.geoms) == 2
|
||||
a, b = result.geoms
|
||||
assert isinstance(a, MultiLineString)
|
||||
assert len(a.geoms) == 1
|
||||
assert a.geoms[0].coords[:] == [(5, 0), (10, 0)]
|
||||
assert b.is_empty
|
||||
|
||||
def test_shared_paths_forward2(self):
|
||||
g1 = LineString([(0, 0), (10, 0), (10, 5), (20, 5)])
|
||||
g2 = LineString([(15, 0), (5, 0)])
|
||||
result = shared_paths(g1, g2)
|
||||
|
||||
assert isinstance(result, GeometryCollection)
|
||||
assert len(result.geoms) == 2
|
||||
a, b = result.geoms
|
||||
assert isinstance(b, MultiLineString)
|
||||
assert len(b.geoms) == 1
|
||||
assert b.geoms[0].coords[:] == [(5, 0), (10, 0)]
|
||||
assert a.is_empty
|
||||
|
||||
def test_wrong_type(self):
|
||||
g1 = Point(0, 0)
|
||||
g2 = LineString([(5, 0), (15, 0)])
|
||||
|
||||
with pytest.raises(GeometryTypeError):
|
||||
shared_paths(g1, g2)
|
||||
|
||||
with pytest.raises(GeometryTypeError):
|
||||
shared_paths(g2, g1)
|
||||
@@ -0,0 +1,15 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
|
||||
class PolygonTestCase(unittest.TestCase):
|
||||
def test_polygon_3(self):
|
||||
p = (1.0, 1.0)
|
||||
poly = Polygon([p, p, p])
|
||||
assert poly.bounds == (1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
def test_polygon_5(self):
|
||||
p = (1.0, 1.0)
|
||||
poly = Polygon([p, p, p, p, p])
|
||||
assert poly.bounds == (1.0, 1.0, 1.0, 1.0)
|
||||
@@ -0,0 +1,25 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import LineString, Polygon
|
||||
from shapely.ops import snap
|
||||
|
||||
|
||||
class Snap(unittest.TestCase):
|
||||
def test_snap(self):
|
||||
|
||||
# input geometries
|
||||
square = Polygon([(1, 1), (2, 1), (2, 2), (1, 2), (1, 1)])
|
||||
line = LineString([(0, 0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)])
|
||||
|
||||
square_coords = square.exterior.coords[:]
|
||||
line_coords = line.coords[:]
|
||||
|
||||
result = snap(line, square, 0.5)
|
||||
|
||||
# test result is correct
|
||||
assert isinstance(result, LineString)
|
||||
assert result.coords[:] == [(0.0, 0.0), (1.0, 1.0), (2.0, 1.0), (2.6, 0.5)]
|
||||
|
||||
# test inputs have not been modified
|
||||
assert square.exterior.coords[:] == square_coords
|
||||
assert line.coords[:] == line_coords
|
||||
@@ -0,0 +1,249 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.errors import GeometryTypeError
|
||||
from shapely.geometry import (
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
)
|
||||
from shapely.ops import linemerge, split, unary_union
|
||||
|
||||
|
||||
class TestSplitGeometry(unittest.TestCase):
|
||||
# helper class for testing below
|
||||
def helper(self, geom, splitter, expected_chunks):
|
||||
s = split(geom, splitter)
|
||||
assert s.geom_type == "GeometryCollection"
|
||||
assert len(s.geoms) == expected_chunks
|
||||
if expected_chunks > 1:
|
||||
# split --> expected collection that when merged is again equal to original geometry
|
||||
if s.geoms[0].geom_type == "LineString":
|
||||
self.assertTrue(linemerge(s).simplify(0.000001).equals(geom))
|
||||
elif s.geoms[0].geom_type == "Polygon":
|
||||
union = unary_union(s).simplify(0.000001)
|
||||
assert union.equals(geom)
|
||||
assert union.area == geom.area
|
||||
else:
|
||||
raise ValueError
|
||||
elif expected_chunks == 1:
|
||||
# not split --> expected equal to line
|
||||
assert s.geoms[0].equals(geom)
|
||||
|
||||
def test_split_closed_line_with_point(self):
|
||||
# point at start/end of closed ring -> return equal
|
||||
# see GH #524
|
||||
ls = LineString([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
|
||||
splitter = Point(0, 0)
|
||||
self.helper(ls, splitter, 1)
|
||||
|
||||
|
||||
class TestSplitPolygon(TestSplitGeometry):
|
||||
poly_simple = Polygon([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
|
||||
poly_hole = Polygon(
|
||||
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)],
|
||||
[[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]],
|
||||
)
|
||||
|
||||
def test_split_poly_with_line(self):
|
||||
# crossing at 2 points --> return 2 segments
|
||||
splitter = LineString([(1, 3), (1, -3)])
|
||||
self.helper(self.poly_simple, splitter, 2)
|
||||
self.helper(self.poly_hole, splitter, 2)
|
||||
|
||||
# touching the boundary--> return equal
|
||||
splitter = LineString([(0, 2), (5, 2)])
|
||||
self.helper(self.poly_simple, splitter, 1)
|
||||
self.helper(self.poly_hole, splitter, 1)
|
||||
|
||||
# inside the polygon --> return equal
|
||||
splitter = LineString([(0.2, 0.2), (1.7, 1.7), (3, 2)])
|
||||
self.helper(self.poly_simple, splitter, 1)
|
||||
self.helper(self.poly_hole, splitter, 1)
|
||||
|
||||
# outside the polygon --> return equal
|
||||
splitter = LineString([(0, 3), (3, 3), (3, 0)])
|
||||
self.helper(self.poly_simple, splitter, 1)
|
||||
self.helper(self.poly_hole, splitter, 1)
|
||||
|
||||
def test_split_poly_with_other(self):
|
||||
with pytest.raises(GeometryTypeError):
|
||||
split(self.poly_simple, Point(1, 1))
|
||||
with pytest.raises(GeometryTypeError):
|
||||
split(self.poly_simple, MultiPoint([(1, 1), (3, 4)]))
|
||||
with pytest.raises(GeometryTypeError):
|
||||
split(self.poly_simple, self.poly_hole)
|
||||
|
||||
|
||||
class TestSplitLine(TestSplitGeometry):
|
||||
ls = LineString([(0, 0), (1.5, 1.5), (3.0, 4.0)])
|
||||
|
||||
def test_split_line_with_point(self):
|
||||
# point on line interior --> return 2 segments
|
||||
splitter = Point(1, 1)
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# point on line point --> return 2 segments
|
||||
splitter = Point(1.5, 1.5)
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# point on boundary --> return equal
|
||||
splitter = Point(3, 4)
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
# point on exterior of line --> return equal
|
||||
splitter = Point(2, 2)
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
def test_split_line_with_multipoint(self):
|
||||
# points on line interior --> return 4 segments
|
||||
splitter = MultiPoint([(1, 1), (1.5, 1.5), (0.5, 0.5)])
|
||||
self.helper(self.ls, splitter, 4)
|
||||
|
||||
# points on line interior and boundary -> return 2 segments
|
||||
splitter = MultiPoint([(1, 1), (3, 4)])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# point on linear interior but twice --> return 2 segments
|
||||
splitter = MultiPoint([(1, 1), (1.5, 1.5), (1, 1)])
|
||||
self.helper(self.ls, splitter, 3)
|
||||
|
||||
def test_split_line_with_line(self):
|
||||
# crosses at one point --> return 2 segments
|
||||
splitter = LineString([(0, 1), (1, 0)])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# crosses at two points --> return 3 segments
|
||||
splitter = LineString([(0, 1), (1, 0), (1, 2)])
|
||||
self.helper(self.ls, splitter, 3)
|
||||
|
||||
# overlaps --> raise
|
||||
splitter = LineString([(0, 0), (15, 15)])
|
||||
with pytest.raises(ValueError):
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
# does not cross --> return equal
|
||||
splitter = LineString([(0, 1), (0, 2)])
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
# is touching the boundary --> return equal
|
||||
splitter = LineString([(-1, 1), (1, -1)])
|
||||
assert splitter.touches(self.ls)
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
# splitter boundary touches interior of line --> return 2 segments
|
||||
splitter = LineString([(0, 1), (1, 1)]) # touches at (1, 1)
|
||||
assert splitter.touches(self.ls)
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
def test_split_line_with_multiline(self):
|
||||
# crosses at one point --> return 2 segments
|
||||
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 0), (2, -2)]])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# crosses at two points --> return 3 segments
|
||||
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 2), (2, 0)]])
|
||||
self.helper(self.ls, splitter, 3)
|
||||
|
||||
# crosses at three points --> return 4 segments
|
||||
splitter = MultiLineString([[(0, 1), (1, 0)], [(0, 2), (2, 0), (2.2, 3.2)]])
|
||||
self.helper(self.ls, splitter, 4)
|
||||
|
||||
# overlaps --> raise
|
||||
splitter = MultiLineString([[(0, 0), (1.5, 1.5)], [(1.5, 1.5), (3, 4)]])
|
||||
with pytest.raises(ValueError):
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
# does not cross --> return equal
|
||||
splitter = MultiLineString([[(0, 1), (0, 2)], [(1, 0), (2, 0)]])
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
def test_split_line_with_polygon(self):
|
||||
# crosses at two points --> return 3 segments
|
||||
splitter = Polygon([(1, 0), (1, 2), (2, 2), (2, 0), (1, 0)])
|
||||
self.helper(self.ls, splitter, 3)
|
||||
|
||||
# crosses at one point and touches boundary --> return 2 segments
|
||||
splitter = Polygon([(0, 0), (1, 2), (2, 2), (1, 0), (0, 0)])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
|
||||
# exterior crosses at one point and touches at (0, 0)
|
||||
# interior crosses at two points
|
||||
splitter = Polygon(
|
||||
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)],
|
||||
[[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]],
|
||||
)
|
||||
self.helper(self.ls, splitter, 4)
|
||||
|
||||
def test_split_line_with_multipolygon(self):
|
||||
poly1 = Polygon(
|
||||
[(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)]
|
||||
) # crosses at one point and touches at (0, 0)
|
||||
poly2 = Polygon(
|
||||
[(0.5, 0.5), (0.5, 1.5), (1.5, 1.5), (1.5, 0.5), (0.5, 0.5)]
|
||||
) # crosses at two points
|
||||
poly3 = Polygon([(0, 0), (0, -2), (-2, -2), (-2, 0), (0, 0)]) # not crossing
|
||||
splitter = MultiPolygon([poly1, poly2, poly3])
|
||||
self.helper(self.ls, splitter, 4)
|
||||
|
||||
|
||||
class TestSplitClosedRing(TestSplitGeometry):
|
||||
ls = LineString([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]])
|
||||
|
||||
def test_split_closed_ring_with_point(self):
|
||||
splitter = Point([0.0, 0.0])
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
splitter = Point([0.0, 0.5])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
result = split(self.ls, splitter)
|
||||
assert result.geoms[0].coords[:] == [(0, 0), (0.0, 0.5)]
|
||||
assert result.geoms[1].coords[:] == [(0.0, 0.5), (0, 1), (1, 1), (1, 0), (0, 0)]
|
||||
|
||||
# previously failed, see GH#585
|
||||
splitter = Point([0.5, 0.0])
|
||||
self.helper(self.ls, splitter, 2)
|
||||
result = split(self.ls, splitter)
|
||||
assert result.geoms[0].coords[:] == [(0, 0), (0, 1), (1, 1), (1, 0), (0.5, 0)]
|
||||
assert result.geoms[1].coords[:] == [(0.5, 0), (0, 0)]
|
||||
|
||||
splitter = Point([2.0, 2.0])
|
||||
self.helper(self.ls, splitter, 1)
|
||||
|
||||
|
||||
class TestSplitMulti(TestSplitGeometry):
|
||||
def test_split_multiline_with_point(self):
|
||||
# a cross-like multilinestring with a point in the middle --> return 4 line segments
|
||||
l1 = LineString([(0, 1), (2, 1)])
|
||||
l2 = LineString([(1, 0), (1, 2)])
|
||||
ml = MultiLineString([l1, l2])
|
||||
splitter = Point((1, 1))
|
||||
self.helper(ml, splitter, 4)
|
||||
|
||||
def test_split_multiline_with_multipoint(self):
|
||||
# a cross-like multilinestring with a point in middle, a point on one of the lines and a point in the exterior
|
||||
# --> return 4+1 line segments
|
||||
l1 = LineString([(0, 1), (3, 1)])
|
||||
l2 = LineString([(1, 0), (1, 2)])
|
||||
ml = MultiLineString([l1, l2])
|
||||
splitter = MultiPoint([(1, 1), (2, 1), (4, 2)])
|
||||
self.helper(ml, splitter, 5)
|
||||
|
||||
def test_split_multipolygon_with_line(self):
|
||||
# two polygons with a crossing line --> return 4 triangles
|
||||
poly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
|
||||
poly2 = Polygon([(1, 1), (1, 2), (2, 2), (2, 1), (1, 1)])
|
||||
mpoly = MultiPolygon([poly1, poly2])
|
||||
ls = LineString([(-1, -1), (3, 3)])
|
||||
self.helper(mpoly, ls, 4)
|
||||
|
||||
# two polygons away from the crossing line --> return identity
|
||||
poly1 = Polygon([(10, 10), (10, 11), (11, 11), (11, 10), (10, 10)])
|
||||
poly2 = Polygon([(-10, -10), (-10, -11), (-11, -11), (-11, -10), (-10, -10)])
|
||||
mpoly = MultiPolygon([poly1, poly2])
|
||||
ls = LineString([(-1, -1), (3, 3)])
|
||||
self.helper(mpoly, ls, 2)
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,213 @@
|
||||
# Tests SVG output and validity
|
||||
import os
|
||||
import unittest
|
||||
from xml.dom.minidom import parseString as parse_xml_string
|
||||
|
||||
from shapely.geometry import (
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
)
|
||||
from shapely.geometry.collection import GeometryCollection
|
||||
|
||||
|
||||
class SvgTestCase(unittest.TestCase):
|
||||
def assertSVG(self, geom, expected, **kwrds):
|
||||
"""Helper function to check XML and debug SVG"""
|
||||
svg_elem = geom.svg(**kwrds)
|
||||
try:
|
||||
parse_xml_string(svg_elem)
|
||||
except Exception:
|
||||
raise AssertionError("XML is not valid for SVG element: " + str(svg_elem))
|
||||
svg_doc = geom._repr_svg_()
|
||||
try:
|
||||
doc = parse_xml_string(svg_doc)
|
||||
except Exception:
|
||||
raise AssertionError("XML is not valid for SVG document: " + str(svg_doc))
|
||||
svg_output_dir = None
|
||||
# svg_output_dir = '.' # useful for debugging SVG files
|
||||
if svg_output_dir:
|
||||
fname = geom.geom_type
|
||||
if geom.is_empty:
|
||||
fname += "_empty"
|
||||
if not geom.is_valid:
|
||||
fname += "_invalid"
|
||||
if kwrds:
|
||||
fname += "_" + ",".join(str(k) + "=" + str(kwrds[k]) for k in kwrds)
|
||||
svg_path = os.path.join(svg_output_dir, fname + ".svg")
|
||||
with open(svg_path, "w") as fp:
|
||||
fp.write(doc.toprettyxml())
|
||||
assert svg_elem == expected
|
||||
|
||||
def test_point(self):
|
||||
# Empty
|
||||
self.assertSVG(Point(), "<g />")
|
||||
# Valid
|
||||
g = Point(6, 7)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<circle cx="6.0" cy="7.0" r="3.0" stroke="#555555" '
|
||||
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />',
|
||||
)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<circle cx="6.0" cy="7.0" r="15.0" stroke="#555555" '
|
||||
'stroke-width="5.0" fill="#66cc99" opacity="0.6" />',
|
||||
scale_factor=5,
|
||||
)
|
||||
|
||||
def test_multipoint(self):
|
||||
# Empty
|
||||
self.assertSVG(MultiPoint(), "<g />")
|
||||
# Valid
|
||||
g = MultiPoint([(6, 7), (3, 4)])
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<g><circle cx="6.0" cy="7.0" r="3.0" stroke="#555555" '
|
||||
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />'
|
||||
'<circle cx="3.0" cy="4.0" r="3.0" stroke="#555555" '
|
||||
'stroke-width="1.0" fill="#66cc99" opacity="0.6" /></g>',
|
||||
)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<g><circle cx="6.0" cy="7.0" r="15.0" stroke="#555555" '
|
||||
'stroke-width="5.0" fill="#66cc99" opacity="0.6" />'
|
||||
'<circle cx="3.0" cy="4.0" r="15.0" stroke="#555555" '
|
||||
'stroke-width="5.0" fill="#66cc99" opacity="0.6" /></g>',
|
||||
scale_factor=5,
|
||||
)
|
||||
|
||||
def test_linestring(self):
|
||||
# Empty
|
||||
self.assertSVG(LineString(), "<g />")
|
||||
# Valid
|
||||
g = LineString([(5, 8), (496, -6), (530, 20)])
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
|
||||
'points="5.0,8.0 496.0,-6.0 530.0,20.0" opacity="0.8" />',
|
||||
)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<polyline fill="none" stroke="#66cc99" stroke-width="10.0" '
|
||||
'points="5.0,8.0 496.0,-6.0 530.0,20.0" opacity="0.8" />',
|
||||
scale_factor=5,
|
||||
)
|
||||
# Invalid
|
||||
self.assertSVG(
|
||||
LineString([(0, 0), (0, 0)]),
|
||||
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
|
||||
'points="0.0,0.0 0.0,0.0" opacity="0.8" />',
|
||||
)
|
||||
|
||||
def test_multilinestring(self):
|
||||
# Empty
|
||||
self.assertSVG(MultiLineString(), "<g />")
|
||||
# Valid
|
||||
self.assertSVG(
|
||||
MultiLineString([[(6, 7), (3, 4)], [(2, 8), (9, 1)]]),
|
||||
'<g><polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
|
||||
'points="6.0,7.0 3.0,4.0" opacity="0.8" />'
|
||||
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
|
||||
'points="2.0,8.0 9.0,1.0" opacity="0.8" /></g>',
|
||||
)
|
||||
# Invalid
|
||||
self.assertSVG(
|
||||
MultiLineString([[(2, 3), (2, 3)], [(2, 8), (9, 1)]]),
|
||||
'<g><polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
|
||||
'points="2.0,3.0 2.0,3.0" opacity="0.8" />'
|
||||
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
|
||||
'points="2.0,8.0 9.0,1.0" opacity="0.8" /></g>',
|
||||
)
|
||||
|
||||
def test_polygon(self):
|
||||
# Empty
|
||||
self.assertSVG(Polygon(), "<g />")
|
||||
# Valid
|
||||
g = Polygon(
|
||||
[(35, 10), (45, 45), (15, 40), (10, 20), (35, 10)],
|
||||
[[(20, 30), (35, 35), (30, 20), (20, 30)]],
|
||||
)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 35.0,10.0 L 45.0,45.0 L '
|
||||
"15.0,40.0 L 10.0,20.0 L 35.0,10.0 z M 20.0,30.0 L 35.0,35.0 L "
|
||||
'30.0,20.0 L 20.0,30.0 z" />',
|
||||
)
|
||||
self.assertSVG(
|
||||
g,
|
||||
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
|
||||
'stroke-width="10.0" opacity="0.6" d="M 35.0,10.0 L 45.0,45.0 L '
|
||||
"15.0,40.0 L 10.0,20.0 L 35.0,10.0 z M 20.0,30.0 L 35.0,35.0 L "
|
||||
'30.0,20.0 L 20.0,30.0 z" />',
|
||||
scale_factor=5,
|
||||
)
|
||||
# Invalid
|
||||
self.assertSVG(
|
||||
Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]),
|
||||
'<path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 0.0,40.0 L 0.0,0.0 L '
|
||||
'40.0,40.0 L 40.0,0.0 L 0.0,40.0 z" />',
|
||||
)
|
||||
|
||||
def test_multipolygon(self):
|
||||
# Empty
|
||||
self.assertSVG(MultiPolygon(), "<g />")
|
||||
# Valid
|
||||
self.assertSVG(
|
||||
MultiPolygon(
|
||||
[
|
||||
Polygon([(40, 40), (20, 45), (45, 30), (40, 40)]),
|
||||
Polygon(
|
||||
[(20, 35), (10, 30), (10, 10), (30, 5), (45, 20), (20, 35)],
|
||||
[[(30, 20), (20, 15), (20, 25), (30, 20)]],
|
||||
),
|
||||
]
|
||||
),
|
||||
'<g><path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 40.0,40.0 L 20.0,45.0 L '
|
||||
'45.0,30.0 L 40.0,40.0 z" />'
|
||||
'<path fill-rule="evenodd" fill="#66cc99" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 20.0,35.0 L 10.0,30.0 L '
|
||||
"10.0,10.0 L 30.0,5.0 L 45.0,20.0 L 20.0,35.0 z M 30.0,20.0 L "
|
||||
'20.0,15.0 L 20.0,25.0 L 30.0,20.0 z" /></g>',
|
||||
)
|
||||
# Invalid
|
||||
self.assertSVG(
|
||||
MultiPolygon(
|
||||
[
|
||||
Polygon([(140, 140), (120, 145), (145, 130), (140, 140)]),
|
||||
Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]),
|
||||
]
|
||||
),
|
||||
'<g><path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 140.0,140.0 L '
|
||||
'120.0,145.0 L 145.0,130.0 L 140.0,140.0 z" />'
|
||||
'<path fill-rule="evenodd" fill="#ff3333" stroke="#555555" '
|
||||
'stroke-width="2.0" opacity="0.6" d="M 0.0,40.0 L 0.0,0.0 L '
|
||||
'40.0,40.0 L 40.0,0.0 L 0.0,40.0 z" /></g>',
|
||||
)
|
||||
|
||||
def test_collection(self):
|
||||
# Empty
|
||||
self.assertSVG(GeometryCollection(), "<g />")
|
||||
# Valid
|
||||
self.assertSVG(
|
||||
GeometryCollection([Point(7, 3), LineString([(4, 2), (8, 4)])]),
|
||||
'<g><circle cx="7.0" cy="3.0" r="3.0" stroke="#555555" '
|
||||
'stroke-width="1.0" fill="#66cc99" opacity="0.6" />'
|
||||
'<polyline fill="none" stroke="#66cc99" stroke-width="2.0" '
|
||||
'points="4.0,2.0 8.0,4.0" opacity="0.8" /></g>',
|
||||
)
|
||||
# Invalid
|
||||
self.assertSVG(
|
||||
Point(7, 3).union(LineString([(4, 2), (4, 2)])),
|
||||
'<g><circle cx="7.0" cy="3.0" r="3.0" stroke="#555555" '
|
||||
'stroke-width="1.0" fill="#ff3333" opacity="0.6" />'
|
||||
'<polyline fill="none" stroke="#ff3333" stroke-width="2.0" '
|
||||
'points="4.0,2.0 4.0,2.0" opacity="0.8" /></g>',
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely import geometry
|
||||
from shapely.ops import transform
|
||||
|
||||
|
||||
class IdentityTestCase(unittest.TestCase):
|
||||
"""New geometry/coordseq method 'xy' makes numpy interop easier"""
|
||||
|
||||
def func(self, x, y, z=None):
|
||||
return tuple(c for c in [x, y, z] if c)
|
||||
|
||||
def test_empty(self):
|
||||
g = geometry.Point()
|
||||
h = transform(self.func, g)
|
||||
assert h.is_empty
|
||||
|
||||
def test_point(self):
|
||||
g = geometry.Point(0, 1)
|
||||
h = transform(self.func, g)
|
||||
assert h.geom_type == "Point"
|
||||
assert list(h.coords) == [(0, 1)]
|
||||
|
||||
def test_line(self):
|
||||
g = geometry.LineString([(0, 1), (2, 3)])
|
||||
h = transform(self.func, g)
|
||||
assert h.geom_type == "LineString"
|
||||
assert list(h.coords) == [(0, 1), (2, 3)]
|
||||
|
||||
def test_linearring(self):
|
||||
g = geometry.LinearRing([(0, 1), (2, 3), (2, 2), (0, 1)])
|
||||
h = transform(self.func, g)
|
||||
assert h.geom_type == "LinearRing"
|
||||
assert list(h.coords) == [(0, 1), (2, 3), (2, 2), (0, 1)]
|
||||
|
||||
def test_polygon(self):
|
||||
g = geometry.Point(0, 1).buffer(1.0)
|
||||
h = transform(self.func, g)
|
||||
assert h.geom_type == "Polygon"
|
||||
assert g.area == pytest.approx(h.area)
|
||||
|
||||
def test_multipolygon(self):
|
||||
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
|
||||
h = transform(self.func, g)
|
||||
assert h.geom_type == "MultiPolygon"
|
||||
assert g.area == pytest.approx(h.area)
|
||||
|
||||
|
||||
class LambdaTestCase(unittest.TestCase):
|
||||
"""New geometry/coordseq method 'xy' makes numpy interop easier"""
|
||||
|
||||
def test_point(self):
|
||||
g = geometry.Point(0, 1)
|
||||
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
|
||||
assert h.geom_type == "Point"
|
||||
assert list(h.coords) == [(1.0, 2.0)]
|
||||
|
||||
def test_line(self):
|
||||
g = geometry.LineString([(0, 1), (2, 3)])
|
||||
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
|
||||
assert h.geom_type == "LineString"
|
||||
assert list(h.coords) == [(1.0, 2.0), (3.0, 4.0)]
|
||||
|
||||
def test_polygon(self):
|
||||
g = geometry.Point(0, 1).buffer(1.0)
|
||||
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
|
||||
assert h.geom_type == "Polygon"
|
||||
assert g.area == pytest.approx(h.area)
|
||||
assert h.centroid.x == pytest.approx(1.0)
|
||||
assert h.centroid.y == pytest.approx(2.0)
|
||||
|
||||
def test_multipolygon(self):
|
||||
g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0)
|
||||
h = transform(lambda x, y, z=None: (x + 1.0, y + 1.0), g)
|
||||
assert h.geom_type == "MultiPolygon"
|
||||
assert g.area == pytest.approx(h.area)
|
||||
assert h.centroid.x == pytest.approx(1.0)
|
||||
assert h.centroid.y == pytest.approx(3.5)
|
||||
@@ -0,0 +1,70 @@
|
||||
import random
|
||||
import unittest
|
||||
from functools import partial
|
||||
from itertools import islice
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.errors import ShapelyDeprecationWarning
|
||||
from shapely.geometry import MultiPolygon, Point
|
||||
from shapely.ops import cascaded_union, unary_union
|
||||
|
||||
|
||||
def halton(base):
|
||||
"""Returns an iterator over an infinite Halton sequence"""
|
||||
|
||||
def value(index):
|
||||
result = 0.0
|
||||
f = 1.0 / base
|
||||
i = index
|
||||
while i > 0:
|
||||
result += f * (i % base)
|
||||
i = i // base
|
||||
f = f / base
|
||||
return result
|
||||
|
||||
i = 1
|
||||
while i > 0:
|
||||
yield value(i)
|
||||
i += 1
|
||||
|
||||
|
||||
class UnionTestCase(unittest.TestCase):
|
||||
def test_cascaded_union(self):
|
||||
# cascaded_union is deprecated, as it was superseded by unary_union
|
||||
|
||||
# Use a partial function to make 100 points uniformly distributed
|
||||
# in a 40x40 box centered on 0,0.
|
||||
|
||||
r = partial(random.uniform, -20.0, 20.0)
|
||||
points = [Point(r(), r()) for i in range(100)]
|
||||
|
||||
# Buffer the points, producing 100 polygon spots
|
||||
spots = [p.buffer(2.5) for p in points]
|
||||
|
||||
# Perform a cascaded union of the polygon spots, dissolving them
|
||||
# into a collection of polygon patches
|
||||
with pytest.warns(ShapelyDeprecationWarning, match="is deprecated"):
|
||||
u = cascaded_union(spots)
|
||||
assert u.geom_type in ("Polygon", "MultiPolygon")
|
||||
|
||||
def setUp(self):
|
||||
# Instead of random points, use deterministic, pseudo-random Halton
|
||||
# sequences for repeatability sake.
|
||||
self.coords = zip(
|
||||
list(islice(halton(5), 20, 120)),
|
||||
list(islice(halton(7), 20, 120)),
|
||||
)
|
||||
|
||||
def test_unary_union(self):
|
||||
patches = [Point(xy).buffer(0.05) for xy in self.coords]
|
||||
u = unary_union(patches)
|
||||
assert u.geom_type == "MultiPolygon"
|
||||
assert u.area == pytest.approx(0.718572540569)
|
||||
|
||||
def test_unary_union_multi(self):
|
||||
# Test of multipart input based on comment by @schwehr at
|
||||
# https://github.com/shapely/shapely/issues/47#issuecomment-21809308
|
||||
patches = MultiPolygon([Point(xy).buffer(0.05) for xy in self.coords])
|
||||
assert unary_union(patches).area == pytest.approx(0.71857254056)
|
||||
assert unary_union([patches, patches]).area == pytest.approx(0.71857254056)
|
||||
@@ -0,0 +1,9 @@
|
||||
import unittest
|
||||
|
||||
from shapely.geometry import Point
|
||||
from shapely.validation import explain_validity
|
||||
|
||||
|
||||
class ValidationTestCase(unittest.TestCase):
|
||||
def test_valid(self):
|
||||
assert explain_validity(Point(0, 0)) == "Valid Geometry"
|
||||
@@ -0,0 +1,105 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from shapely.geometry import box, MultiPolygon, Point
|
||||
|
||||
|
||||
class VectorizedContainsTestCase(unittest.TestCase):
|
||||
def assertContainsResults(self, geom, x, y):
|
||||
from shapely.vectorized import contains
|
||||
|
||||
result = contains(geom, x, y)
|
||||
x = np.asanyarray(x)
|
||||
y = np.asanyarray(y)
|
||||
|
||||
self.assertIsInstance(result, np.ndarray)
|
||||
self.assertEqual(result.dtype, bool)
|
||||
|
||||
result_flat = result.flat
|
||||
x_flat, y_flat = x.flat, y.flat
|
||||
|
||||
# Do the equivalent operation, only slowly, comparing the result
|
||||
# as we go.
|
||||
for idx in range(x.size):
|
||||
assert result_flat[idx] == geom.contains(Point(x_flat[idx], y_flat[idx]))
|
||||
return result
|
||||
|
||||
def construct_torus(self):
|
||||
point = Point(0, 0)
|
||||
return point.buffer(5).symmetric_difference(point.buffer(2.5))
|
||||
|
||||
def test_contains_poly(self):
|
||||
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
|
||||
self.assertContainsResults(self.construct_torus(), x, y)
|
||||
|
||||
def test_contains_point(self):
|
||||
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
|
||||
self.assertContainsResults(Point(x[0], y[0]), x, y)
|
||||
|
||||
def test_contains_linestring(self):
|
||||
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
|
||||
self.assertContainsResults(Point(x[0], y[0]), x, y)
|
||||
|
||||
def test_contains_multipoly(self):
|
||||
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
|
||||
# Construct a geometry of the torus cut in half vertically.
|
||||
cut_poly = box(-1, -10, -2.5, 10)
|
||||
geom = self.construct_torus().difference(cut_poly)
|
||||
assert isinstance(geom, MultiPolygon)
|
||||
self.assertContainsResults(geom, x, y)
|
||||
|
||||
def test_y_array_order(self):
|
||||
y, x = np.mgrid[-10:10:5j, -5:15:5j]
|
||||
y = y.copy("f")
|
||||
self.assertContainsResults(self.construct_torus(), x, y)
|
||||
|
||||
def test_x_array_order(self):
|
||||
y, x = np.mgrid[-10:10:5j, -5:15:5j]
|
||||
x = x.copy("f")
|
||||
self.assertContainsResults(self.construct_torus(), x, y)
|
||||
|
||||
def test_xy_array_order(self):
|
||||
y, x = np.mgrid[-10:10:5j, -5:15:5j]
|
||||
x = x.copy("f")
|
||||
y = y.copy("f")
|
||||
result = self.assertContainsResults(self.construct_torus(), x, y)
|
||||
# Preserve the order
|
||||
assert result.flags["F_CONTIGUOUS"]
|
||||
|
||||
def test_array_dtype(self):
|
||||
y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j]
|
||||
x = x.astype(np.int16)
|
||||
self.assertContainsResults(self.construct_torus(), x, y)
|
||||
|
||||
def test_array_2d(self):
|
||||
y, x = np.mgrid[-10:10:15j, -5:15:16j]
|
||||
result = self.assertContainsResults(self.construct_torus(), x, y)
|
||||
assert result.shape == x.shape
|
||||
|
||||
def test_shapely_xy_attr_contains(self):
|
||||
g = Point(0, 0).buffer(10.0)
|
||||
self.assertContainsResults(self.construct_torus(), *g.exterior.xy)
|
||||
|
||||
|
||||
class VectorizedTouchesTestCase(unittest.TestCase):
|
||||
def test_touches(self):
|
||||
from shapely.vectorized import touches
|
||||
|
||||
y, x = np.mgrid[-2:3:6j, -1:3:5j]
|
||||
geom = box(0, -1, 2, 2)
|
||||
result = touches(geom, x, y)
|
||||
expected = np.array(
|
||||
[
|
||||
[False, False, False, False, False],
|
||||
[False, True, True, True, False],
|
||||
[False, True, False, True, False],
|
||||
[False, True, False, True, False],
|
||||
[False, True, True, True, False],
|
||||
[False, False, False, False, False],
|
||||
],
|
||||
dtype=bool,
|
||||
)
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Test cases for Voronoi Diagram creation.
|
||||
|
||||
Overall, I'm trying less to test the correctness of the result
|
||||
and more to cover input cases and behavior, making sure
|
||||
that we return a sane result without error or raise a useful one.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import MultiPoint
|
||||
from shapely.geos import geos_version
|
||||
from shapely.ops import voronoi_diagram
|
||||
from shapely.wkt import loads as load_wkt
|
||||
|
||||
requires_geos_35 = pytest.mark.skipif(
|
||||
geos_version < (3, 5, 0), reason="GEOS >= 3.5.0 is required."
|
||||
)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_no_regions():
|
||||
mp = MultiPoint(points=[(0.5, 0.5)])
|
||||
with np.errstate(invalid="ignore"):
|
||||
regions = voronoi_diagram(mp)
|
||||
|
||||
assert len(regions.geoms) == 0
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_two_regions():
|
||||
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
|
||||
regions = voronoi_diagram(mp)
|
||||
|
||||
assert len(regions.geoms) == 2
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_edges():
|
||||
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
|
||||
regions = voronoi_diagram(mp, edges=True)
|
||||
|
||||
assert len(regions.geoms) == 1
|
||||
# can be LineString or MultiLineString depending on the GEOS version
|
||||
assert all(r.geom_type.endswith("LineString") for r in regions.geoms)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_smaller_envelope():
|
||||
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
|
||||
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
|
||||
|
||||
regions = voronoi_diagram(mp, envelope=poly)
|
||||
|
||||
assert len(regions.geoms) == 2
|
||||
assert sum(r.area for r in regions.geoms) > poly.area
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_larger_envelope():
|
||||
"""When the envelope we specify is larger than the
|
||||
area of the input feature, the created regions should
|
||||
expand to fill that area."""
|
||||
mp = MultiPoint(points=[(0.5, 0.5), (1.0, 1.0)])
|
||||
poly = load_wkt("POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))")
|
||||
|
||||
regions = voronoi_diagram(mp, envelope=poly)
|
||||
|
||||
assert len(regions.geoms) == 2
|
||||
assert sum(r.area for r in regions.geoms) == poly.area
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_polygon():
|
||||
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
|
||||
regions = voronoi_diagram(poly)
|
||||
|
||||
assert len(regions.geoms) == 4
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_polygon_with_enough_tolerance():
|
||||
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
|
||||
regions = voronoi_diagram(poly, tolerance=1.0)
|
||||
|
||||
assert len(regions.geoms) == 2
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_polygon_without_enough_tolerance():
|
||||
poly = load_wkt("POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0.5, 0 0))")
|
||||
with pytest.raises(ValueError) as exc:
|
||||
voronoi_diagram(poly, tolerance=0.6)
|
||||
|
||||
assert "Could not create Voronoi Diagram with the specified inputs" in str(
|
||||
exc.value
|
||||
)
|
||||
assert "Try running again with default tolerance value." in str(exc.value)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_polygon_without_floating_point_coordinates():
|
||||
poly = load_wkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")
|
||||
with pytest.raises(ValueError) as exc:
|
||||
voronoi_diagram(poly, tolerance=0.1)
|
||||
|
||||
assert "Could not create Voronoi Diagram with the specified inputs" in str(
|
||||
exc.value
|
||||
)
|
||||
assert "Try running again with default tolerance value." in str(exc.value)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_multipoint_without_floating_point_coordinates():
|
||||
"""A Multipoint with the same "shape" as the above Polygon raises the same error..."""
|
||||
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 1, 0 1)")
|
||||
|
||||
with pytest.raises(ValueError) as exc:
|
||||
voronoi_diagram(mp, tolerance=0.1)
|
||||
|
||||
assert "Could not create Voronoi Diagram with the specified inputs" in str(
|
||||
exc.value
|
||||
)
|
||||
assert "Try running again with default tolerance value." in str(exc.value)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_multipoint_with_tolerace_without_floating_point_coordinates():
|
||||
"""This multipoint will not work with a tolerance value."""
|
||||
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 2, 0 1)")
|
||||
with pytest.raises(ValueError) as exc:
|
||||
voronoi_diagram(mp, tolerance=0.1)
|
||||
|
||||
assert "Could not create Voronoi Diagram with the specified inputs" in str(
|
||||
exc.value
|
||||
)
|
||||
assert "Try running again with default tolerance value." in str(exc.value)
|
||||
|
||||
|
||||
@requires_geos_35
|
||||
def test_from_multipoint_without_tolerace_without_floating_point_coordinates():
|
||||
"""But it's fine without it."""
|
||||
mp = load_wkt("MULTIPOINT (0 0, 1 0, 1 2, 0 1)")
|
||||
regions = voronoi_diagram(mp)
|
||||
assert len(regions.geoms) == 4
|
||||
@@ -0,0 +1,193 @@
|
||||
import binascii
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely import wkt
|
||||
from shapely.geometry import Point
|
||||
from shapely.geos import geos_version
|
||||
from shapely.tests.legacy.conftest import shapely20_todo
|
||||
from shapely.wkb import dump, dumps, load, loads
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def some_point():
|
||||
return Point(1.2, 3.4)
|
||||
|
||||
|
||||
def bin2hex(value):
|
||||
return binascii.b2a_hex(value).upper().decode("utf-8")
|
||||
|
||||
|
||||
def hex2bin(value):
|
||||
return binascii.a2b_hex(value)
|
||||
|
||||
|
||||
def hostorder(fmt, value):
|
||||
"""Re-pack a hex WKB value to native endianness if needed
|
||||
|
||||
This routine does not understand WKB format, so it must be provided a
|
||||
struct module format string, without initial indicator character ("@=<>!"),
|
||||
which will be interpreted as big- or little-endian with standard sizes
|
||||
depending on the endian flag in the first byte of the value.
|
||||
"""
|
||||
|
||||
if fmt and fmt[0] in "@=<>!":
|
||||
raise ValueError("Initial indicator character, one of @=<>!, in fmt")
|
||||
if not fmt or fmt[0] not in "cbB":
|
||||
raise ValueError("Missing endian flag in fmt")
|
||||
|
||||
(hexendian,) = struct.unpack(fmt[0], hex2bin(value[:2]))
|
||||
hexorder = {0: ">", 1: "<"}[hexendian]
|
||||
sysorder = {"little": "<", "big": ">"}[sys.byteorder]
|
||||
if hexorder == sysorder:
|
||||
return value # Nothing to do
|
||||
|
||||
return bin2hex(
|
||||
struct.pack(
|
||||
sysorder + fmt,
|
||||
{">": 0, "<": 1}[sysorder],
|
||||
*struct.unpack(hexorder + fmt, hex2bin(value))[1:]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_dumps_srid(some_point):
|
||||
result = dumps(some_point)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIdd", "0101000000333333333333F33F3333333333330B40"
|
||||
)
|
||||
result = dumps(some_point, srid=4326)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
|
||||
)
|
||||
|
||||
|
||||
def test_dumps_endianness(some_point):
|
||||
result = dumps(some_point)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIdd", "0101000000333333333333F33F3333333333330B40"
|
||||
)
|
||||
result = dumps(some_point, big_endian=False)
|
||||
assert bin2hex(result) == "0101000000333333333333F33F3333333333330B40"
|
||||
result = dumps(some_point, big_endian=True)
|
||||
assert bin2hex(result) == "00000000013FF3333333333333400B333333333333"
|
||||
|
||||
|
||||
def test_dumps_hex(some_point):
|
||||
result = dumps(some_point, hex=True)
|
||||
assert result == hostorder("BIdd", "0101000000333333333333F33F3333333333330B40")
|
||||
|
||||
|
||||
def test_loads_srid():
|
||||
# load a geometry which includes an srid
|
||||
geom = loads(hex2bin("0101000020E6100000333333333333F33F3333333333330B40"))
|
||||
assert isinstance(geom, Point)
|
||||
assert geom.coords[:] == [(1.2, 3.4)]
|
||||
# by default srid is not exported
|
||||
result = dumps(geom)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIdd", "0101000000333333333333F33F3333333333330B40"
|
||||
)
|
||||
# include the srid in the output
|
||||
result = dumps(geom, include_srid=True)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
|
||||
)
|
||||
# replace geometry srid with another
|
||||
result = dumps(geom, srid=27700)
|
||||
assert bin2hex(result) == hostorder(
|
||||
"BIIdd", "0101000020346C0000333333333333F33F3333333333330B40"
|
||||
)
|
||||
|
||||
|
||||
def test_loads_hex(some_point):
|
||||
assert loads(dumps(some_point, hex=True), hex=True) == some_point
|
||||
|
||||
|
||||
def test_dump_load_binary(some_point, tmpdir):
|
||||
file = tmpdir.join("test.wkb")
|
||||
with open(file, "wb") as file_pointer:
|
||||
dump(some_point, file_pointer)
|
||||
with open(file, "rb") as file_pointer:
|
||||
restored = load(file_pointer)
|
||||
|
||||
assert some_point == restored
|
||||
|
||||
|
||||
def test_dump_load_hex(some_point, tmpdir):
|
||||
file = tmpdir.join("test.wkb")
|
||||
with open(file, "w") as file_pointer:
|
||||
dump(some_point, file_pointer, hex=True)
|
||||
with open(file, "r") as file_pointer:
|
||||
restored = load(file_pointer, hex=True)
|
||||
|
||||
assert some_point == restored
|
||||
|
||||
|
||||
# pygeos handles both bytes and str
|
||||
@shapely20_todo
|
||||
def test_dump_hex_load_binary(some_point, tmpdir):
|
||||
"""Asserts that reading a binary file as text (hex mode) fails."""
|
||||
file = tmpdir.join("test.wkb")
|
||||
with open(file, "w") as file_pointer:
|
||||
dump(some_point, file_pointer, hex=True)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
with open(file, "rb") as file_pointer:
|
||||
load(file_pointer)
|
||||
|
||||
|
||||
def test_dump_binary_load_hex(some_point, tmpdir):
|
||||
"""Asserts that reading a text file (hex mode) as binary fails."""
|
||||
file = tmpdir.join("test.wkb")
|
||||
with open(file, "wb") as file_pointer:
|
||||
dump(some_point, file_pointer)
|
||||
|
||||
# TODO(shapely-2.0) on windows this doesn't seem to error with pygeos,
|
||||
# but you get back a point with garbage coordinates
|
||||
if sys.platform == "win32":
|
||||
with open(file, "r") as file_pointer:
|
||||
restored = load(file_pointer, hex=True)
|
||||
assert some_point != restored
|
||||
return
|
||||
|
||||
with pytest.raises((UnicodeEncodeError, UnicodeDecodeError)):
|
||||
with open(file, "r") as file_pointer:
|
||||
load(file_pointer, hex=True)
|
||||
|
||||
|
||||
requires_geos_380 = pytest.mark.xfail(
|
||||
geos_version < (3, 8, 0), reason="GEOS >= 3.8.0 is required", strict=True
|
||||
)
|
||||
|
||||
|
||||
@requires_geos_380
|
||||
def test_point_empty():
|
||||
g = wkt.loads("POINT EMPTY")
|
||||
result = dumps(g, big_endian=False)
|
||||
# Use math.isnan for second part of the WKB representation there are
|
||||
# many byte representations for NaN)
|
||||
assert result[: -2 * 8] == b"\x01\x01\x00\x00\x00"
|
||||
coords = struct.unpack("<2d", result[-2 * 8 :])
|
||||
assert len(coords) == 2
|
||||
assert all(math.isnan(val) for val in coords)
|
||||
|
||||
|
||||
# Generally GEOS only serializes this correctly starting with GEOS 3.9
|
||||
# For some reason MacOS has different behaviour (it's actually correct for
|
||||
# older GEOS versions, but not for GEOS 3.8)
|
||||
@pytest.mark.xfail(
|
||||
(
|
||||
geos_version < (3, 9, 0)
|
||||
and not (geos_version < (3, 8, 0) and sys.platform == "darwin")
|
||||
),
|
||||
reason="GEOS >= 3.9.0 is required",
|
||||
)
|
||||
def test_point_z_empty():
|
||||
g = wkt.loads("POINT Z EMPTY")
|
||||
assert g.wkb_hex == hostorder(
|
||||
"BIddd", "0101000080000000000000F87F000000000000F87F000000000000F87F"
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
from math import pi
|
||||
|
||||
import pytest
|
||||
|
||||
from shapely.geometry import Point
|
||||
from shapely.wkt import dump, dumps, load, loads
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def some_point():
|
||||
return Point(pi, -pi)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def empty_geometry():
|
||||
return Point()
|
||||
|
||||
|
||||
def test_wkt(some_point):
|
||||
""".wkt and wkt.dumps() both do not trim by default."""
|
||||
assert some_point.wkt == f"POINT ({pi:.15f} {-pi:.15f})"
|
||||
|
||||
|
||||
def test_wkt_null(empty_geometry):
|
||||
assert empty_geometry.wkt == "POINT EMPTY"
|
||||
|
||||
|
||||
def test_dump_load(some_point, tmpdir):
|
||||
file = tmpdir.join("test.wkt")
|
||||
with open(file, "w") as file_pointer:
|
||||
dump(some_point, file_pointer)
|
||||
with open(file, "r") as file_pointer:
|
||||
restored = load(file_pointer)
|
||||
|
||||
assert some_point == restored
|
||||
|
||||
|
||||
def test_dump_load_null_geometry(empty_geometry, tmpdir):
|
||||
file = tmpdir.join("test.wkt")
|
||||
with open(file, "w") as file_pointer:
|
||||
dump(empty_geometry, file_pointer)
|
||||
with open(file, "r") as file_pointer:
|
||||
restored = load(file_pointer)
|
||||
|
||||
# This is does not work with __eq__():
|
||||
assert empty_geometry.equals(restored)
|
||||
|
||||
|
||||
def test_dumps_loads(some_point):
|
||||
assert dumps(some_point) == f"POINT ({pi:.16f} {-pi:.16f})"
|
||||
assert loads(dumps(some_point)) == some_point
|
||||
|
||||
|
||||
def test_dumps_loads_null_geometry(empty_geometry):
|
||||
assert dumps(empty_geometry) == "POINT EMPTY"
|
||||
# This is does not work with __eq__():
|
||||
assert loads(dumps(empty_geometry)).equals(empty_geometry)
|
||||
|
||||
|
||||
def test_dumps_precision(some_point):
|
||||
assert dumps(some_point, rounding_precision=4) == f"POINT ({pi:.4f} {-pi:.4f})"
|
||||
@@ -0,0 +1,44 @@
|
||||
import threading
|
||||
from binascii import b2a_hex
|
||||
|
||||
|
||||
def main():
|
||||
num_threads = 10
|
||||
use_threads = True
|
||||
|
||||
if not use_threads:
|
||||
# Run core code
|
||||
runShapelyBuilding()
|
||||
else:
|
||||
threads = [
|
||||
threading.Thread(target=runShapelyBuilding, name=str(i), args=(i,))
|
||||
for i in range(num_threads)
|
||||
]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
def runShapelyBuilding(num):
|
||||
print("%s: Running shapely tests on wkb" % num)
|
||||
import shapely.geos
|
||||
|
||||
print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle))
|
||||
import shapely.wkb
|
||||
import shapely.wkt
|
||||
|
||||
p = shapely.wkt.loads("POINT (0 0)")
|
||||
print("%s WKT: %s" % (num, shapely.wkt.dumps(p)))
|
||||
wkb = shapely.wkb.dumps(p)
|
||||
print("%s WKB: %s" % (num, b2a_hex(wkb)))
|
||||
|
||||
for i in range(10):
|
||||
shapely.wkb.loads(wkb)
|
||||
|
||||
print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle))
|
||||
print("Done %s" % num)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user