# -*- coding: utf-8 -*- #----------------------------------------------------------- # # fTools # Copyright (C) 2008-2011 Carson Farmer # EMAIL: carson.farmer (at) gmail.com # WEB : http://www.ftools.ca/fTools.html # # A collection of data management and analysis tools for vector data # #----------------------------------------------------------- # # 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.QtCore import QObject, SIGNAL, QThread, QMutex, QFile from PyQt4.QtGui import QDialog, QDialogButtonBox, QMessageBox from qgis.core import QGis, QgsVectorFileWriter, QgsPoint, QgsGeometry, QgsFeature import ftools_utils from ui_frmSimplify import Ui_Dialog class Dialog(QDialog, Ui_Dialog): def __init__(self, iface, function): QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface self.myFunction = function self.workThread = None if self.myFunction == 2: self.setWindowTitle(self.tr("Densify geometries")) self.lblTolerance.setText(self.tr("Vertices to add")) self.spnTolerance.setDecimals(0) self.spnTolerance.setMinimum(1) self.spnTolerance.setSingleStep(1) self.spnTolerance.setValue(1.0) self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) QObject.connect(self.chkWriteShapefile, SIGNAL("stateChanged( int )"), self.updateGui) QObject.connect(self.btnSelectOutputFile, SIGNAL("clicked()"), self.selectOutputFile) self.populateLayers() def populateLayers(self): layers = ftools_utils.getLayerNames([QGis.Polygon, QGis.Line]) self.cmbInputLayer.clear() self.cmbInputLayer.addItems(layers) def updateGui(self): if self.chkWriteShapefile.isChecked(): self.edOutputFile.setEnabled(True) self.btnSelectOutputFile.setEnabled(True) self.chkAddToCanvas.setEnabled(True) else: self.edOutputFile.setEnabled(False) self.btnSelectOutputFile.setEnabled(False) self.chkAddToCanvas.setEnabled(False) self.encoding = None def selectOutputFile(self): self.edOutputFile.clear() (self.shapeFileName, self.encoding) = ftools_utils.saveDialog(self) if self.shapeFileName is None or self.encoding is None: return self.edOutputFile.setText(self.shapeFileName) def accept(self): if not self.cmbInputLayer.currentText(): QMessageBox.warning(self, self.tr("Warning"), self.tr("Please specify an input layer")) return vLayer = ftools_utils.getVectorLayerByName(self.cmbInputLayer.currentText()) self.btnOk.setEnabled(False) if self.chkWriteShapefile.isChecked(): outFileName = self.edOutputFile.text() outFile = QFile(outFileName) if outFile.exists(): if not QgsVectorFileWriter.deleteShapeFile(outFileName): QMessageBox.warning(self, self.tr("Delete error"), self.tr("Can't delete file %s") % (outFileName)) return self.workThread = GeomThread(self.myFunction, vLayer, self.chkUseSelection.isChecked(), self.spnTolerance.value(), True, outFileName, self.encoding) else: res = QMessageBox.warning(self, self.tr("Warning"), self.tr( "Currently QGIS doesn't allow simultaneous access from " "different threads to the same datasource. Make sure your layer's " "attribute tables are closed. Continue?"), QMessageBox.Yes | QMessageBox.No) if res == QMessageBox.No: return self.workThread = GeomThread(self.myFunction, vLayer, self.chkUseSelection.isChecked(), self.spnTolerance.value(), False, None, None) QObject.connect(self.workThread, SIGNAL("rangeCalculated( PyQt_PyObject )"), self.setProgressRange) QObject.connect(self.workThread, SIGNAL("featureProcessed()"), self.featureProcessed) QObject.connect(self.workThread, SIGNAL("processingFinished( PyQt_PyObject )"), self.processFinished) QObject.connect(self.workThread, SIGNAL("processingInterrupted()"), self.processInterrupted) self.btnClose.setText(self.tr("Cancel")) QObject.disconnect(self.buttonBox, SIGNAL("rejected()"), self.reject) QObject.connect(self.btnClose, SIGNAL("clicked()"), self.stopProcessing) self.workThread.start() def setProgressRange(self, maximum): self.progressBar.setRange(0, maximum) def featureProcessed(self): self.progressBar.setValue(self.progressBar.value() + 1) def processFinished(self, pointsCount): self.stopProcessing() if self.myFunction == 1: QMessageBox.information(self, self.tr("Simplify results"), self.tr("There were %d vertices in original dataset which\nwere reduced to %d vertices after simplification") % (pointsCount[0], pointsCount[1])) self.restoreGui() if self.chkAddToCanvas.isEnabled() and self.chkAddToCanvas.isChecked(): if not ftools_utils.addShapeToCanvas(unicode(self.shapeFileName)): QMessageBox.warning(self, self.tr("Error"), self.tr("Error loading output shapefile:\n%s") % (unicode(self.shapeFileName))) self.populateLayers() QMessageBox.information(self, self.tr("Finished"), self.tr("Processing completed.")) self.iface.mapCanvas().refresh() def processInterrupted(self): self.restoreGui() def stopProcessing(self): if self.workThread is not None: self.workThread.stop() self.workThread = None def restoreGui(self): self.progressBar.setValue(0) QObject.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) self.btnClose.setText(self.tr("Close")) self.btnOk.setEnabled(True) def geomVertexCount(geometry): geomType = geometry.type() if geomType == QGis.Line: if geometry.isMultipart(): pointsList = geometry.asMultiPolyline() points = sum(pointsList, []) else: points = geometry.asPolyline() return len(points) elif geomType == QGis.Polygon: if geometry.isMultipart(): polylinesList = geometry.asMultiPolygon() polylines = sum(polylinesList, []) else: polylines = geometry.asPolygon() points = [] for l in polylines: points.extend(l) return len(points) else: return None def densify(polyline, pointsNumber): output = [] if pointsNumber != 1: multiplier = 1.0 / float(pointsNumber + 1) else: multiplier = 1 for i in xrange(len(polyline) - 1): p1 = polyline[i] p2 = polyline[i + 1] output.append(p1) for j in xrange(pointsNumber): delta = multiplier * (j + 1) x = p1.x() + delta * (p2.x() - p1.x()) y = p1.y() + delta * (p2.y() - p1.y()) output.append(QgsPoint(x, y)) if j + 1 == pointsNumber: break output.append(polyline[len(polyline) - 1]) return output def densifyGeometry(geometry, pointsNumber, isPolygon): output = [] if isPolygon: if geometry.isMultipart(): polygons = geometry.asMultiPolygon() for poly in polygons: p = [] for ring in poly: p.append(densify(ring, pointsNumber)) output.append(p) return QgsGeometry.fromMultiPolygon(output) else: rings = geometry.asPolygon() for ring in rings: output.append(densify(ring, pointsNumber)) return QgsGeometry.fromPolygon(output) else: if geometry.isMultipart(): lines = geometry.asMultiPolyline() for points in lines: output.append(densify(points, pointsNumber)) return QgsGeometry.fromMultiPolyline(output) else: points = geometry.asPolyline() output = densify(points, pointsNumber) return QgsGeometry.fromPolyline(output) class GeomThread(QThread): def __init__(self, function, inputLayer, useSelection, tolerance, writeShape, shapePath, shapeEncoding): QThread.__init__(self, QThread.currentThread()) self.inputLayer = inputLayer self.useSelection = useSelection self.tolerance = tolerance self.writeShape = writeShape self.outputFileName = shapePath self.outputEncoding = shapeEncoding self.myFunction = function self.mutex = QMutex() self.stopMe = 0 def run(self): if self.myFunction == 1: self.runSimplify() else: self.runDensify() def runSimplify(self): self.mutex.lock() self.stopMe = 0 self.mutex.unlock() interrupted = False shapeFileWriter = None pointsBefore = 0 pointsAfter = 0 if self.writeShape: vProvider = self.inputLayer.dataProvider() shapeFields = vProvider.fields() crs = vProvider.crs() wkbType = self.inputLayer.wkbType() if not crs.isValid(): crs = None shapeFileWriter = QgsVectorFileWriter(self.outputFileName, self.outputEncoding, shapeFields, wkbType, crs) featureId = 0 if self.useSelection: selection = self.inputLayer.selectedFeatures() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection)) for f in selection: featGeometry = QgsGeometry(f.geometry()) attrMap = f.attributes() pointsBefore += geomVertexCount(featGeometry) newGeometry = featGeometry.simplify(self.tolerance) pointsAfter += geomVertexCount(newGeometry) feature = QgsFeature() feature.setGeometry(newGeometry) feature.setAttributes(attrMap) shapeFileWriter.addFeature(feature) featureId += 1 self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount()) f = QgsFeature() fit = vProvider.getFeatures() while fit.nextFeature(f): featGeometry = QgsGeometry(f.geometry()) attrMap = f.attributes() pointsBefore += geomVertexCount(featGeometry) newGeometry = featGeometry.simplify(self.tolerance) pointsAfter += geomVertexCount(newGeometry) feature = QgsFeature() feature.setGeometry(newGeometry) feature.setAttributes(attrMap) shapeFileWriter.addFeature(feature) featureId += 1 self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: # modify existing shapefile if not self.inputLayer.isEditable(): self.inputLayer.startEditing() self.inputLayer.beginEditCommand("Simplify line(s)") if self.useSelection: selection = self.inputLayer.selectedFeatures() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection)) for f in selection: featureId = f.id() featGeometry = QgsGeometry(f.geometry()) pointsBefore += geomVertexCount(featGeometry) newGeometry = featGeometry.simplify(self.tolerance) pointsAfter += geomVertexCount(newGeometry) self.inputLayer.changeGeometry(featureId, newGeometry) self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: vProvider = self.inputLayer.dataProvider() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount()) f = QgsFeature() fit = vProvider.getFeatures() while fit.nextFeature(f): featureId = f.id() featGeometry = QgsGeometry(f.geometry()) pointsBefore += geomVertexCount(featGeometry) newGeometry = featGeometry.simplify(self.tolerance) pointsAfter += geomVertexCount(newGeometry) self.inputLayer.changeGeometry(featureId, newGeometry) self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break # cleanup if self.inputLayer.isEditable(): self.inputLayer.endEditCommand() if shapeFileWriter is not None: del shapeFileWriter if not interrupted: self.emit(SIGNAL("processingFinished( PyQt_PyObject )"), (pointsBefore, pointsAfter)) else: self.emit(SIGNAL("processingInterrupted()")) def runDensify(self): self.mutex.lock() self.stopMe = 0 self.mutex.unlock() interrupted = False shapeFileWriter = None isPolygon = self.inputLayer.geometryType() == QGis.Polygon if self.writeShape: # prepare writer vProvider = self.inputLayer.dataProvider() shapeFields = vProvider.fields() crs = vProvider.crs() wkbType = self.inputLayer.wkbType() if not crs.isValid(): crs = None shapeFileWriter = QgsVectorFileWriter(self.outputFileName, self.outputEncoding, shapeFields, wkbType, crs) featureId = 0 if self.useSelection: selection = self.inputLayer.selectedFeatures() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection)) for f in selection: featGeometry = QgsGeometry(f.geometry()) attrMap = f.attributes() newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon) feature = QgsFeature() feature.setGeometry(newGeometry) feature.setAttributes(attrMap) shapeFileWriter.addFeature(feature) featureId += 1 self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount()) f = QgsFeature() fit = vProvider.getFeatures() while fit.nextFeature(f): featGeometry = QgsGeometry(f.geometry()) attrMap = f.attributes() newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon) feature = QgsFeature() feature.setGeometry(newGeometry) feature.setAttributes(attrMap) shapeFileWriter.addFeature(feature) featureId += 1 self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: # modify existing shapefile if not self.inputLayer.isEditable(): self.inputLayer.startEditing() self.inputLayer.beginEditCommand("Densify line(s)") if self.useSelection: selection = self.inputLayer.selectedFeatures() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection)) for f in selection: featureId = f.id() featGeometry = QgsGeometry(f.geometry()) newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon) self.inputLayer.changeGeometry(featureId, newGeometry) self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break else: vProvider = self.inputLayer.dataProvider() self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount()) f = QgsFeature() fit = vProvider.getFeatures() while fit.nextFeature(f): featureId = f.id() featGeometry = QgsGeometry(f.geometry()) newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon) self.inputLayer.changeGeometry(featureId, newGeometry) self.emit(SIGNAL("featureProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break # cleanup if self.inputLayer.isEditable(): self.inputLayer.endEditCommand() if shapeFileWriter is not None: del shapeFileWriter if not interrupted: self.emit(SIGNAL("processingFinished( PyQt_PyObject )"), None) else: self.emit(SIGNAL("processingInterrupted()")) def stop(self): self.mutex.lock() self.stopMe = 1 self.mutex.unlock() QThread.wait(self)