EliminateSelection.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. """
  2. ***************************************************************************
  3. EliminateSelection.py
  4. ---------------------
  5. Date : January 2017
  6. Copyright : (C) 2017 by Bernhard Ströbl
  7. Email : bernhard.stroebl@jena.de
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Bernhard Ströbl'
  18. __date__ = 'January 2017'
  19. __copyright__ = '(C) 2017, Bernhard Ströbl'
  20. import os
  21. from qgis.PyQt.QtGui import QIcon
  22. from qgis.core import (QgsApplication,
  23. QgsFeatureRequest,
  24. QgsFeature,
  25. QgsFeatureSink,
  26. QgsGeometry,
  27. QgsProcessingAlgorithm,
  28. QgsProcessingException,
  29. QgsProcessingUtils,
  30. QgsProcessingParameterVectorLayer,
  31. QgsProcessingParameterEnum,
  32. QgsProcessing,
  33. QgsProcessingParameterFeatureSink)
  34. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  35. pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
  36. class EliminateSelection(QgisAlgorithm):
  37. INPUT = 'INPUT'
  38. OUTPUT = 'OUTPUT'
  39. MODE = 'MODE'
  40. MODE_LARGEST_AREA = 0
  41. MODE_SMALLEST_AREA = 1
  42. MODE_BOUNDARY = 2
  43. def icon(self):
  44. return QgsApplication.getThemeIcon("/algorithms/mAlgorithmDissolve.svg")
  45. def svgIconPath(self):
  46. return QgsApplication.iconPath("/algorithms/mAlgorithmDissolve.svg")
  47. def group(self):
  48. return self.tr('Vector geometry')
  49. def groupId(self):
  50. return 'vectorgeometry'
  51. def __init__(self):
  52. super().__init__()
  53. def flags(self):
  54. return super().flags() | QgsProcessingAlgorithm.FlagNoThreading | QgsProcessingAlgorithm.FlagNotAvailableInStandaloneTool
  55. def initAlgorithm(self, config=None):
  56. self.modes = [self.tr('Largest Area'),
  57. self.tr('Smallest Area'),
  58. self.tr('Largest Common Boundary')]
  59. self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT,
  60. self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon]))
  61. self.addParameter(QgsProcessingParameterEnum(self.MODE,
  62. self.tr('Merge selection with the neighbouring polygon with the'),
  63. options=self.modes))
  64. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Eliminated'), QgsProcessing.TypeVectorPolygon))
  65. def name(self):
  66. return 'eliminateselectedpolygons'
  67. def displayName(self):
  68. return self.tr('Eliminate selected polygons')
  69. def processAlgorithm(self, parameters, context, feedback):
  70. inLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
  71. boundary = self.parameterAsEnum(parameters, self.MODE, context) == self.MODE_BOUNDARY
  72. smallestArea = self.parameterAsEnum(parameters, self.MODE, context) == self.MODE_SMALLEST_AREA
  73. if inLayer.selectedFeatureCount() == 0:
  74. feedback.reportError(self.tr('{0}: (No selection in input layer "{1}")').format(self.displayName(), parameters[self.INPUT]))
  75. featToEliminate = []
  76. selFeatIds = inLayer.selectedFeatureIds()
  77. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  78. inLayer.fields(), inLayer.wkbType(), inLayer.sourceCrs())
  79. if sink is None:
  80. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  81. for aFeat in inLayer.getFeatures():
  82. if feedback.isCanceled():
  83. break
  84. if aFeat.id() in selFeatIds:
  85. # Keep references to the features to eliminate
  86. featToEliminate.append(aFeat)
  87. else:
  88. # write the others to output
  89. sink.addFeature(aFeat, QgsFeatureSink.FastInsert)
  90. del sink
  91. # Delete all features to eliminate in processLayer
  92. processLayer = QgsProcessingUtils.mapLayerFromString(dest_id, context)
  93. processLayer.startEditing()
  94. # ANALYZE
  95. if len(featToEliminate) > 0: # Prevent zero division
  96. start = 20.00
  97. add = 80.00 / len(featToEliminate)
  98. else:
  99. start = 100
  100. feedback.setProgress(start)
  101. madeProgress = True
  102. # We go through the list and see if we find any polygons we can
  103. # merge the selected with. If we have no success with some we
  104. # merge and then restart the whole story.
  105. while madeProgress: # Check if we made any progress
  106. madeProgress = False
  107. featNotEliminated = []
  108. # Iterate over the polygons to eliminate
  109. for i in range(len(featToEliminate)):
  110. if feedback.isCanceled():
  111. break
  112. feat = featToEliminate.pop()
  113. geom2Eliminate = feat.geometry()
  114. bbox = geom2Eliminate.boundingBox()
  115. fit = processLayer.getFeatures(
  116. QgsFeatureRequest().setFilterRect(bbox).setSubsetOfAttributes([]))
  117. mergeWithFid = None
  118. mergeWithGeom = None
  119. max = 0
  120. min = -1
  121. selFeat = QgsFeature()
  122. # use prepared geometries for faster intersection tests
  123. engine = QgsGeometry.createGeometryEngine(geom2Eliminate.constGet())
  124. engine.prepareGeometry()
  125. while fit.nextFeature(selFeat):
  126. if feedback.isCanceled():
  127. break
  128. selGeom = selFeat.geometry()
  129. if engine.intersects(selGeom.constGet()):
  130. # We have a candidate
  131. iGeom = geom2Eliminate.intersection(selGeom)
  132. if not iGeom:
  133. continue
  134. if boundary:
  135. selValue = iGeom.length()
  136. else:
  137. # area. We need a common boundary in
  138. # order to merge
  139. if 0 < iGeom.length():
  140. selValue = selGeom.area()
  141. else:
  142. selValue = -1
  143. if -1 != selValue:
  144. useThis = True
  145. if smallestArea:
  146. if -1 == min:
  147. min = selValue
  148. else:
  149. if selValue < min:
  150. min = selValue
  151. else:
  152. useThis = False
  153. else:
  154. if selValue > max:
  155. max = selValue
  156. else:
  157. useThis = False
  158. if useThis:
  159. mergeWithFid = selFeat.id()
  160. mergeWithGeom = QgsGeometry(selGeom)
  161. # End while fit
  162. if mergeWithFid is not None:
  163. # A successful candidate
  164. newGeom = mergeWithGeom.combine(geom2Eliminate)
  165. if processLayer.changeGeometry(mergeWithFid, newGeom):
  166. madeProgress = True
  167. else:
  168. raise QgsProcessingException(
  169. self.tr('Could not replace geometry of feature with id {0}').format(mergeWithFid))
  170. start = start + add
  171. feedback.setProgress(start)
  172. else:
  173. featNotEliminated.append(feat)
  174. # End for featToEliminate
  175. featToEliminate = featNotEliminated
  176. # End while
  177. if not processLayer.commitChanges():
  178. raise QgsProcessingException(self.tr('Could not commit changes'))
  179. for feature in featNotEliminated:
  180. if feedback.isCanceled():
  181. break
  182. processLayer.dataProvider().addFeature(feature, QgsFeatureSink.FastInsert)
  183. return {self.OUTPUT: dest_id}