test_coordinates.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import numpy as np
  2. import pytest
  3. from numpy.testing import assert_allclose, assert_equal
  4. import shapely
  5. from shapely import count_coordinates, get_coordinates, set_coordinates, transform
  6. from shapely.tests.common import (
  7. empty,
  8. empty_line_string_z,
  9. empty_point,
  10. empty_point_m,
  11. empty_point_z,
  12. empty_point_zm,
  13. geometry_collection,
  14. geometry_collection_z,
  15. geometry_collection_zm,
  16. line_string,
  17. line_string_m,
  18. line_string_z,
  19. line_string_zm,
  20. linear_ring,
  21. multi_line_string,
  22. multi_point,
  23. multi_polygon,
  24. point,
  25. point_m,
  26. point_z,
  27. point_zm,
  28. polygon,
  29. polygon_with_hole,
  30. polygon_z,
  31. )
  32. nested_2 = shapely.geometrycollections([geometry_collection, point])
  33. nested_3 = shapely.geometrycollections([nested_2, point])
  34. @pytest.mark.parametrize(
  35. "geoms,count",
  36. [
  37. ([], 0),
  38. ([empty], 0),
  39. ([point, empty], 1),
  40. ([empty, point, empty], 1),
  41. ([point, None], 1),
  42. ([None, point, None], 1),
  43. ([point, point], 2),
  44. ([point, point_z], 2),
  45. ([line_string, linear_ring], 8),
  46. ([polygon], 5),
  47. ([polygon_with_hole], 10),
  48. ([multi_point, multi_line_string], 4),
  49. ([multi_polygon], 10),
  50. ([geometry_collection], 3),
  51. ([nested_2], 4),
  52. ([nested_3], 5),
  53. ],
  54. )
  55. def test_count_coords(geoms, count):
  56. actual = count_coordinates(np.array(geoms, np.object_))
  57. assert actual == count
  58. @pytest.mark.parametrize("include_m", [True, False])
  59. @pytest.mark.parametrize("include_z", [True, False])
  60. @pytest.mark.parametrize(
  61. "geoms,x,y",
  62. [
  63. ([], [], []),
  64. ([empty], [], []),
  65. ([point, empty], [2], [3]),
  66. ([empty, point, empty], [2], [3]),
  67. ([point, None], [2], [3]),
  68. ([None, point, None], [2], [3]),
  69. ([point, point], [2, 2], [3, 3]),
  70. (
  71. [line_string, linear_ring],
  72. [0, 1, 1, 0, 1, 1, 0, 0],
  73. [0, 0, 1, 0, 0, 1, 1, 0],
  74. ),
  75. ([polygon], [0, 2, 2, 0, 0], [0, 0, 2, 2, 0]),
  76. (
  77. [polygon_with_hole],
  78. [0, 0, 10, 10, 0, 2, 2, 4, 4, 2],
  79. [0, 10, 10, 0, 0, 2, 4, 4, 2, 2],
  80. ),
  81. ([multi_point, multi_line_string], [0, 1, 0, 1], [0, 2, 0, 2]),
  82. (
  83. [multi_polygon],
  84. [0, 1, 1, 0, 0, 2.1, 2.2, 2.2, 2.1, 2.1],
  85. [0, 0, 1, 1, 0, 2.1, 2.1, 2.2, 2.2, 2.1],
  86. ),
  87. ([geometry_collection], [51, 52, 49], [-1, -1, 2]),
  88. ([nested_2], [51, 52, 49, 2], [-1, -1, 2, 3]),
  89. ([nested_3], [51, 52, 49, 2, 2], [-1, -1, 2, 3, 3]),
  90. ],
  91. )
  92. def test_get_coords(geoms, x, y, include_z, include_m):
  93. actual = get_coordinates(geoms, include_z=include_z, include_m=include_m)
  94. expected = [x, y]
  95. if include_z:
  96. expected.append([np.nan] * len(x))
  97. if include_m:
  98. expected.append([np.nan] * len(x))
  99. assert_equal(actual, np.array(expected, np.float64).T)
  100. @pytest.mark.parametrize(
  101. "geoms,index",
  102. [
  103. ([], []),
  104. ([empty], []),
  105. ([point, empty], [0]),
  106. ([empty, point, empty], [1]),
  107. ([point, None], [0]),
  108. ([None, point, None], [1]),
  109. ([point, point], [0, 1]),
  110. ([point, line_string], [0, 1, 1, 1]),
  111. ([line_string, point], [0, 0, 0, 1]),
  112. ([line_string, linear_ring], [0, 0, 0, 1, 1, 1, 1, 1]),
  113. ],
  114. )
  115. def test_get_coords_index(geoms, index):
  116. _, actual = get_coordinates(np.array(geoms, np.object_), return_index=True)
  117. expected = np.array(index, dtype=np.intp)
  118. assert_equal(actual, expected)
  119. @pytest.mark.parametrize("order", ["C", "F"])
  120. def test_get_coords_index_multidim(order):
  121. geometry = np.array([[point, line_string], [empty, empty]], order=order)
  122. expected = [0, 1, 1, 1] # would be [0, 2, 2, 2] with fortran order
  123. _, actual = get_coordinates(geometry, return_index=True)
  124. assert_equal(actual, expected)
  125. @pytest.mark.parametrize("include_m", [True, False])
  126. @pytest.mark.parametrize("include_z", [True, False])
  127. @pytest.mark.parametrize(
  128. "geoms,x,y,z",
  129. [
  130. ([point, point_z], [2, 2], [3, 3], [np.nan, 4]),
  131. ([line_string_z], [0, 1, 1], [0, 0, 1], [4, 4, 4]),
  132. ([polygon_z], [0, 2, 2, 0, 0], [0, 0, 2, 2, 0], [4, 4, 4, 4, 4]),
  133. ([geometry_collection_z], [2, 0, 1, 1], [3, 0, 0, 1], [4, 4, 4, 4]),
  134. ([point, empty_point], [2], [3], [np.nan]),
  135. ],
  136. )
  137. def test_get_coords_z(geoms, x, y, z, include_z, include_m):
  138. actual = get_coordinates(geoms, include_z=include_z, include_m=include_m)
  139. expected = [x, y]
  140. if include_z:
  141. expected.append(z)
  142. if include_m:
  143. expected.append([np.nan] * len(x))
  144. assert_equal(actual, np.array(expected, np.float64).T)
  145. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  146. @pytest.mark.parametrize("include_m", [True, False])
  147. @pytest.mark.parametrize("include_z", [True, False])
  148. @pytest.mark.parametrize(
  149. "geoms,x,y,z,m",
  150. [
  151. (
  152. [point, point_z, point_m, point_zm],
  153. [2, 2, 2, 2],
  154. [3, 3, 3, 3],
  155. [np.nan, 4, np.nan, 4],
  156. [np.nan, np.nan, 5, 5],
  157. ),
  158. (
  159. [line_string, line_string_z, line_string_m, line_string_zm],
  160. [0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1],
  161. [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
  162. [np.nan, np.nan, np.nan, 4, 4, 4] * 2,
  163. [np.nan] * 6 + [1, 2, 3, 1, 2, 3],
  164. ),
  165. (
  166. [geometry_collection_zm],
  167. [2, 0, 1, 1],
  168. [3, 0, 0, 1],
  169. [4, 4, 4, 4],
  170. [5, 1, 2, 3],
  171. ),
  172. (
  173. [point, empty_point, empty_point_z, empty_point_m, empty_point_zm],
  174. [2],
  175. [3],
  176. [np.nan],
  177. [np.nan],
  178. ),
  179. ],
  180. )
  181. def test_get_coords_zm(geoms, x, y, z, m, include_z, include_m):
  182. actual = get_coordinates(geoms, include_z=include_z, include_m=include_m)
  183. expected = [x, y]
  184. if include_z:
  185. expected.append(z)
  186. if include_m:
  187. expected.append(m)
  188. assert_equal(actual, np.array(expected, np.float64).T)
  189. def test_get_coords_deprecate_positional():
  190. with pytest.deprecated_call(
  191. match="positional argument `include_z` for `get_coordinates` is deprecated"
  192. ):
  193. get_coordinates(point, False)
  194. with pytest.deprecated_call(
  195. match="positional arguments `include_z` and `return_index` "
  196. "for `get_coordinates` are deprecated"
  197. ):
  198. get_coordinates(point, False, False)
  199. @pytest.mark.parametrize("include_z", [True, False])
  200. @pytest.mark.parametrize(
  201. "geoms,count,has_ring",
  202. [
  203. ([], 0, False),
  204. ([empty], 0, False),
  205. ([empty_point], 0, False),
  206. ([point, empty], 1, False),
  207. ([empty, point, empty], 1, False),
  208. ([point, None], 1, False),
  209. ([None, point, None], 1, False),
  210. ([point, point], 2, False),
  211. ([point, point_z], 2, False),
  212. ([line_string, linear_ring], 8, True),
  213. ([line_string_z], 3, True),
  214. ([polygon], 5, True),
  215. ([polygon_z], 5, True),
  216. ([polygon_with_hole], 10, True),
  217. ([multi_point, multi_line_string], 4, False),
  218. ([multi_polygon], 10, True),
  219. ([geometry_collection], 3, False),
  220. ([geometry_collection_z], 3, False),
  221. ([nested_2], 4, False),
  222. ([nested_3], 5, False),
  223. ],
  224. )
  225. def test_set_coords(geoms, count, has_ring, include_z):
  226. arr_geoms = np.array(geoms, np.object_)
  227. n = 3 if include_z else 2
  228. coords = get_coordinates(arr_geoms, include_z=include_z) + np.random.random((1, n))
  229. new_geoms = set_coordinates(arr_geoms, coords)
  230. assert_equal(coords, get_coordinates(new_geoms, include_z=include_z))
  231. def test_set_coords_nan():
  232. geoms = np.array([point])
  233. coords = np.array([[np.nan, np.inf]])
  234. new_geoms = set_coordinates(geoms, coords)
  235. assert_equal(coords, get_coordinates(new_geoms))
  236. def test_set_coords_breaks_ring():
  237. with pytest.raises(shapely.GEOSException):
  238. set_coordinates(linear_ring, np.random.random((5, 2)))
  239. def test_set_coords_0dim():
  240. # a geometry input returns a geometry
  241. actual = set_coordinates(point, [[1, 1]])
  242. assert isinstance(actual, shapely.Geometry)
  243. # a 0-dim array input returns a 0-dim array
  244. actual = set_coordinates(np.asarray(point), [[1, 1]])
  245. assert isinstance(actual, np.ndarray)
  246. assert actual.ndim == 0
  247. @pytest.mark.parametrize("include_z", [True, False])
  248. def test_set_coords_mixed_dimension(include_z):
  249. geoms = np.array([point, point_z], dtype=object)
  250. coords = get_coordinates(geoms, include_z=include_z)
  251. new_geoms = set_coordinates(geoms, coords * 2)
  252. if include_z:
  253. # preserve original dimensionality
  254. assert not shapely.has_z(new_geoms[0])
  255. assert shapely.has_z(new_geoms[1])
  256. else:
  257. # all 2D
  258. assert not shapely.has_z(new_geoms).any()
  259. @pytest.mark.parametrize("include_z", [True, False])
  260. @pytest.mark.parametrize(
  261. "geoms",
  262. [[], [empty], [None, point, None], [nested_3], [point, point_z], [line_string_z]],
  263. )
  264. @pytest.mark.parametrize("interleaved", [True, False])
  265. def test_transform(geoms, include_z, interleaved):
  266. geoms = np.array(geoms, np.object_)
  267. coordinates_before = get_coordinates(geoms, include_z=include_z)
  268. if interleaved:
  269. transformation = lambda coords: coords + 1
  270. elif not include_z:
  271. transformation = lambda x, y: (x + 1, y + 1)
  272. else:
  273. transformation = lambda x, y, z: (x + 1, y + 1, z + 1)
  274. new_geoms = transform(
  275. geoms, transformation, include_z=include_z, interleaved=interleaved
  276. )
  277. assert new_geoms is not geoms
  278. coordinates_after = get_coordinates(new_geoms, include_z=include_z)
  279. assert_allclose(coordinates_before + 1, coordinates_after, equal_nan=True)
  280. def test_transform_0dim():
  281. # a geometry input returns a geometry
  282. actual = transform(point, lambda x: x + 1)
  283. assert isinstance(actual, shapely.Geometry)
  284. # a 0-dim array input returns a 0-dim array
  285. actual = transform(np.asarray(point), lambda x: x + 1)
  286. assert isinstance(actual, np.ndarray)
  287. assert actual.ndim == 0
  288. def test_transform_no_geoms():
  289. # a geometry input returns a geometry
  290. actual = transform([], lambda x: x + 1)
  291. assert actual.shape == (0,)
  292. def test_transform_check_shape():
  293. def remove_coord(arr):
  294. return arr[:-1]
  295. with pytest.raises(ValueError):
  296. transform(linear_ring, remove_coord)
  297. def test_transform_correct_coordinate_dimension():
  298. # ensure that new geometry is 2D with include_z=False
  299. geom = line_string_z
  300. assert shapely.get_coordinate_dimension(geom) == 3
  301. new_geom = transform(geom, lambda x: x + 1, include_z=False)
  302. assert shapely.get_coordinate_dimension(new_geom) == 2
  303. @pytest.mark.parametrize(
  304. "geom",
  305. [
  306. empty_point_z,
  307. empty_line_string_z,
  308. ],
  309. )
  310. def test_transform_empty_preserve_z(geom):
  311. assert shapely.get_coordinate_dimension(geom) == 3
  312. new_geom = transform(geom, lambda x: x + 1, include_z=True)
  313. assert shapely.get_coordinate_dimension(new_geom) == 3
  314. @pytest.mark.parametrize(
  315. "geom",
  316. [
  317. empty_point_z,
  318. empty_line_string_z,
  319. ],
  320. )
  321. def test_transform_remove_z(geom):
  322. assert shapely.get_coordinate_dimension(geom) == 3
  323. new_geom = transform(geom, lambda x: x + 1, include_z=False)
  324. assert shapely.get_coordinate_dimension(new_geom) == 2
  325. @pytest.mark.parametrize(
  326. "geom,expected",
  327. [
  328. (line_string, 2),
  329. (line_string_z, 3),
  330. ],
  331. )
  332. def test_transform_auto_coordinate_dimension(geom, expected):
  333. new_geom = transform(geom, lambda x: x + 1, include_z=None)
  334. assert (shapely.get_coordinate_dimension(new_geom) == expected).all()
  335. def test_transform_auto_coordinate_dimension_mixed():
  336. new_geom = transform([line_string, line_string_z], lambda x: x + 1, include_z=None)
  337. assert_equal(shapely.get_coordinate_dimension(new_geom), [2, 3])
  338. assert_equal(
  339. shapely.get_coordinates(line_string, include_z=False) + 1,
  340. shapely.get_coordinates(new_geom[0], include_z=False),
  341. )
  342. assert_equal(
  343. shapely.get_coordinates(line_string_z, include_z=True) + 1,
  344. shapely.get_coordinates(new_geom[1], include_z=True),
  345. )
  346. def transform_non_interleaved(x, y, z=None):
  347. if z is None:
  348. return [x + 1, y + 2]
  349. else:
  350. return [x + 1, y + 2, z + 3]
  351. def test_transform_auto_coordinate_dimension_mixed_interleaved():
  352. new_geom = transform(
  353. [line_string, line_string_z],
  354. transform_non_interleaved,
  355. include_z=None,
  356. interleaved=False,
  357. )
  358. assert_equal(shapely.get_coordinate_dimension(new_geom), [2, 3])
  359. assert_equal(
  360. shapely.get_coordinates(line_string, include_z=False) + [1, 2],
  361. shapely.get_coordinates(new_geom[0], include_z=False),
  362. )
  363. assert_equal(
  364. shapely.get_coordinates(line_string_z, include_z=True) + [1, 2, 3],
  365. shapely.get_coordinates(new_geom[1], include_z=True),
  366. )
  367. def test_transform_deprecate_positional():
  368. with pytest.deprecated_call(
  369. match="positional argument `include_z` for `transform` is deprecated"
  370. ):
  371. transform(line_string_z, lambda x: x + 1, False)