_coverage.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import numpy as np
  2. from shapely import Geometry, GeometryType, lib
  3. from shapely._geometry import get_parts
  4. from shapely.decorators import multithreading_enabled, requires_geos
  5. __all__ = ["coverage_invalid_edges", "coverage_is_valid", "coverage_simplify"]
  6. @requires_geos("3.12.0")
  7. @multithreading_enabled
  8. def coverage_is_valid(geometry, gap_width=0.0, **kwargs):
  9. """Verify if a coverage is valid.
  10. The coverage is represented by an array of polygonal geometries with
  11. exactly matching edges and no overlap.
  12. A valid coverage may contain holes (regions of no coverage). However,
  13. sometimes it might be desirable to detect narrow gaps as invalidities in
  14. the coverage. The `gap_width` parameter allows to specify the maximum
  15. width of gaps to detect. When gaps are detected, this function will
  16. return False and the `coverage_invalid_edges` function can be used to
  17. find the edges of those gaps.
  18. Geometries that are not Polygon or MultiPolygon are ignored.
  19. .. versionadded:: 2.1.0
  20. Parameters
  21. ----------
  22. geometry : array_like
  23. Array of geometries to verify.
  24. gap_width : float, default 0.0
  25. The maximum width of gaps to detect.
  26. **kwargs
  27. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  28. Returns
  29. -------
  30. bool
  31. See Also
  32. --------
  33. coverage_invalid_edges, coverage_simplify
  34. """
  35. geometries = np.asarray(geometry)
  36. # we always consider the full array as a single coverage -> ravel the input
  37. # to pass a 1D array
  38. return lib.coverage_is_valid(geometries.ravel(order="K"), gap_width, **kwargs)
  39. @requires_geos("3.12.0")
  40. @multithreading_enabled
  41. def coverage_invalid_edges(geometry, gap_width=0.0, **kwargs):
  42. """Verify if a coverage is valid and return invalid edges.
  43. This functions returns linear indicators showing the location of invalid
  44. edges (if any) in each polygon in the input array.
  45. The coverage is represented by an array of polygonal geometries with
  46. exactly matching edges and no overlap.
  47. A valid coverage may contain holes (regions of no coverage). However,
  48. sometimes it might be desirable to detect narrow gaps as invalidities in
  49. the coverage. The `gap_width` parameter allows to specify the maximum
  50. width of gaps to detect. When gaps are detected, the `coverage_is_valid`
  51. function will return False and this function can be used to find the
  52. edges of those gaps.
  53. Geometries that are not Polygon or MultiPolygon are ignored.
  54. .. versionadded:: 2.1.0
  55. Parameters
  56. ----------
  57. geometry : array_like
  58. Array of geometries to verify.
  59. gap_width : float, default 0.0
  60. The maximum width of gaps to detect.
  61. **kwargs
  62. See :ref:`NumPy ufunc docs <ufuncs.kwargs>` for other keyword arguments.
  63. Returns
  64. -------
  65. numpy.ndarray | shapely.Geometry
  66. See Also
  67. --------
  68. coverage_is_valid, coverage_simplify
  69. """
  70. geometries = np.asarray(geometry)
  71. # we always consider the full array as a single coverage -> ravel the input
  72. # to pass a 1D array
  73. return lib.coverage_invalid_edges(geometries.ravel(order="K"), gap_width, **kwargs)
  74. @requires_geos("3.12.0")
  75. @multithreading_enabled
  76. def coverage_simplify(geometry, tolerance, *, simplify_boundary=True):
  77. """Return a simplified version of an input geometry using coverage simplification.
  78. Assumes that the geometry forms a polygonal coverage. Under this assumption, the
  79. function simplifies the edges using the Visvalingam-Whyatt algorithm, while
  80. preserving a valid coverage. In the most simplified case, polygons are reduced to
  81. triangles.
  82. A collection of valid polygons is considered a coverage if the polygons are:
  83. * **Non-overlapping** - polygons do not overlap (their interiors do not intersect)
  84. * **Edge-Matched** - vertices along shared edges are identical
  85. The function allows simplification of all edges including the outer boundaries of
  86. the coverage or simplification of only the inner (shared) edges.
  87. If there are other geometry types than Polygons or MultiPolygons present,
  88. the function will raise an error.
  89. If the geometry is polygonal but does not form a valid coverage due to overlaps,
  90. it will be simplified but it may result in invalid topology.
  91. .. versionadded:: 2.1.0
  92. Parameters
  93. ----------
  94. geometry : Geometry or array_like
  95. tolerance : float or array_like
  96. The degree of simplification roughly equal to the square root of the area
  97. of triangles that will be removed.
  98. simplify_boundary : bool, optional
  99. By default (True), simplifies both internal edges of the coverage as well
  100. as its boundary. If set to False, only simplifies internal edges.
  101. Returns
  102. -------
  103. numpy.ndarray | shapely.Geometry
  104. See Also
  105. --------
  106. coverage_is_valid, coverage_invalid_edges
  107. Examples
  108. --------
  109. >>> import shapely
  110. >>> from shapely import Polygon
  111. >>> poly = Polygon([(0, 0), (20, 0), (20, 10), (10, 5), (0, 10), (0, 0)])
  112. >>> shapely.coverage_simplify(poly, tolerance=2)
  113. <POLYGON ((0 0, 20 0, 20 10, 10 5, 0 10, 0 0))>
  114. """
  115. scalar = False
  116. if isinstance(geometry, Geometry):
  117. scalar = True
  118. geometries = np.asarray(geometry)
  119. shape = geometries.shape
  120. geometries = geometries.ravel()
  121. # create_collection acts on the inner axis
  122. collections = lib.create_collection(
  123. geometries, np.intc(GeometryType.GEOMETRYCOLLECTION)
  124. )
  125. simplified = lib.coverage_simplify(collections, tolerance, simplify_boundary)
  126. parts = get_parts(simplified).reshape(shape)
  127. if scalar:
  128. return parts.item()
  129. return parts