test_coverage.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import numpy as np
  2. import pytest
  3. import shapely
  4. from shapely import (
  5. Geometry,
  6. LineString,
  7. MultiPolygon,
  8. Polygon,
  9. )
  10. from shapely.errors import UnsupportedGEOSVersionError
  11. from shapely.testing import assert_geometries_equal
  12. from shapely.tests.common import (
  13. all_types,
  14. all_types_z,
  15. empty_line_string,
  16. )
  17. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="requires >= 3.12")
  18. @pytest.mark.parametrize("geometry", all_types + all_types_z)
  19. def test_coverage_is_valid(geometry):
  20. actual = shapely.coverage_is_valid([geometry])
  21. assert actual.ndim == 0
  22. assert actual.dtype == np.bool_
  23. assert actual.item() is True
  24. actual = shapely.coverage_invalid_edges([geometry])
  25. expected = np.array([empty_line_string], dtype=object)
  26. assert_geometries_equal(actual, expected)
  27. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="requires >= 3.12")
  28. def test_coverage_is_valid_non_polygonal():
  29. # non-polygonal geometries are ignored to validate the coverage
  30. # (e.g. even if you have crossing linestrings)
  31. geoms = [
  32. LineString([(0, 0), (1, 1)]),
  33. LineString([(1, 0), (0, 1)]),
  34. Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]),
  35. ]
  36. assert shapely.coverage_is_valid(geoms)
  37. assert (shapely.coverage_invalid_edges(geoms) == empty_line_string).all()
  38. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="requires >= 3.12")
  39. def test_coverage_is_valid_polygonal():
  40. # adjacent triangles
  41. poly1 = Polygon([(0, 0), (1, 1), (1, 0), (0, 0)])
  42. poly2 = Polygon([(0, 0), (1, 1), (0, 1), (0, 0)])
  43. assert shapely.coverage_is_valid([poly1, poly2])
  44. assert shapely.is_empty(shapely.coverage_invalid_edges([poly1, poly2])).all()
  45. # shared egde but without identical vertices
  46. poly2b = Polygon([(0, 0), (0.5, 0.5), (1, 1), (0, 1), (0, 0)])
  47. assert not shapely.coverage_is_valid([poly1, poly2b])
  48. result = shapely.coverage_invalid_edges([poly1, poly2b])
  49. expected = [LineString([(0, 0), (1, 1)]), LineString([(0, 0), (0.5, 0.5), (1, 1)])]
  50. assert_geometries_equal(result, expected)
  51. # overlap
  52. poly3 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
  53. assert not shapely.coverage_is_valid([poly1, poly3])
  54. result = shapely.coverage_invalid_edges([poly1, poly3])
  55. assert not shapely.is_empty(result).any()
  56. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="requires >= 3.12")
  57. def test_coverage_is_valid_gap_width():
  58. # shared edge of boxes with multiple vertices
  59. poly1 = shapely.from_wkt("POLYGON ((0 10, 10 10, 10 7, 10 3, 10 0, 0 0, 0 10))")
  60. poly2 = shapely.from_wkt("POLYGON ((10 10, 20 10, 20 0, 10 0, 10 3, 10 7, 10 10))")
  61. # extra vertex in middle of shared edge
  62. poly2_extra = shapely.from_wkt(
  63. "POLYGON ((10 10, 20 10, 20 0, 10 0, 10 3, 10 5, 10 7, 10 10))"
  64. )
  65. # extra vertex shifted -> gap of max 1 wide
  66. poly2_shift = shapely.from_wkt(
  67. "POLYGON ((10 10, 20 10, 20 0, 10 0, 10 3, 11 5, 10 7, 10 10))"
  68. )
  69. # valid coverage -> gap_width value does not matter
  70. assert shapely.coverage_is_valid([poly1, poly2], gap_width=0.0)
  71. assert shapely.coverage_is_valid([poly1, poly2], gap_width=2.0)
  72. result = shapely.coverage_invalid_edges([poly1, poly2], gap_width=0.0)
  73. assert_geometries_equal(result, [empty_line_string] * 2)
  74. result = shapely.coverage_invalid_edges([poly1, poly2], gap_width=2.0)
  75. assert_geometries_equal(result, [empty_line_string] * 2)
  76. # invalid coverage -> gap_width value does not matter
  77. assert not shapely.coverage_is_valid([poly1, poly2_extra], gap_width=0.0)
  78. assert not shapely.coverage_is_valid([poly1, poly2_extra], gap_width=2.0)
  79. expected = shapely.from_wkt(
  80. ["LINESTRING (10 7, 10 3)", "LINESTRING (10 3, 10 5, 10 7)"]
  81. )
  82. result = shapely.coverage_invalid_edges([poly1, poly2_extra], gap_width=0.0)
  83. assert_geometries_equal(result, expected)
  84. result = shapely.coverage_invalid_edges([poly1, poly2_extra], gap_width=2.0)
  85. assert_geometries_equal(result, expected)
  86. # coverage with gap of 1 unit wide
  87. assert shapely.coverage_is_valid([poly1, poly2_shift], gap_width=0.0)
  88. assert shapely.coverage_is_valid([poly1, poly2_shift], gap_width=0.5)
  89. assert not shapely.coverage_is_valid([poly1, poly2_shift], gap_width=1.0)
  90. assert not shapely.coverage_is_valid([poly1, poly2_shift], gap_width=1.5)
  91. # TODO why this behaviour?
  92. assert shapely.coverage_is_valid([poly1, poly2_shift], gap_width=2.0)
  93. assert_geometries_equal(
  94. shapely.coverage_invalid_edges([poly1, poly2_shift], gap_width=0.0),
  95. [empty_line_string] * 2,
  96. )
  97. assert_geometries_equal(
  98. shapely.coverage_invalid_edges([poly1, poly2_shift], gap_width=1.0),
  99. shapely.from_wkt(["LINESTRING (10 7, 10 3)", "LINESTRING (10 3, 11 5, 10 7)"]),
  100. )
  101. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="requires >= 3.12")
  102. def test_coverage_invalid_edges_gufunc():
  103. poly1 = shapely.from_wkt("POLYGON ((0 10, 10 10, 10 7, 10 3, 10 0, 0 0, 0 10))")
  104. poly2 = shapely.from_wkt("POLYGON ((10 10, 20 10, 20 0, 10 0, 10 3, 10 7, 10 10))")
  105. poly2_extra = shapely.from_wkt(
  106. "POLYGON ((10 10, 20 10, 20 0, 10 0, 10 3, 10 5, 10 7, 10 10))"
  107. )
  108. poly3 = shapely.from_wkt("POLYGON ((20 10, 30 10, 30 7, 30 3, 30 0, 20 0, 20 10))")
  109. arr = np.array([[poly1, poly2, poly3], [poly1, poly2_extra, poly3]])
  110. result = shapely.lib.coverage_invalid_edges(arr, 0.0)
  111. expected = shapely.from_wkt(
  112. [
  113. ["LINESTRING EMPTY"] * 3,
  114. [
  115. "LINESTRING (10 7, 10 3)",
  116. "LINESTRING (10 3, 10 5, 10 7)",
  117. "LINESTRING EMPTY",
  118. ],
  119. ]
  120. )
  121. assert_geometries_equal(result, expected)
  122. arr2 = np.array(arr, order="F")
  123. result = shapely.lib.coverage_invalid_edges(arr2, 0.0)
  124. assert_geometries_equal(result, expected)
  125. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  126. @pytest.mark.parametrize("geometry", all_types)
  127. def test_coverage_simplify_scalars(geometry):
  128. if geometry.geom_type in ("Polygon", "MultiPolygon"):
  129. actual = shapely.coverage_simplify(geometry, 0.0)
  130. assert isinstance(actual, Geometry)
  131. assert shapely.get_type_id(actual) == shapely.get_type_id(geometry)
  132. assert actual.equals(geometry)
  133. else:
  134. with pytest.raises(TypeError, match="incorrect geometry type"):
  135. shapely.coverage_simplify(geometry, 0.0)
  136. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  137. @pytest.mark.parametrize("geometry", all_types)
  138. def test_coverage_simplify_geom_types(geometry):
  139. if geometry.geom_type in ("Polygon", "MultiPolygon"):
  140. actual = shapely.coverage_simplify([geometry, geometry], 0.0)
  141. assert isinstance(actual, np.ndarray)
  142. assert actual.shape == (2,)
  143. assert (shapely.get_type_id(actual) == shapely.get_type_id(geometry)).all()
  144. else:
  145. with pytest.raises(TypeError, match="incorrect geometry type"):
  146. shapely.coverage_simplify([geometry, geometry], 0.0)
  147. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  148. def test_coverage_simplify_multipolygon():
  149. mp = MultiPolygon(
  150. [
  151. Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]),
  152. Polygon([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]),
  153. ]
  154. )
  155. actual = shapely.coverage_simplify(mp, 1)
  156. assert actual.equals(
  157. shapely.from_wkt(
  158. "MULTIPOLYGON (((0 1, 1 1, 1 0, 0 1)), ((2 3, 3 3, 3 2, 2 3)))"
  159. )
  160. )
  161. @pytest.mark.skipif(shapely.geos_version < (3, 12, 0), reason="GEOS < 3.12")
  162. def test_coverage_simplify_array():
  163. polygons = np.array(
  164. [
  165. shapely.Polygon([(0, 0), (20, 0), (20, 10), (10, 5), (0, 10), (0, 0)]),
  166. shapely.Polygon([(0, 10), (10, 5), (20, 10), (20, 20), (0, 20), (0, 10)]),
  167. ]
  168. )
  169. low_tolerance = shapely.coverage_simplify(polygons, 1)
  170. mid_tolerance = shapely.coverage_simplify(polygons, 8)
  171. high_tolerance = shapely.coverage_simplify(polygons, 10)
  172. assert shapely.equals(low_tolerance, shapely.normalize(polygons)).all()
  173. assert shapely.equals(
  174. mid_tolerance,
  175. shapely.from_wkt(
  176. [
  177. "POLYGON ((20 10, 0 10, 0 0, 20 0, 20 10))",
  178. "POLYGON ((20 10, 0 10, 0 20, 20 20, 20 10))",
  179. ]
  180. ),
  181. ).all()
  182. assert shapely.equals(
  183. high_tolerance,
  184. shapely.from_wkt(
  185. [
  186. "POLYGON ((20 10, 0 10, 20 0, 20 10))",
  187. "POLYGON ((20 10, 0 10, 0 20, 20 10))",
  188. ]
  189. ),
  190. ).all()
  191. no_boundary = shapely.coverage_simplify(polygons, 10, simplify_boundary=False)
  192. assert shapely.equals(
  193. no_boundary,
  194. shapely.from_wkt(
  195. [
  196. "POLYGON ((20 10, 0 10, 0 0, 20 0, 20 10))",
  197. "POLYGON ((20 10, 0 10, 0 20, 20 20, 20 10))",
  198. ]
  199. ),
  200. ).all()
  201. @pytest.mark.skipif(shapely.geos_version >= (3, 12, 0), reason="requires >= 3.12")
  202. def test_coverage_unsupported_geos():
  203. geoms = [
  204. Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]),
  205. Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]),
  206. ]
  207. with pytest.raises(UnsupportedGEOSVersionError):
  208. shapely.coverage_is_valid(geoms)
  209. with pytest.raises(UnsupportedGEOSVersionError):
  210. shapely.coverage_invalid_edges(geoms)
  211. with pytest.raises(UnsupportedGEOSVersionError):
  212. shapely.coverage_simplify(geoms, 1.0)