test_polygon.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. """Polygons and Linear Rings"""
  2. import numpy as np
  3. import pytest
  4. from shapely import LinearRing, LineString, Point, Polygon
  5. from shapely.coords import CoordinateSequence
  6. from shapely.errors import TopologicalError
  7. from shapely.wkb import loads as load_wkb
  8. def test_empty_linearring_coords():
  9. assert LinearRing().coords[:] == []
  10. def test_linearring_from_coordinate_sequence():
  11. expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  12. ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  13. assert ring.coords[:] == expected_coords
  14. ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  15. assert ring.coords[:] == expected_coords
  16. def test_linearring_from_points():
  17. # From Points
  18. expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  19. ring = LinearRing([Point(0.0, 0.0), Point(0.0, 1.0), Point(1.0, 1.0)])
  20. assert ring.coords[:] == expected_coords
  21. def test_linearring_from_closed_linestring():
  22. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  23. line = LineString(coords)
  24. ring = LinearRing(line)
  25. assert len(ring.coords) == 4
  26. assert ring.coords[:] == coords
  27. assert ring.geom_type == "LinearRing"
  28. def test_linearring_from_unclosed_linestring():
  29. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  30. line = LineString(coords[:-1]) # Pass in unclosed line
  31. ring = LinearRing(line)
  32. assert len(ring.coords) == 4
  33. assert ring.coords[:] == coords
  34. assert ring.geom_type == "LinearRing"
  35. def test_linearring_from_invalid():
  36. coords = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
  37. line = LineString(coords)
  38. assert not line.is_valid
  39. with pytest.raises(TopologicalError):
  40. LinearRing(line)
  41. def test_linearring_from_too_short_linestring():
  42. # Creation of LinearRing request at least 3 coordinates (unclosed) or
  43. # 4 coordinates (closed)
  44. coords = [(0.0, 0.0), (1.0, 1.0)]
  45. line = LineString(coords)
  46. with pytest.raises(ValueError, match="requires at least 4 coordinates"):
  47. LinearRing(line)
  48. def test_linearring_from_linearring():
  49. coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  50. ring = LinearRing(coords)
  51. assert ring.coords[:] == coords
  52. def test_linearring_from_generator():
  53. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  54. gen = (coord for coord in coords)
  55. ring = LinearRing(gen)
  56. assert ring.coords[:] == coords
  57. def test_linearring_from_empty():
  58. ring = LinearRing()
  59. assert ring.is_empty
  60. assert isinstance(ring.coords, CoordinateSequence)
  61. assert ring.coords[:] == []
  62. ring = LinearRing([])
  63. assert ring.is_empty
  64. assert isinstance(ring.coords, CoordinateSequence)
  65. assert ring.coords[:] == []
  66. def test_linearring_from_numpy():
  67. # Construct from a numpy array
  68. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  69. ring = LinearRing(np.array(coords))
  70. assert ring.coords[:] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  71. def test_numpy_linearring_coords():
  72. from numpy.testing import assert_array_equal
  73. ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  74. ra = np.asarray(ring.coords)
  75. expected = np.asarray([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)])
  76. assert_array_equal(ra, expected)
  77. def test_numpy_empty_linearring_coords():
  78. ring = LinearRing()
  79. assert np.asarray(ring.coords).shape == (0, 2)
  80. def test_numpy_object_array():
  81. geom = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  82. ar = np.empty(1, object)
  83. ar[:] = [geom]
  84. assert ar[0] == geom
  85. def test_polygon_from_coordinate_sequence():
  86. coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  87. # Construct a polygon, exterior ring only
  88. polygon = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  89. assert polygon.exterior.coords[:] == coords
  90. assert len(polygon.interiors) == 0
  91. polygon = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)])
  92. assert polygon.exterior.coords[:] == coords
  93. assert len(polygon.interiors) == 0
  94. def test_polygon_from_coordinate_sequence_with_holes():
  95. coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  96. # Interior rings (holes)
  97. polygon = Polygon(coords, [[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)]])
  98. assert polygon.exterior.coords[:] == coords
  99. assert len(polygon.interiors) == 1
  100. assert len(polygon.interiors[0].coords) == 5
  101. # Multiple interior rings with different length
  102. coords = [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]
  103. holes = [
  104. [(1, 1), (2, 1), (2, 2), (1, 2), (1, 1)],
  105. [(3, 3), (3, 4), (4, 5), (5, 4), (5, 3), (3, 3)],
  106. ]
  107. polygon = Polygon(coords, holes)
  108. assert polygon.exterior.coords[:] == coords
  109. assert len(polygon.interiors) == 2
  110. assert len(polygon.interiors[0].coords) == 5
  111. assert len(polygon.interiors[1].coords) == 6
  112. def test_polygon_from_linearring():
  113. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  114. ring = LinearRing(coords)
  115. polygon = Polygon(ring)
  116. assert polygon.exterior.coords[:] == coords
  117. assert len(polygon.interiors) == 0
  118. # from shell and holes linearrings
  119. shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
  120. holes = [
  121. LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
  122. LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
  123. LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
  124. ]
  125. polygon = Polygon(shell, holes)
  126. assert polygon.exterior.coords[:] == shell.coords[:]
  127. assert len(polygon.interiors) == 3
  128. for i in range(3):
  129. assert polygon.interiors[i].coords[:] == holes[i].coords[:]
  130. def test_polygon_from_linestring():
  131. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  132. line = LineString(coords)
  133. polygon = Polygon(line)
  134. assert polygon.exterior.coords[:] == coords
  135. # from unclosed linestring
  136. line = LineString(coords[:-1])
  137. polygon = Polygon(line)
  138. assert polygon.exterior.coords[:] == coords
  139. def test_polygon_from_points():
  140. polygon = Polygon([Point(0.0, 0.0), Point(0.0, 1.0), Point(1.0, 1.0)])
  141. expected_coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]
  142. assert polygon.exterior.coords[:] == expected_coords
  143. def test_polygon_from_polygon():
  144. coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]
  145. polygon = Polygon(coords, [[(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)]])
  146. # Test from another Polygon
  147. copy = Polygon(polygon)
  148. assert len(copy.exterior.coords) == 5
  149. assert len(copy.interiors) == 1
  150. assert len(copy.interiors[0].coords) == 5
  151. def test_polygon_from_invalid():
  152. # Error handling
  153. with pytest.raises(ValueError):
  154. # A LinearRing must have at least 3 coordinate tuples
  155. Polygon([[1, 2], [2, 3]])
  156. def test_polygon_from_empty():
  157. polygon = Polygon()
  158. assert polygon.is_empty
  159. assert polygon.exterior.coords[:] == []
  160. polygon = Polygon([])
  161. assert polygon.is_empty
  162. assert polygon.exterior.coords[:] == []
  163. def test_polygon_from_numpy():
  164. a = np.array(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)))
  165. polygon = Polygon(a)
  166. assert len(polygon.exterior.coords) == 5
  167. assert polygon.exterior.coords[:] == [
  168. (0.0, 0.0),
  169. (0.0, 1.0),
  170. (1.0, 1.0),
  171. (1.0, 0.0),
  172. (0.0, 0.0),
  173. ]
  174. assert len(polygon.interiors) == 0
  175. def test_polygon_from_generator():
  176. coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
  177. gen = (coord for coord in coords)
  178. polygon = Polygon(gen)
  179. assert polygon.exterior.coords[:] == coords
  180. class TestPolygon:
  181. def test_linearring(self):
  182. # Initialization
  183. # Linear rings won't usually be created by users, but by polygons
  184. coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0))
  185. ring = LinearRing(coords)
  186. assert len(ring.coords) == 5
  187. assert ring.coords[0] == ring.coords[4]
  188. assert ring.coords[0] == ring.coords[-1]
  189. assert ring.is_ring is True
  190. def test_polygon(self):
  191. coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0))
  192. # Construct a polygon, exterior ring only
  193. polygon = Polygon(coords)
  194. assert len(polygon.exterior.coords) == 5
  195. # Ring Access
  196. assert isinstance(polygon.exterior, LinearRing)
  197. ring = polygon.exterior
  198. assert len(ring.coords) == 5
  199. assert ring.coords[0] == ring.coords[4]
  200. assert ring.coords[0] == (0.0, 0.0)
  201. assert ring.is_ring is True
  202. assert len(polygon.interiors) == 0
  203. # Create a new polygon from WKB
  204. data = polygon.wkb
  205. polygon = None
  206. ring = None
  207. polygon = load_wkb(data)
  208. ring = polygon.exterior
  209. assert len(ring.coords) == 5
  210. assert ring.coords[0] == ring.coords[4]
  211. assert ring.coords[0] == (0.0, 0.0)
  212. assert ring.is_ring is True
  213. polygon = None
  214. # Interior rings (holes)
  215. polygon = Polygon(
  216. coords, [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))]
  217. )
  218. assert len(polygon.exterior.coords) == 5
  219. assert len(polygon.interiors[0].coords) == 5
  220. with pytest.raises(IndexError): # index out of range
  221. polygon.interiors[1]
  222. # Coordinate getter raises exceptions
  223. with pytest.raises(NotImplementedError):
  224. polygon.coords
  225. # Geo interface
  226. assert polygon.__geo_interface__ == {
  227. "type": "Polygon",
  228. "coordinates": (
  229. ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)),
  230. ((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)),
  231. ),
  232. }
  233. def test_linearring_empty(self):
  234. # Test Non-operability of Null rings
  235. r_null = LinearRing()
  236. assert r_null.wkt == "LINEARRING EMPTY"
  237. assert r_null.length == 0.0
  238. def test_dimensions(self):
  239. # Background: see http://trac.gispython.org/lab/ticket/168
  240. # http://lists.gispython.org/pipermail/community/2008-August/001859.html
  241. coords = ((0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0))
  242. polygon = Polygon(coords)
  243. assert polygon._ndim == 3
  244. gi = polygon.__geo_interface__
  245. assert gi["coordinates"] == (
  246. (
  247. (0.0, 0.0, 0.0),
  248. (0.0, 1.0, 0.0),
  249. (1.0, 1.0, 0.0),
  250. (1.0, 0.0, 0.0),
  251. (0.0, 0.0, 0.0),
  252. ),
  253. )
  254. e = polygon.exterior
  255. assert e._ndim == 3
  256. gi = e.__geo_interface__
  257. assert gi["coordinates"] == (
  258. (0.0, 0.0, 0.0),
  259. (0.0, 1.0, 0.0),
  260. (1.0, 1.0, 0.0),
  261. (1.0, 0.0, 0.0),
  262. (0.0, 0.0, 0.0),
  263. )
  264. def test_attribute_chains(self):
  265. # Attribute Chaining
  266. # See also ticket #151.
  267. p = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
  268. assert list(p.boundary.coords) == [
  269. (0.0, 0.0),
  270. (0.0, 1.0),
  271. (-1.0, 1.0),
  272. (-1.0, 0.0),
  273. (0.0, 0.0),
  274. ]
  275. ec = list(Point(0.0, 0.0).buffer(1.0, quad_segs=1).exterior.coords)
  276. assert isinstance(ec, list) # TODO: this is a poor test
  277. # Test chained access to interiors
  278. p = Polygon(
  279. [(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)],
  280. [[(-0.25, 0.25), (-0.25, 0.75), (-0.75, 0.75), (-0.75, 0.25)]],
  281. )
  282. assert p.area == 0.75
  283. """Not so much testing the exact values here, which are the
  284. responsibility of the geometry engine (GEOS), but that we can get
  285. chain functions and properties using anonymous references.
  286. """
  287. assert list(p.interiors[0].coords) == [
  288. (-0.25, 0.25),
  289. (-0.25, 0.75),
  290. (-0.75, 0.75),
  291. (-0.75, 0.25),
  292. (-0.25, 0.25),
  293. ]
  294. xy = next(iter(p.interiors[0].buffer(1).exterior.coords))
  295. assert len(xy) == 2
  296. # Test multiple operators, boundary of a buffer
  297. ec = list(p.buffer(1).boundary.coords)
  298. assert isinstance(ec, list) # TODO: this is a poor test
  299. def test_empty_equality(self):
  300. # Test equals operator, including empty geometries
  301. # see issue #338
  302. point1 = Point(0, 0)
  303. polygon1 = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
  304. polygon2 = Polygon([(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)])
  305. polygon_empty1 = Polygon()
  306. polygon_empty2 = Polygon()
  307. assert point1 != polygon1
  308. assert polygon_empty1 == polygon_empty2
  309. assert polygon1 != polygon_empty1
  310. assert polygon1 == polygon2
  311. assert polygon_empty1 is not None
  312. def test_from_bounds(self):
  313. xmin, ymin, xmax, ymax = -180, -90, 180, 90
  314. coords = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
  315. assert Polygon(coords) == Polygon.from_bounds(xmin, ymin, xmax, ymax)
  316. def test_empty_polygon_exterior(self):
  317. p = Polygon()
  318. assert p.exterior == LinearRing()
  319. def test_linearring_immutable():
  320. ring = LinearRing([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
  321. with pytest.raises(AttributeError):
  322. ring.coords = [(1.0, 1.0), (2.0, 2.0), (1.0, 2.0)]
  323. with pytest.raises(TypeError):
  324. ring.coords[0] = (1.0, 1.0)
  325. class TestLinearRingGetItem:
  326. def test_index_linearring(self):
  327. shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
  328. holes = [
  329. LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
  330. LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
  331. LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
  332. ]
  333. g = Polygon(shell, holes)
  334. for i in range(-3, 3):
  335. assert g.interiors[i].equals(holes[i])
  336. with pytest.raises(IndexError):
  337. g.interiors[3]
  338. with pytest.raises(IndexError):
  339. g.interiors[-4]
  340. def test_index_linearring_misc(self):
  341. g = Polygon() # empty
  342. with pytest.raises(IndexError):
  343. g.interiors[0]
  344. with pytest.raises(TypeError):
  345. g.interiors[0.0]
  346. def test_slice_linearring(self):
  347. shell = LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)])
  348. holes = [
  349. LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]),
  350. LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]),
  351. LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)]),
  352. ]
  353. g = Polygon(shell, holes)
  354. t = [a.equals(b) for (a, b) in zip(g.interiors[1:], holes[1:])]
  355. assert all(t)
  356. t = [a.equals(b) for (a, b) in zip(g.interiors[:-1], holes[:-1])]
  357. assert all(t)
  358. t = [a.equals(b) for (a, b) in zip(g.interiors[::-1], holes[::-1])]
  359. assert all(t)
  360. t = [a.equals(b) for (a, b) in zip(g.interiors[::2], holes[::2])]
  361. assert all(t)
  362. t = [a.equals(b) for (a, b) in zip(g.interiors[:3], holes[:3])]
  363. assert all(t)
  364. assert g.interiors[3:] == holes[3:] == []