test_equality.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import numpy as np
  2. import pytest
  3. import shapely
  4. from shapely import LinearRing, LineString, MultiLineString, Point, Polygon
  5. from shapely.tests.common import all_types, all_types_z, empty_point, ignore_invalid
  6. all_non_empty_types = np.array(all_types + all_types_z)[
  7. ~shapely.is_empty(all_types + all_types_z)
  8. ]
  9. # TODO add all_types_m and all_types_zm once tranform supports M coordinates
  10. @pytest.mark.parametrize("geom", all_types + all_types_z)
  11. def test_equality(geom):
  12. assert geom == geom # noqa: PLR0124
  13. transformed = shapely.transform(geom, lambda x: x, include_z=True)
  14. if (
  15. shapely.geos_version < (3, 9, 0)
  16. and isinstance(geom, Point)
  17. and geom.is_empty
  18. and not geom.has_z
  19. ):
  20. # the transformed empty 2D point has become 3D on GEOS 3.8
  21. transformed = shapely.force_2d(geom)
  22. assert geom == transformed
  23. assert not (geom != transformed)
  24. @pytest.mark.parametrize(
  25. "left, right",
  26. # automated test cases with transformed coordinate values
  27. [(geom, shapely.transform(geom, lambda x: x + 1)) for geom in all_non_empty_types]
  28. + [
  29. # (slightly) different coordinate values
  30. (LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 2)])),
  31. (LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 1 + 1e-12)])),
  32. # different coordinate order
  33. (LineString([(0, 0), (1, 1)]), LineString([(1, 1), (0, 0)])),
  34. # different number of coordinates (but spatially equal)
  35. (LineString([(0, 0), (1, 1)]), LineString([(0, 0), (1, 1), (1, 1)])),
  36. (LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0.5, 0.5), (1, 1)])),
  37. # different order of sub-geometries
  38. (
  39. MultiLineString([[(1, 1), (2, 2)], [(2, 2), (3, 3)]]),
  40. MultiLineString([[(2, 2), (3, 3)], [(1, 1), (2, 2)]]),
  41. ),
  42. # M coordinates (don't work yet with automated cases)
  43. pytest.param(
  44. shapely.from_wkt("POINT M (0 0 0)"),
  45. shapely.from_wkt("POINT M (0 0 1)"),
  46. marks=pytest.mark.skipif(
  47. shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12"
  48. ),
  49. ),
  50. pytest.param(
  51. shapely.from_wkt("POINT ZM (0 0 0 0)"),
  52. shapely.from_wkt("POINT ZM (0 0 0 1)"),
  53. marks=pytest.mark.skipif(
  54. shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12"
  55. ),
  56. ),
  57. ],
  58. )
  59. def test_equality_false(left, right):
  60. assert left != right
  61. assert not (left == right)
  62. with ignore_invalid():
  63. cases1 = [
  64. (LineString([(0, 1), (2, np.nan)]), LineString([(0, 1), (2, np.nan)])),
  65. (
  66. LineString([(0, 1), (np.nan, np.nan)]),
  67. LineString([(0, 1), (np.nan, np.nan)]),
  68. ),
  69. (LineString([(np.nan, 1), (2, 3)]), LineString([(np.nan, 1), (2, 3)])),
  70. (LineString([(0, np.nan), (2, 3)]), LineString([(0, np.nan), (2, 3)])),
  71. (
  72. LineString([(np.nan, np.nan), (np.nan, np.nan)]),
  73. LineString([(np.nan, np.nan), (np.nan, np.nan)]),
  74. ),
  75. # NaN as explicit Z coordinate
  76. # TODO: if first z is NaN -> considered as 2D -> tested below explicitly
  77. # (
  78. # LineString([(0, 1, np.nan), (2, 3, np.nan)]),
  79. # LineString([(0, 1, np.nan), (2, 3, np.nan)]),
  80. # ),
  81. (
  82. LineString([(0, 1, 2), (2, 3, np.nan)]),
  83. LineString([(0, 1, 2), (2, 3, np.nan)]),
  84. ),
  85. # (
  86. # LineString([(0, 1, np.nan), (2, 3, 4)]),
  87. # LineString([(0, 1, np.nan), (2, 3, 4)]),
  88. # ),
  89. ]
  90. @pytest.mark.parametrize("left, right", cases1)
  91. def test_equality_with_nan(left, right):
  92. assert left == right
  93. assert not (left != right)
  94. with ignore_invalid():
  95. cases2 = [
  96. (
  97. LineString([(0, 1, np.nan), (2, 3, np.nan)]),
  98. LineString([(0, 1, np.nan), (2, 3, np.nan)]),
  99. ),
  100. (
  101. LineString([(0, 1, np.nan), (2, 3, 4)]),
  102. LineString([(0, 1, np.nan), (2, 3, 4)]),
  103. ),
  104. ]
  105. @pytest.mark.parametrize("left, right", cases2)
  106. def test_equality_with_nan_z(left, right):
  107. assert left == right
  108. assert not (left != right)
  109. with ignore_invalid():
  110. cases3 = [
  111. (LineString([(0, np.nan), (2, 3)]), LineString([(0, 1), (2, 3)])),
  112. (LineString([(0, 1), (2, np.nan)]), LineString([(0, 1), (2, 3)])),
  113. (LineString([(0, 1, np.nan), (2, 3, 4)]), LineString([(0, 1, 2), (2, 3, 4)])),
  114. (LineString([(0, 1, 2), (2, 3, np.nan)]), LineString([(0, 1, 2), (2, 3, 4)])),
  115. pytest.param(
  116. shapely.from_wkt("POINT M (0 0 0)"),
  117. shapely.from_wkt("POINT M (0 0 NaN)"),
  118. marks=pytest.mark.skipif(
  119. shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12"
  120. ),
  121. ),
  122. pytest.param(
  123. shapely.from_wkt("POINT ZM (0 0 0 0)"),
  124. shapely.from_wkt("POINT ZM (0 0 0 NaN)"),
  125. marks=pytest.mark.skipif(
  126. shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12"
  127. ),
  128. ),
  129. ]
  130. @pytest.mark.parametrize("left, right", cases3)
  131. def test_equality_with_nan_false(left, right):
  132. assert left != right
  133. def test_equality_with_nan_z_false():
  134. with ignore_invalid():
  135. left = LineString([(0, 1, np.nan), (2, 3, np.nan)])
  136. right = LineString([(0, 1, np.nan), (2, 3, 4)])
  137. if shapely.geos_version < (3, 10, 0):
  138. # GEOS <= 3.9 fill the NaN with 0, so the z dimension is different
  139. assert left != right
  140. elif shapely.geos_version < (3, 12, 0):
  141. # GEOS 3.10-3.11 ignore NaN for Z also when explicitly created with 3D
  142. # and so the geometries are considered as 2D (and thus z dimension is ignored)
  143. assert left == right
  144. else:
  145. assert left != right
  146. def test_equality_z():
  147. # different dimensionality
  148. geom1 = Point(0, 1)
  149. geom2 = Point(0, 1, 0)
  150. assert geom1 != geom2
  151. # different dimensionality with NaN z
  152. geom2 = Point(0, 1, np.nan)
  153. if shapely.geos_version < (3, 12, 0):
  154. # GEOS 3.10-3.11 ignore NaN for Z also when explicitly created with 3D
  155. # and so the geometries are considered as 2D (and thus z dimension is ignored)
  156. assert geom1 == geom2
  157. else:
  158. assert geom1 != geom2
  159. def test_equality_exact_type():
  160. # geometries with different type but same coord seq are not equal
  161. geom1 = LineString([(0, 0), (1, 1), (0, 1), (0, 0)])
  162. geom2 = LinearRing([(0, 0), (1, 1), (0, 1), (0, 0)])
  163. geom3 = Polygon([(0, 0), (1, 1), (0, 1), (0, 0)])
  164. assert geom1 != geom2
  165. assert geom1 != geom3
  166. assert geom2 != geom3
  167. # empty with different type
  168. geom1 = shapely.from_wkt("POINT EMPTY")
  169. geom2 = shapely.from_wkt("LINESTRING EMPTY")
  170. assert geom1 != geom2
  171. def test_equality_polygon():
  172. # different exterior rings
  173. geom1 = shapely.from_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")
  174. geom2 = shapely.from_wkt("POLYGON ((0 0, 10 0, 10 10, 0 15, 0 0))")
  175. assert geom1 != geom2
  176. # different number of holes
  177. geom1 = shapely.from_wkt(
  178. "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1))"
  179. )
  180. geom2 = shapely.from_wkt(
  181. "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1), "
  182. "(3 3, 4 3, 4 4, 3 3))"
  183. )
  184. assert geom1 != geom2
  185. # different order of holes
  186. geom1 = shapely.from_wkt(
  187. "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (3 3, 4 3, 4 4, 3 3), "
  188. "(1 1, 2 1, 2 2, 1 1))"
  189. )
  190. geom2 = shapely.from_wkt(
  191. "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1), "
  192. "(3 3, 4 3, 4 4, 3 3))"
  193. )
  194. assert geom1 != geom2
  195. @pytest.mark.parametrize("geom", all_types)
  196. def test_comparison_notimplemented(geom):
  197. # comparing to a non-geometry class should return NotImplemented in __eq__
  198. # to ensure proper delegation to other (eg to ensure comparison of scalar
  199. # with array works)
  200. # https://github.com/shapely/shapely/issues/1056
  201. assert geom.__eq__(1) is NotImplemented
  202. # with array
  203. arr = np.array([geom, geom], dtype=object)
  204. result = arr == geom
  205. assert isinstance(result, np.ndarray)
  206. assert result.all()
  207. result = geom == arr
  208. assert isinstance(result, np.ndarray)
  209. assert result.all()
  210. result = arr != geom
  211. assert isinstance(result, np.ndarray)
  212. assert not result.any()
  213. result = geom != arr
  214. assert isinstance(result, np.ndarray)
  215. assert not result.any()
  216. def test_comparison_not_supported():
  217. geom1 = Point(1, 1)
  218. geom2 = Point(2, 2)
  219. with pytest.raises(TypeError, match="not supported between instances"):
  220. assert geom1 > geom2
  221. with pytest.raises(TypeError, match="not supported between instances"):
  222. assert geom1 < geom2
  223. with pytest.raises(TypeError, match="not supported between instances"):
  224. assert geom1 >= geom2
  225. with pytest.raises(TypeError, match="not supported between instances"):
  226. assert geom1 <= geom2
  227. @pytest.mark.parametrize(
  228. "geom", all_types + (shapely.points(np.nan, np.nan), empty_point)
  229. )
  230. def test_hash_same_equal(geom):
  231. hash1 = hash(geom)
  232. hash2 = hash(shapely.transform(geom, lambda x: x))
  233. assert hash1 == hash2, geom
  234. @pytest.mark.parametrize("geom", all_non_empty_types)
  235. def test_hash_same_not_equal(geom):
  236. assert hash(geom) != hash(shapely.transform(geom, lambda x: x + 1))