# -*- 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 )