1from . import unittest
2from math import pi
3from shapely import affinity
4from shapely.wkt import loads as load_wkt
5from shapely.geometry import Point
6
7try:
8    import numpy
9except ImportError:
10    numpy = False
11
12
13class AffineTestCase(unittest.TestCase):
14
15    def test_affine_params(self):
16        g = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)')
17        self.assertRaises(
18            TypeError, affinity.affine_transform, g, None)
19        self.assertRaises(
20            TypeError, affinity.affine_transform, g, '123456')
21        self.assertRaises(ValueError, affinity.affine_transform, g,
22                          [1, 2, 3, 4, 5, 6, 7, 8, 9])
23        self.assertRaises(AttributeError, affinity.affine_transform, None,
24                          [1, 2, 3, 4, 5, 6])
25
26    def test_affine_geom_types(self):
27
28        # identity matrices, which should result with no transformation
29        matrix2d = (1, 0,
30                    0, 1,
31                    0, 0)
32        matrix3d = (1, 0, 0,
33                    0, 1, 0,
34                    0, 0, 1,
35                    0, 0, 0)
36
37        # empty in, empty out
38        empty2d = load_wkt('MULTIPOLYGON EMPTY')
39        self.assertTrue(affinity.affine_transform(empty2d, matrix2d).is_empty)
40
41        def test_geom(g2, g3=None):
42            self.assertFalse(g2.has_z)
43            a2 = affinity.affine_transform(g2, matrix2d)
44            self.assertFalse(a2.has_z)
45            self.assertTrue(g2.equals(a2))
46            if g3 is not None:
47                self.assertTrue(g3.has_z)
48                a3 = affinity.affine_transform(g3, matrix3d)
49                self.assertTrue(a3.has_z)
50                self.assertTrue(g3.equals(a3))
51            return
52
53        pt2d = load_wkt('POINT(12.3 45.6)')
54        pt3d = load_wkt('POINT(12.3 45.6 7.89)')
55        test_geom(pt2d, pt3d)
56        ls2d = load_wkt('LINESTRING(0.9 3.4, 0.7 2, 2.5 2.7)')
57        ls3d = load_wkt('LINESTRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5)')
58        test_geom(ls2d, ls3d)
59        lr2d = load_wkt('LINEARRING(0.9 3.4, 0.7 2, 2.5 2.7, 0.9 3.4)')
60        lr3d = load_wkt(
61            'LINEARRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5, 0.9 3.4 3.3)')
62        test_geom(lr2d, lr3d)
63        test_geom(load_wkt('POLYGON((0.9 2.3, 0.5 1.1, 2.4 0.8, 0.9 2.3), '
64                           '(1.1 1.7, 0.9 1.3, 1.4 1.2, 1.1 1.7), '
65                           '(1.6 1.3, 1.7 1, 1.9 1.1, 1.6 1.3))'))
66        test_geom(load_wkt(
67            'MULTIPOINT ((-300 300), (700 300), (-800 -1100), (200 -300))'))
68        test_geom(load_wkt(
69            'MULTILINESTRING((0 0, -0.7 -0.7, 0.6 -1), '
70            '(-0.5 0.5, 0.7 0.6, 0 -0.6))'))
71        test_geom(load_wkt(
72            'MULTIPOLYGON(((900 4300, -1100 -400, 900 -800, 900 4300)), '
73            '((1200 4300, 2300 4400, 1900 1000, 1200 4300)))'))
74        test_geom(load_wkt('GEOMETRYCOLLECTION(POINT(20 70),'
75                      ' POLYGON((60 70, 13 35, 60 -30, 60 70)),'
76                      ' LINESTRING(60 70, 50 100, 80 100))'))
77
78    def test_affine_2d(self):
79        g = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)')
80        # custom scale and translate
81        expected2d = load_wkt('LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)')
82        matrix2d = (2, 0,
83                    0, 2.5,
84                    -5, 4.1)
85        a2 = affinity.affine_transform(g, matrix2d)
86        self.assertTrue(a2.equals_exact(expected2d, 1e-6))
87        self.assertFalse(a2.has_z)
88        # Make sure a 3D matrix does not make a 3D shape from a 2D input
89        matrix3d = (2, 0, 0,
90                    0, 2.5, 0,
91                    0, 0, 10,
92                    -5, 4.1, 100)
93        a3 = affinity.affine_transform(g, matrix3d)
94        self.assertTrue(a3.equals_exact(expected2d, 1e-6))
95        self.assertFalse(a3.has_z)
96
97    def test_affine_3d(self):
98        g2 = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)')
99        g3 = load_wkt('LINESTRING(2.4 4.1 100.2, 2.4 3 132.8, 3 3 128.6)')
100        # custom scale and translate
101        matrix2d = (2, 0,
102                    0, 2.5,
103                    -5, 4.1)
104        matrix3d = (2, 0, 0,
105                    0, 2.5, 0,
106                    0, 0, 0.3048,
107                    -5, 4.1, 100)
108        # Combinations of 2D and 3D geometries and matrices
109        a22 = affinity.affine_transform(g2, matrix2d)
110        a23 = affinity.affine_transform(g2, matrix3d)
111        a32 = affinity.affine_transform(g3, matrix2d)
112        a33 = affinity.affine_transform(g3, matrix3d)
113        # Check dimensions
114        self.assertFalse(a22.has_z)
115        self.assertFalse(a23.has_z)
116        self.assertTrue(a32.has_z)
117        self.assertTrue(a33.has_z)
118        # 2D equality checks
119        expected2d = load_wkt('LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)')
120        expected3d = load_wkt('LINESTRING(-0.2 14.35 130.54096, '
121                              '-0.2 11.6 140.47744, 1 11.6 139.19728)')
122        expected32 = load_wkt('LINESTRING(-0.2 14.35 100.2, '
123                              '-0.2 11.6 132.8, 1 11.6 128.6)')
124        self.assertTrue(a22.equals_exact(expected2d, 1e-6))
125        self.assertTrue(a23.equals_exact(expected2d, 1e-6))
126        # Do explicit 3D check of coordinate values
127        for a, e in zip(a32.coords, expected32.coords):
128            for ap, ep in zip(a, e):
129                self.assertAlmostEqual(ap, ep)
130        for a, e in zip(a33.coords, expected3d.coords):
131            for ap, ep in zip(a, e):
132                self.assertAlmostEqual(ap, ep)
133
134
135class TransformOpsTestCase(unittest.TestCase):
136
137    def test_rotate(self):
138        ls = load_wkt('LINESTRING(240 400, 240 300, 300 300)')
139        # counter-clockwise degrees
140        rls = affinity.rotate(ls, 90)
141        els = load_wkt('LINESTRING(220 320, 320 320, 320 380)')
142        self.assertTrue(rls.equals(els))
143        # retest with named parameters for the same result
144        rls = affinity.rotate(geom=ls, angle=90, origin='center')
145        self.assertTrue(rls.equals(els))
146        # clockwise radians
147        rls = affinity.rotate(ls, -pi/2, use_radians=True)
148        els = load_wkt('LINESTRING(320 380, 220 380, 220 320)')
149        self.assertTrue(rls.equals(els))
150        ## other `origin` parameters
151        # around the centroid
152        rls = affinity.rotate(ls, 90, origin='centroid')
153        els = load_wkt('LINESTRING(182.5 320, 282.5 320, 282.5 380)')
154        self.assertTrue(rls.equals(els))
155        # around the second coordinate tuple
156        rls = affinity.rotate(ls, 90, origin=ls.coords[1])
157        els = load_wkt('LINESTRING(140 300, 240 300, 240 360)')
158        self.assertTrue(rls.equals(els))
159        # around the absolute Point of origin
160        rls = affinity.rotate(ls, 90, origin=Point(0, 0))
161        els = load_wkt('LINESTRING(-400 240, -300 240, -300 300)')
162        self.assertTrue(rls.equals(els))
163
164    def test_rotate_empty(self):
165        rls = affinity.rotate(load_wkt('LINESTRING EMPTY'), 90)
166        els = load_wkt('LINESTRING EMPTY')
167        self.assertTrue(rls.equals(els))
168
169    @unittest.skipIf(not numpy, 'numpy not installed')
170    def test_rotate_angle_array(self):
171        ls = load_wkt('LINESTRING(240 400, 240 300, 300 300)')
172        els = load_wkt('LINESTRING(220 320, 320 320, 320 380)')
173        # check with degrees
174        theta = numpy.array([90.0])
175        rls = affinity.rotate(ls, theta)
176        self.assertEqual(theta[0], 90.0)
177        self.assertTrue(rls.equals(els))
178        # check with radians
179        theta = numpy.array([pi/2])
180        rls = affinity.rotate(ls, theta, use_radians=True)
181        self.assertEqual(theta[0], pi/2)
182        self.assertTrue(rls.equals(els))
183
184    def test_scale(self):
185        ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)')
186        # test defaults of 1.0
187        sls = affinity.scale(ls)
188        self.assertTrue(sls.equals(ls))
189        # different scaling in different dimensions
190        sls = affinity.scale(ls, 2, 3, 0.5)
191        els = load_wkt('LINESTRING(210 500 5, 210 200 15, 330 200 10)')
192        self.assertTrue(sls.equals(els))
193        # Do explicit 3D check of coordinate values
194        for a, b in zip(sls.coords, els.coords):
195            for ap, bp in zip(a, b):
196                self.assertEqual(ap, bp)
197        # retest with named parameters for the same result
198        sls = affinity.scale(geom=ls, xfact=2, yfact=3, zfact=0.5,
199                             origin='center')
200        self.assertTrue(sls.equals(els))
201        ## other `origin` parameters
202        # around the centroid
203        sls = affinity.scale(ls, 2, 3, 0.5, origin='centroid')
204        els = load_wkt('LINESTRING(228.75 537.5, 228.75 237.5, 348.75 237.5)')
205        self.assertTrue(sls.equals(els))
206        # around the second coordinate tuple
207        sls = affinity.scale(ls, 2, 3, 0.5, origin=ls.coords[1])
208        els = load_wkt('LINESTRING(240 600, 240 300, 360 300)')
209        self.assertTrue(sls.equals(els))
210        # around some other 3D Point of origin
211        sls = affinity.scale(ls, 2, 3, 0.5, origin=Point(100, 200, 1000))
212        els = load_wkt('LINESTRING(380 800 505, 380 500 515, 500 500 510)')
213        self.assertTrue(sls.equals(els))
214        # Do explicit 3D check of coordinate values
215        for a, b in zip(sls.coords, els.coords):
216            for ap, bp in zip(a, b):
217                self.assertEqual(ap, bp)
218
219    def test_scale_empty(self):
220        sls = affinity.scale(load_wkt('LINESTRING EMPTY'))
221        els = load_wkt('LINESTRING EMPTY')
222        self.assertTrue(sls.equals(els))
223
224    def test_skew(self):
225        ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)')
226        # test default shear angles of 0.0
227        sls = affinity.skew(ls)
228        self.assertTrue(sls.equals(ls))
229        # different shearing in x- and y-directions
230        sls = affinity.skew(ls, 15, -30)
231        els = load_wkt('LINESTRING (253.39745962155615 417.3205080756888, '
232                       '226.60254037844385 317.3205080756888, '
233                       '286.60254037844385 282.67949192431126)')
234        self.assertTrue(sls.equals_exact(els, 1e-6))
235        # retest with radians for the same result
236        sls = affinity.skew(ls, pi/12, -pi/6, use_radians=True)
237        self.assertTrue(sls.equals_exact(els, 1e-6))
238        # retest with named parameters for the same result
239        sls = affinity.skew(geom=ls, xs=15, ys=-30,
240                            origin='center', use_radians=False)
241        self.assertTrue(sls.equals_exact(els, 1e-6))
242        ## other `origin` parameters
243        # around the centroid
244        sls = affinity.skew(ls, 15, -30, origin='centroid')
245        els = load_wkt('LINESTRING(258.42150697963973 406.49519052838332, '
246                       '231.6265877365273980 306.4951905283833185, '
247                       '291.6265877365274264 271.8541743770057337)')
248        self.assertTrue(sls.equals_exact(els, 1e-6))
249        # around the second coordinate tuple
250        sls = affinity.skew(ls, 15, -30, origin=ls.coords[1])
251        els = load_wkt('LINESTRING(266.7949192431123038 400, 240 300, '
252                       '300 265.3589838486224153)')
253        self.assertTrue(sls.equals_exact(els, 1e-6))
254        # around the absolute Point of origin
255        sls = affinity.skew(ls, 15, -30, origin=Point(0, 0))
256        els = load_wkt('LINESTRING(347.179676972449101 261.435935394489832, '
257                       '320.3847577293367976 161.4359353944898317, '
258                       '380.3847577293367976 126.7949192431122754)')
259        self.assertTrue(sls.equals_exact(els, 1e-6))
260
261    def test_skew_empty(self):
262        sls = affinity.skew(load_wkt('LINESTRING EMPTY'))
263        els = load_wkt('LINESTRING EMPTY')
264        self.assertTrue(sls.equals(els))
265
266    @unittest.skipIf(not numpy, 'numpy not installed')
267    def test_skew_xs_ys_array(self):
268        ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)')
269        els = load_wkt('LINESTRING (253.39745962155615 417.3205080756888, '
270                       '226.60254037844385 317.3205080756888, '
271                       '286.60254037844385 282.67949192431126)')
272        # check with degrees
273        xs_ys = numpy.array([15.0, -30.0])
274        sls = affinity.skew(ls, xs_ys[0:1], xs_ys[1:2])
275        self.assertEqual(xs_ys[0], 15.0)
276        self.assertEqual(xs_ys[1], -30.0)
277        self.assertTrue(sls.equals_exact(els, 1e-6))
278        # check with radians
279        xs_ys = numpy.array([pi/12, -pi/6])
280        sls = affinity.skew(ls, xs_ys[0:1], xs_ys[1:2], use_radians=True)
281        self.assertEqual(xs_ys[0], pi/12)
282        self.assertEqual(xs_ys[1], -pi/6)
283        self.assertTrue(sls.equals_exact(els, 1e-6))
284
285    def test_translate(self):
286        ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)')
287        # test default offset of 0.0
288        tls = affinity.translate(ls)
289        self.assertTrue(tls.equals(ls))
290        # test all offsets
291        tls = affinity.translate(ls, 100, 400, -10)
292        els = load_wkt('LINESTRING(340 800 0, 340 700 20, 400 700 10)')
293        self.assertTrue(tls.equals(els))
294        # Do explicit 3D check of coordinate values
295        for a, b in zip(tls.coords, els.coords):
296            for ap, bp in zip(a, b):
297                self.assertEqual(ap, bp)
298        # retest with named parameters for the same result
299        tls = affinity.translate(geom=ls, xoff=100, yoff=400, zoff=-10)
300        self.assertTrue(tls.equals(els))
301
302    def test_translate_empty(self):
303        tls = affinity.translate(load_wkt('LINESTRING EMPTY'))
304        els = load_wkt('LINESTRING EMPTY')
305        self.assertTrue(tls.equals(els))
306