|
18 | 18 | """
|
19 | 19 | from processing.parameters.ParameterSelection import ParameterSelection
|
20 | 20 | from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
21 |
| -from PyQt4 import QtCore |
22 | 21 |
|
23 |
| -__author__ = 'Bernhard Strobl' |
24 |
| -__date__ = 'August 2013' |
25 |
| -__copyright__ = '(C) 2013, Bernhard Strobl' |
| 22 | +__author__ = 'Bernhard Ströbl' |
| 23 | +__date__ = 'September 2013' |
| 24 | +__copyright__ = '(C) 2013, Bernhard Ströbl' |
26 | 25 | # This will get replaced with a git SHA1 when you do a git archive
|
27 | 26 | __revision__ = '$Format:%H$'
|
28 | 27 |
|
|
31 | 30 | from processing.core.GeoAlgorithm import GeoAlgorithm
|
32 | 31 | from processing.tools import dataobjects
|
33 | 32 | from processing.parameters.ParameterVector import ParameterVector
|
| 33 | +from processing.parameters.ParameterBoolean import ParameterBoolean |
| 34 | +from processing.parameters.ParameterTableField import ParameterTableField |
| 35 | +from processing.parameters.ParameterString import ParameterString |
34 | 36 | from processing.outputs.OutputVector import OutputVector
|
35 |
| - |
| 37 | +from processing.core.ProcessingLog import ProcessingLog |
36 | 38 |
|
37 | 39 | class Eliminate(GeoAlgorithm):
|
38 | 40 |
|
39 | 41 | INPUT = "INPUT"
|
40 | 42 | OUTPUT = "OUTPUT"
|
41 | 43 | MODE = "MODE"
|
42 |
| - |
| 44 | + KEEPSELECTION = "KEEPSELECTION" |
| 45 | + ATTRIBUTE = "ATTRIBUTE" |
| 46 | + COMPARISONVALUE = "COMPARISONVALUE" |
| 47 | + COMPARISON = "COMPARISON" |
| 48 | + |
43 | 49 | MODES = ["Area", "Common boundary"]
|
44 | 50 | MODE_AREA = 0
|
45 | 51 | MODE_BOUNDARY = 1
|
46 |
| - |
47 | 52 |
|
48 | 53 | def defineCharacteristics(self):
|
49 | 54 | self.name = "Eliminate sliver polygons"
|
50 | 55 | self.group = "Vector geometry tools"
|
51 |
| - self.addParameter(ParameterVector(self.INPUT, "Input layer", [ParameterVector.VECTOR_TYPE_POLYGON])) |
52 |
| - self.addParameter(ParameterSelection(self.MODE, "Merge selection with the neighbouring polygon with the largest", self.MODES)) |
| 56 | + self.addParameter(ParameterVector(self.INPUT, "Input layer", [ParameterVector.VECTOR_TYPE_POLYGON])) |
| 57 | + self.addParameter(ParameterBoolean(self.KEEPSELECTION, "Use current selection in input layer (works only if called from toolbox)", False)) |
| 58 | + self.addParameter(ParameterTableField(self.ATTRIBUTE, "Selection attribute", self.INPUT)) |
| 59 | + self.comparisons = ['==', '!=', '>', '>=', '<', '<=', 'begins with', 'contains'] |
| 60 | + self.addParameter(ParameterSelection(self.COMPARISON, "Comparison", self.comparisons, default = 0)) |
| 61 | + self.addParameter(ParameterString(self.COMPARISONVALUE, "Value", default = "0")) |
| 62 | + self.addParameter(ParameterSelection(self.MODE, "Merge selection with the neighbouring polygon with the largest", self.MODES)) |
53 | 63 | self.addOutput(OutputVector(self.OUTPUT, "Cleaned layer"))
|
54 | 64 |
|
55 | 65 | def processAlgorithm(self, progress):
|
56 |
| - inLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT)) |
57 |
| - boundary = self.getParameterValue(self.MODE) == self.MODE_BOUNDARY |
| 66 | + inLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT)) |
| 67 | + boundary = self.getParameterValue(self.MODE) == self.MODE_BOUNDARY |
| 68 | + keepSelection = self.getParameterValue(self.KEEPSELECTION) |
| 69 | + |
| 70 | + if not keepSelection: |
| 71 | + # make a selection with the values provided |
| 72 | + attribute = self.getParameterValue(self.ATTRIBUTE) |
| 73 | + comparison = self.comparisons [self.getParameterValue(self.COMPARISON)] |
| 74 | + comparisonvalue = self.getParameterValue(self.COMPARISONVALUE) |
| 75 | + |
| 76 | + selectindex = inLayer.dataProvider().fieldNameIndex(attribute) |
| 77 | + selectType = inLayer.dataProvider().fields()[selectindex].type() |
| 78 | + selectionError = False |
| 79 | + |
| 80 | + if selectType == 2: |
| 81 | + try: |
| 82 | + y = int(comparisonvalue) |
| 83 | + except ValueError: |
| 84 | + selectionError = True |
| 85 | + msg = "Cannot convert \"" + unicode(comparisonvalue) + "\" to integer" |
| 86 | + elif selectType == 6: |
| 87 | + try: |
| 88 | + y = float(comparisonvalue) |
| 89 | + except ValueError: |
| 90 | + selectionError = True |
| 91 | + msg = "Cannot convert \"" + unicode(comparisonvalue) + "\" to float" |
| 92 | + elif selectType == 10: # 10: string, boolean |
| 93 | + try: |
| 94 | + y = unicode(comparisonvalue) |
| 95 | + except ValueError: |
| 96 | + selectionError = True |
| 97 | + msg = "Cannot convert \"" + unicode(comparisonvalue) + "\" to unicode" |
| 98 | + elif selectType == 14: # date |
| 99 | + dateAndFormat = comparisonvalue.split(" ") |
| 100 | + |
| 101 | + if len(dateAndFormat) == 1: |
| 102 | + y = QLocale.system().toDate(dateAndFormat[0]) # QtCore.QDate object |
| 103 | + |
| 104 | + if y.isNull(): |
| 105 | + msg = "Cannot convert \"" + unicode(dateAndFormat) + "\" to date with system date format " + QLocale.system().dateFormat() |
| 106 | + elif len(dateAndFormat) == 2: |
| 107 | + y = QDate.fromString(dateAndFormat[0], dateAndFormat[1]) |
| 108 | + |
| 109 | + if y.isNull(): |
| 110 | + msg = "Cannot convert \"" + unicode(dateAndFormat[0]) + "\" to date with format string \"" + unicode(dateAndFormat[1] + "\". ") |
| 111 | + else: |
| 112 | + y = QDate() |
| 113 | + msg = "" |
| 114 | + |
| 115 | + if y.isNull(): # conversion was unsuccessfull |
| 116 | + selectionError = True |
| 117 | + msg += "Enter the date and the date format, e.g. \"07.26.2011\" \"MM.dd.yyyy\"." |
| 118 | + |
| 119 | + if ((comparison == 'begins with') or (comparison == 'contains')) and selectType != 10: |
| 120 | + selectionError = True |
| 121 | + msg = "\"" + comparison + "\" can only be used with string fields" |
| 122 | + |
| 123 | + selected = [] |
| 124 | + |
| 125 | + if selectionError: |
| 126 | + raise GeoAlgorithmExecutionException("Error in selection input: " + msg) |
| 127 | + else: |
| 128 | + for feature in inLayer.getFeatures(): |
| 129 | + aValue = feature.attributes()[selectindex] |
| 130 | + |
| 131 | + if aValue == None: |
| 132 | + continue |
| 133 | + |
| 134 | + if selectType == 2: |
| 135 | + x = int(aValue) |
| 136 | + elif selectType == 6: |
| 137 | + x = float(aValue) |
| 138 | + elif selectType == 10: # 10: string, boolean |
| 139 | + x = unicode(aValue) |
| 140 | + elif selectType == 14: # date |
| 141 | + x = aValue # should be date |
| 142 | + |
| 143 | + match = False |
| 144 | + |
| 145 | + if (comparison == '=='): |
| 146 | + match = (x == y) |
| 147 | + elif (comparison == '!='): |
| 148 | + match = (x != y) |
| 149 | + elif (comparison == '>'): |
| 150 | + match = (x > y) |
| 151 | + elif (comparison == '>='): |
| 152 | + match = (x >= y) |
| 153 | + elif (comparison == '<'): |
| 154 | + match = (x < y) |
| 155 | + elif (comparison == '<='): |
| 156 | + match = (x <= y) |
| 157 | + elif (comparison == 'begins with'): |
| 158 | + match = x.startswith(y) |
| 159 | + elif (comparison == 'contains'): |
| 160 | + match = (x.find(y) >= 0) |
| 161 | + |
| 162 | + if (match): |
| 163 | + selected.append(feature.id()) |
| 164 | + |
| 165 | + inLayer.setSelectedFeatures(selected) |
58 | 166 |
|
59 |
| - # keep references to the features to eliminate |
60 |
| - fidsToEliminate = inLayer.selectedFeaturesIds() |
| 167 | + if inLayer.selectedFeatureCount() == 0: |
| 168 | + ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.commandLineName() + "(No selection in input layer \"" + self.getParameterValue(self.INPUT) + "\")") |
| 169 | + #self.iface.messageBar().pushMessage("Eliminate", "No selection provided") |
61 | 170 |
|
| 171 | + # keep references to the features to eliminate |
| 172 | + featToEliminate = [] |
| 173 | + for aFeat in inLayer.selectedFeatures(): |
| 174 | + featToEliminate.append( aFeat ) |
62 | 175 |
|
63 |
| - provider = inLayer.dataProvider() |
64 |
| - output = self.getOutputFromName(self.OUTPUT) |
65 |
| - writer = output.getVectorWriter( provider.fields(), |
66 |
| - provider.geometryType(), |
67 |
| - inLayer.crs() ) |
68 |
| - |
69 |
| - #write all features to output layer |
70 |
| - iterator = inLayer.getFeatures() |
71 |
| - for feature in iterator: |
72 |
| - writer.addFeature(feature) |
73 |
| - |
74 |
| - #Open the layer to start cleaning it |
75 |
| - outFileName = output.value |
76 |
| - outLayer = QgsVectorLayer(outFileName, QtCore.QFileInfo(outFileName).completeBaseName(), "ogr") |
77 |
| - |
78 |
| - |
79 |
| - # delete features to be eliminated in outLayer |
80 |
| - outLayer.setSelectedFeatures(fidsToEliminate) |
81 |
| - outLayer.startEditing() |
82 |
| - |
83 |
| - if outLayer.deleteSelectedFeatures(): |
84 |
| - if self.saveChanges(outLayer): |
85 |
| - outLayer.startEditing() |
86 |
| - else: |
87 |
| - raise GeoAlgorithmExecutionException("Could not delete features") |
| 176 | + #delete all features to eliminate in inLayer (we won't save this) |
| 177 | + inLayer.startEditing() |
| 178 | + inLayer.deleteSelectedFeatures() |
88 | 179 |
|
89 | 180 | # ANALYZE
|
90 |
| - start = 20.00 |
91 |
| - progress.setPercentage(start) |
92 |
| - add = 80.00 / len(fidsToEliminate) |
| 181 | + if len( featToEliminate ) > 0: # prevent zero division |
| 182 | + start = 20.00 |
| 183 | + add = 80.00 / len( featToEliminate ) |
| 184 | + else: |
| 185 | + start = 100 |
93 | 186 |
|
94 |
| - lastLen = 0 |
95 |
| - geomsToMerge = dict() |
| 187 | + progress.setPercentage( start ) |
| 188 | + madeProgress = True |
96 | 189 |
|
97 | 190 | # we go through the list and see if we find any polygons we can merge the selected with
|
98 | 191 | # if we have no success with some we merge and then restart the whole story
|
99 |
| - while (lastLen != inLayer.selectedFeatureCount()): #check if we made any progress |
100 |
| - lastLen = inLayer.selectedFeatureCount() |
101 |
| - fidsToDeselect = [] |
| 192 | + while ( madeProgress ): #check if we made any progress |
| 193 | + madeProgress = False |
| 194 | + featNotEliminated = [] |
102 | 195 |
|
103 | 196 | #iterate over the polygons to eliminate
|
104 |
| - for fid2Eliminate in inLayer.selectedFeaturesIds(): |
105 |
| - feat = QgsFeature() |
106 |
| - |
107 |
| - if inLayer.getFeatures( QgsFeatureRequest().setFilterFid( fid2Eliminate ).setSubsetOfAttributes([]) ).nextFeature( feat ): |
108 |
| - geom2Eliminate = feat.geometry() |
109 |
| - bbox = geom2Eliminate.boundingBox() |
110 |
| - fit = outLayer.getFeatures( QgsFeatureRequest().setFilterRect( bbox ) ) |
111 |
| - mergeWithFid = None |
112 |
| - mergeWithGeom = None |
113 |
| - max = 0 |
114 |
| - |
115 |
| - selFeat = QgsFeature() |
116 |
| - while fit.nextFeature(selFeat): |
117 |
| - selGeom = selFeat.geometry() |
118 |
| - |
119 |
| - if geom2Eliminate.intersects(selGeom): # we have a candidate |
120 |
| - iGeom = geom2Eliminate.intersection(selGeom) |
121 |
| - |
122 |
| - if boundary: |
123 |
| - selValue = iGeom.length() |
124 |
| - else: |
125 |
| - # we need a common boundary |
126 |
| - if 0 < iGeom.length(): |
127 |
| - selValue = selGeom.area() |
128 |
| - else: |
129 |
| - selValue = 0 |
130 |
| - |
131 |
| - if selValue > max: |
132 |
| - max = selValue |
133 |
| - mergeWithFid = selFeat.id() |
134 |
| - mergeWithGeom = QgsGeometry(selGeom) # deep copy of the geometry |
135 |
| - |
136 |
| - if mergeWithFid != None: # a successful candidate |
137 |
| - newGeom = mergeWithGeom.combine(geom2Eliminate) |
138 |
| - |
139 |
| - if outLayer.changeGeometry(mergeWithFid, newGeom): |
140 |
| - # write change back to disc |
141 |
| - if self.saveChanges(outLayer): |
142 |
| - outLayer.startEditing() |
| 197 | + for i in range( len( featToEliminate )): |
| 198 | + feat = featToEliminate.pop() |
| 199 | + geom2Eliminate = feat.geometry() |
| 200 | + bbox = geom2Eliminate.boundingBox() |
| 201 | + fit = inLayer.getFeatures( QgsFeatureRequest().setFilterRect( bbox ) ) |
| 202 | + mergeWithFid = None |
| 203 | + mergeWithGeom = None |
| 204 | + max = 0 |
| 205 | + selFeat = QgsFeature() |
| 206 | + |
| 207 | + while fit.nextFeature( selFeat ): |
| 208 | + selGeom = selFeat.geometry() |
| 209 | + |
| 210 | + if geom2Eliminate.intersects( selGeom ): # we have a candidate |
| 211 | + iGeom = geom2Eliminate.intersection( selGeom ) |
| 212 | + |
| 213 | + if boundary: |
| 214 | + selValue = iGeom.length() |
| 215 | + else: # largest area |
| 216 | + # we need a common boundary in order to merge |
| 217 | + if 0 < iGeom.length(): |
| 218 | + selValue = selGeom.area() |
143 | 219 | else:
|
144 |
| - return |
145 |
| - |
146 |
| - # mark feature as eliminated in inLayer |
147 |
| - fidsToDeselect.append(fid2Eliminate) |
148 |
| - else: |
149 |
| - raise GeoAlgorithmExecutionException("Could not replace geometry of feature with id %s" % (mergeWithFid)) |
150 |
| - |
151 |
| - start = start + add |
152 |
| - progress.setPercentage(start) |
153 |
| - # end for fid2Eliminate |
154 |
| - |
155 |
| - # deselect features that are already eliminated in inLayer |
156 |
| - inLayer.deselect(fidsToDeselect) |
157 |
| - |
| 220 | + selValue = 0 |
| 221 | + |
| 222 | + if selValue > max: |
| 223 | + max = selValue |
| 224 | + mergeWithFid = selFeat.id() |
| 225 | + mergeWithGeom = QgsGeometry( selGeom ) # deep copy of the geometry |
| 226 | + # end while fit |
| 227 | + |
| 228 | + if mergeWithFid != None: # a successful candidate |
| 229 | + newGeom = mergeWithGeom.combine( geom2Eliminate ) |
| 230 | + |
| 231 | + if inLayer.changeGeometry( mergeWithFid, newGeom ): |
| 232 | + madeProgress = True |
| 233 | + else: |
| 234 | + raise GeoAlgorithmExecutionException("Could not replace geometry of feature with id %s" % (mergeWithFid)) |
| 235 | + |
| 236 | + start = start + add |
| 237 | + progress.setPercentage( start ) |
| 238 | + else: |
| 239 | + featNotEliminated.append( feat ) |
| 240 | + # end for featToEliminate |
| 241 | + featToEliminate = featNotEliminated |
158 | 242 | #end while
|
159 | 243 |
|
160 |
| - if inLayer.selectedFeatureCount() > 0: |
161 |
| - # copy all features that could not be eliminated to outLayer |
162 |
| - if outLayer.addFeatures(inLayer.selectedFeatures()): |
163 |
| - # inform user |
164 |
| - fidList = "" |
| 244 | + # create output |
| 245 | + provider = inLayer.dataProvider() |
| 246 | + output = self.getOutputFromName( self.OUTPUT ) |
| 247 | + writer = output.getVectorWriter( provider.fields(), |
| 248 | + provider.geometryType(), |
| 249 | + inLayer.crs() ) |
165 | 250 |
|
166 |
| - for fid in inLayer.selectedFeaturesIds(): |
167 |
| - if not fidList == "": |
168 |
| - fidList += ", " |
| 251 | + #write all features that are left over to output layer |
| 252 | + iterator = inLayer.getFeatures() |
| 253 | + for feature in iterator: |
| 254 | + writer.addFeature( feature ) |
169 | 255 |
|
170 |
| - fidList += str(fid) |
| 256 | + # leave inLayer untouched |
| 257 | + inLayer.rollBack() |
171 | 258 |
|
172 |
| - raise GeoAlgorithmExecutionException("Could not eliminate features with these ids:\n%s" % (fidList)) |
173 |
| - else: |
174 |
| - raise GeoAlgorithmExecutionException("Could not add features") |
175 |
| - |
176 |
| - # stop editing outLayer and commit any pending changes |
177 |
| - self.saveChanges(outLayer) |
178 |
| - |
179 |
| - def saveChanges(self, outLayer): |
180 |
| - if not outLayer.commitChanges(): |
181 |
| - msg = "" |
182 |
| - for aStrm in outLayer.commitErrors(): |
183 |
| - msg = msg + "\n" + aStrm |
184 |
| - outLayer.rollBack() |
185 |
| - raise GeoAlgorithmExecutionException("Commit error:\n%s" % (msg)) |
186 |
| - |
187 |
| - def checkParameterValuesBeforeExecuting(self): |
188 |
| - inLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT)) |
189 |
| - if inLayer.selectedFeatureCount() == 0: |
190 |
| - return "No selection in input layer" |
191 |
| - |
| 259 | + for feature in featNotEliminated: |
| 260 | + writer.addFeature( feature ) |
0 commit comments