# -*- coding: utf-8 -*- #----------------------------------------------------------- # # Eliminate for fTools # Copyright (C) 2011 Bernhard Ströbl # EMAIL: bernhard.stroebl@jena.de # # Eliminate sliver polygons # #----------------------------------------------------------- # # licensed under the terms of GNU GPL 2 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # #--------------------------------------------------------------------- from PyQt4 import QtCore, QtGui from qgis.core import * import ftools_utils from ui_frmEliminate import Ui_Dialog class Dialog(QtGui.QDialog, Ui_Dialog): def __init__(self, iface): QtGui.QDialog.__init__(self) self.iface = iface # Set up the user interface from Designer. self.setupUi(self) QtCore.QObject.connect(self.toolOut, QtCore.SIGNAL("clicked()"), self.outFile) QtCore.QObject.connect(self.inShape, QtCore.SIGNAL("currentIndexChanged(QString)"), self.update) QtCore.QObject.connect(self.writeShapefileCheck, QtCore.SIGNAL("stateChanged(int)"), self.on_writeShapefileCheck_stateChanged) self.setWindowTitle(self.tr("Eliminate sliver polygons")) self.buttonOk = self.buttonBox_2.button(QtGui.QDialogButtonBox.Ok) # populate layer list self.progressBar.setValue(0) self.area.setChecked(True) layers = ftools_utils.getLayerNames([QGis.Polygon]) self.inShape.addItems(layers) if len(layers) > 0: self.update(layers[0]) self.on_writeShapefileCheck_stateChanged(self.writeShapefileCheck.checkState()) def update(self, inputLayer): changedLayer = ftools_utils.getVectorLayerByName(inputLayer) selFeatures = changedLayer.selectedFeatureCount() self.selected.setText( self.tr("Selected features: %1").arg(selFeatures)) def on_writeShapefileCheck_stateChanged(self, newState): doEnable = (newState == 2) self.outShape.setEnabled(doEnable) self.toolOut.setEnabled(doEnable) self.addToCanvasCheck.setEnabled(doEnable) def accept(self): self.buttonOk.setEnabled(False) if self.inShape.currentText() == "": QtGui.QMessageBox.information(self, self.tr("Eliminate"), self.tr("No input shapefile specified")) else: if self.writeShapefileCheck.isChecked(): outFileName = self.outShape.text() if outFileName == "": QtGui.MessageBox.information(self, self.tr("Eliminate"), self.tr("Please specify output shapefile")) else: outFile = QtCore.QFile(outFileName) if outFile.exists(): if not QgsVectorFileWriter.deleteShapeFile(outFileName): QtGui.QMessageBox.warning(self, self.tr("Delete error"), self.tr("Can't delete file %1").arg(outFileName)) return outFileName = unicode(outFileName) else: outFileName = None inLayer = ftools_utils.getVectorLayerByName(unicode(self.inShape.currentText())) if inLayer.selectedFeatureCount() == 0: QtGui.QMessageBox.information(self, self.tr("Eliminate"), self.tr("No selection in input layer")) else: self.progressBar.setValue(5) boundary = self.boundary.isChecked() self.eliminate(inLayer, boundary, self.progressBar, outFileName) self.progressBar.setValue(100) self.outShape.clear() self.progressBar.setValue(0) self.buttonOk.setEnabled(True) def outFile(self): self.outShape.clear() (outFileName, self.encoding) = ftools_utils.saveDialog(self) if outFileName is None or self.encoding is None: return self.outShape.setText(outFileName) def eliminate(self, inLayer, boundary, progressBar, outFileName = None): # keep references to the features to eliminate fidsToEliminate = inLayer.selectedFeaturesIds() fidsToProcess = inLayer.selectedFeaturesIds() if outFileName: # user wants a new shape file to be created as result provider = inLayer.dataProvider() error = QgsVectorFileWriter.writeAsVectorFormat(inLayer, outFileName, provider.encoding(), inLayer.crs(), "ESRI Shapefile") if error != QgsVectorFileWriter.NoError: QtGui.QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Error creating output file")) return None outLayer = QgsVectorLayer(outFileName, QtCore.QFileInfo(outFileName).completeBaseName(), "ogr") else: outLayer = inLayer outLayer.removeSelection(False) outLayer.startEditing() doCommit = True # ANALYZE start = 20.00 progressBar.setValue(start) add = 80.00 / len(fidsToEliminate) lastLen = 0 geomsToMerge = dict() # we go through the list and see if we find any polygons we can merge the selected with # if we have no success with some we merge and then restart the whole story while (lastLen != len(fidsToProcess)): #check if we made any progress lastLen = len(fidsToProcess) fidsNotEliminated = [] fidsToDelete = [] #iterate over the polygons to eliminate for fid in fidsToProcess: feat = QgsFeature() if outLayer.featureAtId(fid, feat, True, False): geom = feat.geometry() bbox = geom.boundingBox() outLayer.select(bbox, False) # make a new selection mergeWithFid = None mergeWithGeom = None max = 0 for selFid in outLayer.selectedFeaturesIds(): if fid != selFid: #check if this feature is to be eliminated, too try: found = fidsToEliminate.index(selFid) except ValueError: #selFid is not in fidsToEliminate # check wether the geometry to eliminate and the other geometry intersect selFeat = QgsFeature() if outLayer.featureAtId(selFid, selFeat, True, False): selGeom = selFeat.geometry() if geom.intersects(selGeom): # we have a candidate iGeom = geom.intersection(selGeom) if boundary: selValue = iGeom.length() else: # we need a common boundary if 0 < iGeom.length(): selValue = selGeom.area() else: selValue = 0 if selValue > max: max = selValue mergeWithFid = selFid mergeWithGeom = QgsGeometry(selGeom) # deep copy of the geometry if mergeWithFid: # a successful candidate try: geomList = geomsToMerge[mergeWithFid] except KeyError: geomList = [mergeWithGeom] geomList.append(QgsGeometry(geom)) # deep copy of the geom geomsToMerge[mergeWithFid] = geomList fidsToDelete.append(fid) start = start + add progressBar.setValue(start) else: fidsNotEliminated.append(fid) # PROCESS for aFid in geomsToMerge.iterkeys(): geomList = geomsToMerge[aFid] if len(geomList) > 1: for i in range(len(geomList)): aGeom = geomList[i] if i == 0: newGeom = aGeom else: newGeom = newGeom.combine(aGeom) # replace geometry in outLayer if not outLayer.changeGeometry(aFid, newGeom): QtGui.QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not replace geometry of feature with id %1").arg( aFid )) doCommit = False break # delete eliminated features for aFid in fidsToDelete: if not outLayer.deleteFeature(aFid): QtGui.QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not delete feature with id %1").arg( aFid )) doCommit = False break # prepare array for the next loop fidsToProcess = fidsNotEliminated # SAVE CHANGES if doCommit: if not outLayer.commitChanges(): QtGui.QMessageBox.warning(self, self.tr("Commit error"), self.tr("Commit error")) else: outLayer.rollBack() if outFileName: if self.addToCanvasCheck.isChecked(): ftools_utils.addShapeToCanvas(outFileName) else: QtGui.QMessageBox.information(self, self.tr("Eliminate"), self.tr("Created output shapefile:\n%1").arg(outFileName)) # inform user if len(fidsNotEliminated) > 0: fidList = QtCore.QString() for fid in fidsNotEliminated: if not fidList.isEmpty(): fidList.append(", ") fidList.append(str(fid)) QtGui.QMessageBox.information(self, self.tr("Eliminate"), self.tr("Could not eliminate features with these ids:\n%1").arg(fidList)) self.iface.mapCanvas().refresh()