import unittest
from math import pi
import numpy as np
import pytest
from shapely import affinity
from shapely.geometry import Point
from shapely.wkt import loads as load_wkt
class AffineTestCase(unittest.TestCase):
def test_affine_params(self):
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
with pytest.raises(TypeError):
affinity.affine_transform(g, None)
with pytest.raises(ValueError):
affinity.affine_transform(g, [1, 2, 3, 4, 5, 6, 7, 8, 9])
with pytest.raises(AttributeError):
affinity.affine_transform(None, [1, 2, 3, 4, 5, 6])
def test_affine_geom_types(self):
# identity matrices, which should result with no transformation
matrix2d = (1, 0, 0, 1, 0, 0)
matrix3d = (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
# empty in, empty out
empty2d = load_wkt("MULTIPOLYGON EMPTY")
assert affinity.affine_transform(empty2d, matrix2d).is_empty
def test_geom(g2, g3=None):
assert not g2.has_z
a2 = affinity.affine_transform(g2, matrix2d)
assert not a2.has_z
assert g2.equals(a2)
if g3 is not None:
assert g3.has_z
a3 = affinity.affine_transform(g3, matrix3d)
assert a3.has_z
assert g3.equals(a3)
pt2d = load_wkt("POINT(12.3 45.6)")
pt3d = load_wkt("POINT(12.3 45.6 7.89)")
test_geom(pt2d, pt3d)
ls2d = load_wkt("LINESTRING(0.9 3.4, 0.7 2, 2.5 2.7)")
ls3d = load_wkt("LINESTRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5)")
test_geom(ls2d, ls3d)
lr2d = load_wkt("LINEARRING(0.9 3.4, 0.7 2, 2.5 2.7, 0.9 3.4)")
lr3d = load_wkt("LINEARRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5, 0.9 3.4 3.3)")
test_geom(lr2d, lr3d)
test_geom(
load_wkt(
"POLYGON((0.9 2.3, 0.5 1.1, 2.4 0.8, 0.9 2.3), "
"(1.1 1.7, 0.9 1.3, 1.4 1.2, 1.1 1.7), "
"(1.6 1.3, 1.7 1, 1.9 1.1, 1.6 1.3))"
)
)
test_geom(
load_wkt("MULTIPOINT ((-300 300), (700 300), (-800 -1100), (200 -300))")
)
test_geom(
load_wkt(
"MULTILINESTRING((0 0, -0.7 -0.7, 0.6 -1), (-0.5 0.5, 0.7 0.6, 0 -0.6))"
)
)
test_geom(
load_wkt(
"MULTIPOLYGON(((900 4300, -1100 -400, 900 -800, 900 4300)), "
"((1200 4300, 2300 4400, 1900 1000, 1200 4300)))"
)
)
test_geom(
load_wkt(
"GEOMETRYCOLLECTION(POINT(20 70),"
" POLYGON((60 70, 13 35, 60 -30, 60 70)),"
" LINESTRING(60 70, 50 100, 80 100))"
)
)
def test_affine_2d(self):
g = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
# custom scale and translate
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
a2 = affinity.affine_transform(g, matrix2d)
assert a2.equals_exact(expected2d, 1e-6)
assert not a2.has_z
# Make sure a 3D matrix does not make a 3D shape from a 2D input
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 10, -5, 4.1, 100)
a3 = affinity.affine_transform(g, matrix3d)
assert a3.equals_exact(expected2d, 1e-6)
assert not a3.has_z
def test_affine_3d(self):
g2 = load_wkt("LINESTRING(2.4 4.1, 2.4 3, 3 3)")
g3 = load_wkt("LINESTRING(2.4 4.1 100.2, 2.4 3 132.8, 3 3 128.6)")
# custom scale and translate
matrix2d = (2, 0, 0, 2.5, -5, 4.1)
matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 0.3048, -5, 4.1, 100)
# Combinations of 2D and 3D geometries and matrices
a22 = affinity.affine_transform(g2, matrix2d)
a23 = affinity.affine_transform(g2, matrix3d)
a32 = affinity.affine_transform(g3, matrix2d)
a33 = affinity.affine_transform(g3, matrix3d)
# Check dimensions
assert not a22.has_z
assert not a23.has_z
assert a32.has_z
assert a33.has_z
# 2D equality checks
expected2d = load_wkt("LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)")
expected3d = load_wkt(
"LINESTRING(-0.2 14.35 130.54096, -0.2 11.6 140.47744, 1 11.6 139.19728)"
)
expected32 = load_wkt(
"LINESTRING(-0.2 14.35 100.2, -0.2 11.6 132.8, 1 11.6 128.6)"
)
assert a22.equals_exact(expected2d, 1e-6)
assert a23.equals_exact(expected2d, 1e-6)
# Do explicit 3D check of coordinate values
for a, e in zip(a32.coords, expected32.coords):
for ap, ep in zip(a, e):
self.assertAlmostEqual(ap, ep)
for a, e in zip(a33.coords, expected3d.coords):
for ap, ep in zip(a, e):
self.assertAlmostEqual(ap, ep)
class TransformOpsTestCase(unittest.TestCase):
def test_rotate(self):
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
# counter-clockwise degrees
rls = affinity.rotate(ls, 90)
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
assert rls.equals(els)
# retest with named parameters for the same result
rls = affinity.rotate(geom=ls, angle=90, origin="center")
assert rls.equals(els)
# clockwise radians
rls = affinity.rotate(ls, -pi / 2, use_radians=True)
els = load_wkt("LINESTRING(320 380, 220 380, 220 320)")
assert rls.equals(els)
## other `origin` parameters
# around the centroid
rls = affinity.rotate(ls, 90, origin="centroid")
els = load_wkt("LINESTRING(182.5 320, 282.5 320, 282.5 380)")
assert rls.equals(els)
# around the second coordinate tuple
rls = affinity.rotate(ls, 90, origin=ls.coords[1])
els = load_wkt("LINESTRING(140 300, 240 300, 240 360)")
assert rls.equals(els)
# around the absolute Point of origin
rls = affinity.rotate(ls, 90, origin=Point(0, 0))
els = load_wkt("LINESTRING(-400 240, -300 240, -300 300)")
assert rls.equals(els)
def test_rotate_empty(self):
rls = affinity.rotate(load_wkt("LINESTRING EMPTY"), 90)
els = load_wkt("LINESTRING EMPTY")
assert rls.equals(els)
def test_rotate_angle_array(self):
ls = load_wkt("LINESTRING(240 400, 240 300, 300 300)")
els = load_wkt("LINESTRING(220 320, 320 320, 320 380)")
# check with degrees
theta = np.array(90.0)
rls = affinity.rotate(ls, theta)
assert theta.item() == 90.0
assert rls.equals(els)
# check with radians
theta = np.array(pi / 2)
rls = affinity.rotate(ls, theta, use_radians=True)
assert theta.item() == pi / 2
assert rls.equals(els)
def test_scale(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test defaults of 1.0
sls = affinity.scale(ls)
assert sls.equals(ls)
# different scaling in different dimensions
sls = affinity.scale(ls, 2, 3, 0.5)
els = load_wkt("LINESTRING(210 500 5, 210 200 15, 330 200 10)")
assert sls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(sls.coords, els.coords):
for ap, bp in zip(a, b):
self.assertEqual(ap, bp)
# retest with named parameters for the same result
sls = affinity.scale(geom=ls, xfact=2, yfact=3, zfact=0.5, origin="center")
assert sls.equals(els)
## other `origin` parameters
# around the centroid
sls = affinity.scale(ls, 2, 3, 0.5, origin="centroid")
els = load_wkt("LINESTRING(228.75 537.5, 228.75 237.5, 348.75 237.5)")
assert sls.equals(els)
# around the second coordinate tuple
sls = affinity.scale(ls, 2, 3, 0.5, origin=ls.coords[1])
els = load_wkt("LINESTRING(240 600, 240 300, 360 300)")
assert sls.equals(els)
# around some other 3D Point of origin
sls = affinity.scale(ls, 2, 3, 0.5, origin=Point(100, 200, 1000))
els = load_wkt("LINESTRING(380 800 505, 380 500 515, 500 500 510)")
assert sls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(sls.coords, els.coords):
for ap, bp in zip(a, b):
assert ap == bp
def test_scale_empty(self):
sls = affinity.scale(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
assert sls.equals(els)
def test_skew(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test default shear angles of 0.0
sls = affinity.skew(ls)
assert sls.equals(ls)
# different shearing in x- and y-directions
sls = affinity.skew(ls, 15, -30)
els = load_wkt(
"LINESTRING (253.39745962155615 417.3205080756888, "
"226.60254037844385 317.3205080756888, "
"286.60254037844385 282.67949192431126)"
)
assert sls.equals_exact(els, 1e-6)
# retest with radians for the same result
sls = affinity.skew(ls, pi / 12, -pi / 6, use_radians=True)
assert sls.equals_exact(els, 1e-6)
# retest with named parameters for the same result
sls = affinity.skew(geom=ls, xs=15, ys=-30, origin="center", use_radians=False)
assert sls.equals_exact(els, 1e-6)
## other `origin` parameters
# around the centroid
sls = affinity.skew(ls, 15, -30, origin="centroid")
els = load_wkt(
"LINESTRING(258.42150697963973 406.49519052838332, "
"231.6265877365273980 306.4951905283833185, "
"291.6265877365274264 271.8541743770057337)"
)
assert sls.equals_exact(els, 1e-6)
# around the second coordinate tuple
sls = affinity.skew(ls, 15, -30, origin=ls.coords[1])
els = load_wkt(
"LINESTRING(266.7949192431123038 400, 240 300, 300 265.3589838486224153)"
)
assert sls.equals_exact(els, 1e-6)
# around the absolute Point of origin
sls = affinity.skew(ls, 15, -30, origin=Point(0, 0))
els = load_wkt(
"LINESTRING(347.179676972449101 261.435935394489832, "
"320.3847577293367976 161.4359353944898317, "
"380.3847577293367976 126.7949192431122754)"
)
assert sls.equals_exact(els, 1e-6)
def test_skew_empty(self):
sls = affinity.skew(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
assert sls.equals(els)
def test_skew_xs_ys_array(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
els = load_wkt(
"LINESTRING (253.39745962155615 417.3205080756888, "
"226.60254037844385 317.3205080756888, "
"286.60254037844385 282.67949192431126)"
)
# check with degrees
xs_ys = np.array([15.0, -30.0])
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...])
assert xs_ys[0] == 15.0
assert xs_ys[1] == -30.0
assert sls.equals_exact(els, 1e-6)
# check with radians
xs_ys = np.array([pi / 12, -pi / 6])
sls = affinity.skew(ls, xs_ys[0, ...], xs_ys[1, ...], use_radians=True)
assert xs_ys[0] == pi / 12
assert xs_ys[1] == -pi / 6
assert sls.equals_exact(els, 1e-6)
def test_translate(self):
ls = load_wkt("LINESTRING(240 400 10, 240 300 30, 300 300 20)")
# test default offset of 0.0
tls = affinity.translate(ls)
assert tls.equals(ls)
# test all offsets
tls = affinity.translate(ls, 100, 400, -10)
els = load_wkt("LINESTRING(340 800 0, 340 700 20, 400 700 10)")
assert tls.equals(els)
# Do explicit 3D check of coordinate values
for a, b in zip(tls.coords, els.coords):
for ap, bp in zip(a, b):
assert ap == bp
# retest with named parameters for the same result
tls = affinity.translate(geom=ls, xoff=100, yoff=400, zoff=-10)
assert tls.equals(els)
def test_translate_empty(self):
tls = affinity.translate(load_wkt("LINESTRING EMPTY"))
els = load_wkt("LINESTRING EMPTY")
self.assertTrue(tls.equals(els))
assert tls.equals(els)