test_affinity.py 12 KB

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