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