# -*- coding: utf-8 -*- """ *************************************************************************** doMergeShapes.py - merge multiple shapefile into one -------------------------------------- Date : 30-Mar-2010 Copyright : (C) 2010 by Alexander Bruy Email : alexander dot bruy at gmail dot com *************************************************************************** * * * 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. * * * *************************************************************************** """ from PyQt4.QtCore import QObject, SIGNAL, QSettings, QDir, QFileInfo, QFile, QThread, QMutex from PyQt4.QtGui import QDialog, QDialogButtonBox, QFileDialog, QMessageBox from qgis.core import QgsVectorFileWriter, QgsVectorLayer, QgsFields, QgsFeature, QgsGeometry import ftools_utils from ui_frmMergeShapes import Ui_Dialog class Dialog(QDialog, Ui_Dialog): def __init__(self, iface): QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface self.mergeThread = None self.inputFiles = None self.outFileName = None self.inEncoding = None self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) QObject.connect(self.btnSelectDir, SIGNAL("clicked()"), self.inputDir) QObject.connect(self.btnSelectFile, SIGNAL("clicked()"), self.outFile) QObject.connect(self.chkListMode, SIGNAL("stateChanged( int )"), self.changeMode) QObject.connect(self.leOutShape, SIGNAL("editingFinished()"), self.updateOutFile) def inputDir(self): settings = QSettings() lastDir = settings.value("/fTools/lastShapeDir", ".") inDir = QFileDialog.getExistingDirectory( self, self.tr("Select directory with shapefiles to merge"), lastDir ) if not inDir: return workDir = QDir(inDir) workDir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot) nameFilter = ["*.shp", "*.SHP"] workDir.setNameFilters(nameFilter) self.inputFiles = workDir.entryList() if len(self.inputFiles) == 0: QMessageBox.warning( self, self.tr("No shapefiles found"), self.tr("There are no shapefiles in this directory. Please select another one.")) self.inputFiles = None return settings.setValue("/fTools/lastShapeDir", inDir) self.progressFiles.setRange(0, len(self.inputFiles)) self.leInputDir.setText(inDir) def outFile(self): (self.outFileName, self.encoding) = ftools_utils.saveDialog(self) if self.outFileName is None or self.encoding is None: return self.leOutShape.setText(self.outFileName) def inputFile(self): (files, self.inEncoding) = ftools_utils.openDialog(self, dialogMode="ManyFiles") if files is None or self.inEncoding is None: self.inputFiles = None return self.inputFiles = [] for f in files: fileName = QFileInfo(f).fileName() self.inputFiles.append(fileName) self.progressFiles.setRange(0, len(self.inputFiles)) self.leInputDir.setText(";".join(files)) def changeMode(self): if self.chkListMode.isChecked(): self.label.setText(self.tr("Input files")) QObject.disconnect(self.btnSelectDir, SIGNAL("clicked()"), self.inputDir) QObject.connect(self.btnSelectDir, SIGNAL("clicked()"), self.inputFile) self.lblGeometry.setEnabled(False) self.cmbGeometry.setEnabled(False) else: self.label.setText(self.tr("Input directory")) QObject.disconnect(self.btnSelectDir, SIGNAL("clicked()"), self.inputFile) QObject.connect(self.btnSelectDir, SIGNAL("clicked()"), self.inputDir) self.lblGeometry.setEnabled(True) self.cmbGeometry.setEnabled(True) def updateOutFile(self): self.outFileName = self.leOutShape.text() settings = QSettings() self.outEncoding = settings.value("/UI/encoding") def reject(self): QDialog.reject(self) def accept(self): if self.inputFiles is None: workDir = QDir(self.leInputDir.text()) workDir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot) nameFilter = ["*.shp", "*.SHP"] workDir.setNameFilters(nameFilter) self.inputFiles = workDir.entryList() if len(self.inputFiles) == 0: QMessageBox.warning( self, self.tr("No shapefiles found"), self.tr("There are no shapefiles in this directory. Please select another one.")) self.inputFiles = None return if self.outFileName is None: QMessageBox.warning( self, self.tr("No output file"), self.tr("Please specify output file.")) return if self.chkListMode.isChecked(): files = self.leInputDir.text().split(";") baseDir = QFileInfo(files[0]).absolutePath() else: baseDir = self.leInputDir.text() # look for shapes with specified geometry type self.inputFiles = ftools_utils.getShapesByGeometryType(baseDir, self.inputFiles, self.cmbGeometry.currentIndex()) if self.inputFiles is None: QMessageBox.warning( self, self.tr("No shapefiles found"), self.tr("There are no shapefiles with the given geometry type. Please select an available geometry type.")) return self.progressFiles.setRange(0, len(self.inputFiles)) outFile = QFile(self.outFileName) if outFile.exists(): if not QgsVectorFileWriter.deleteShapeFile(self.outFileName): QMessageBox.warning(self, self.tr("Delete error"), self.tr("Can't delete file %s") % (self.outFileName)) return if self.inEncoding is None: self.inEncoding = "System" self.btnOk.setEnabled(False) self.mergeThread = ShapeMergeThread(baseDir, self.inputFiles, self.inEncoding, self.outFileName, self.encoding) QObject.connect(self.mergeThread, SIGNAL("rangeChanged( PyQt_PyObject )"), self.setFeatureProgressRange) QObject.connect(self.mergeThread, SIGNAL("checkStarted()"), self.setFeatureProgressFormat) QObject.connect(self.mergeThread, SIGNAL("checkFinished()"), self.resetFeatureProgressFormat) QObject.connect(self.mergeThread, SIGNAL("fileNameChanged( PyQt_PyObject )"), self.setShapeProgressFormat) QObject.connect(self.mergeThread, SIGNAL("featureProcessed()"), self.featureProcessed) QObject.connect(self.mergeThread, SIGNAL("shapeProcessed()"), self.shapeProcessed) QObject.connect(self.mergeThread, SIGNAL("processingFinished()"), self.processingFinished) QObject.connect(self.mergeThread, SIGNAL("processingInterrupted()"), self.processingInterrupted) self.btnClose.setText(self.tr("Cancel")) QObject.disconnect(self.buttonBox, SIGNAL("rejected()"), self.reject) QObject.connect(self.btnClose, SIGNAL("clicked()"), self.stopProcessing) self.mergeThread.start() def setFeatureProgressRange(self, maximum): self.progressFeatures.setRange(0, maximum) self.progressFeatures.setValue(0) def setFeatureProgressFormat(self): self.progressFeatures.setFormat("Checking files: %p% ") def resetFeatureProgressFormat(self): self.progressFeatures.setFormat("%p% ") def featureProcessed(self): self.progressFeatures.setValue(self.progressFeatures.value() + 1) def setShapeProgressFormat(self, fileName): self.progressFiles.setFormat("%p% " + fileName) def shapeProcessed(self): self.progressFiles.setValue(self.progressFiles.value() + 1) def processingFinished(self): self.stopProcessing() if self.chkAddToCanvas.isChecked(): if not ftools_utils.addShapeToCanvas(unicode(self.outFileName)): QMessageBox.warning(self, self.tr("Merging"), self.tr("Error loading output shapefile:\n%s") % (unicode(self.outFileName))) self.restoreGui() def processingInterrupted(self): self.restoreGui() def stopProcessing(self): if self.mergeThread is not None: self.mergeThread.stop() self.mergeThread = None def restoreGui(self): self.progressFiles.setFormat("%p%") self.progressFeatures.setRange(0, 100) self.progressFeatures.setValue(0) self.progressFiles.setValue(0) QObject.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) self.btnClose.setText(self.tr("Close")) self.btnOk.setEnabled(True) class ShapeMergeThread(QThread): def __init__(self, dir, shapes, inputEncoding, outputFileName, outputEncoding): QThread.__init__(self, QThread.currentThread()) self.baseDir = dir self.shapes = shapes self.inputEncoding = inputEncoding self.outputFileName = outputFileName self.outputEncoding = outputEncoding self.mutex = QMutex() self.stopMe = 0 def run(self): self.mutex.lock() self.stopMe = 0 self.mutex.unlock() interrupted = False # create attribute list with uniquie fields # from all selected layers mergedFields = [] self.emit(SIGNAL("rangeChanged( PyQt_PyObject )"), len(self.shapes)) self.emit(SIGNAL("checkStarted()")) shapeIndex = 0 fieldMap = {} for fileName in self.shapes: layerPath = QFileInfo(self.baseDir + "/" + fileName).absoluteFilePath() newLayer = QgsVectorLayer(layerPath, QFileInfo(layerPath).baseName(), "ogr") if not newLayer.isValid(): continue newLayer.setProviderEncoding(self.inputEncoding) vprovider = newLayer.dataProvider() fieldMap[shapeIndex] = {} fieldIndex = 0 for layerField in vprovider.fields(): fieldFound = False for mergedFieldIndex, mergedField in enumerate(mergedFields): if mergedField.name() == layerField.name() and mergedField.type() == layerField.type(): fieldFound = True fieldMap[shapeIndex][fieldIndex] = mergedFieldIndex if mergedField.length() < layerField.length(): # suit the field size to the field of this layer mergedField.setLength(layerField.length()) break if not fieldFound: fieldMap[shapeIndex][fieldIndex] = len(mergedFields) mergedFields.append(layerField) fieldIndex += 1 shapeIndex += 1 self.emit(SIGNAL("featureProcessed()")) self.emit(SIGNAL("checkFinished()")) # get information about shapefiles layerPath = QFileInfo(self.baseDir + "/" + self.shapes[0]).absoluteFilePath() newLayer = QgsVectorLayer(layerPath, QFileInfo(layerPath).baseName(), "ogr") self.crs = newLayer.crs() self.geom = newLayer.wkbType() vprovider = newLayer.dataProvider() fields = QgsFields() for f in mergedFields: fields.append(f) writer = QgsVectorFileWriter( self.outputFileName, self.outputEncoding, fields, self.geom, self.crs) shapeIndex = 0 for fileName in self.shapes: layerPath = QFileInfo(self.baseDir + "/" + fileName).absoluteFilePath() newLayer = QgsVectorLayer(layerPath, QFileInfo(layerPath).baseName(), "ogr") if not newLayer.isValid(): continue newLayer.setProviderEncoding(self.inputEncoding) vprovider = newLayer.dataProvider() nFeat = vprovider.featureCount() self.emit(SIGNAL("rangeChanged( PyQt_PyObject )"), nFeat) self.emit(SIGNAL("fileNameChanged( PyQt_PyObject )"), fileName) inFeat = QgsFeature() outFeat = QgsFeature() inGeom = QgsGeometry() fit = vprovider.getFeatures() while fit.nextFeature(inFeat): mergedAttrs = [""] * len(mergedFields) # fill available attributes with values fieldIndex = 0 for v in inFeat.attributes(): if shapeIndex in fieldMap and fieldIndex in fieldMap[shapeIndex]: mergedAttrs[fieldMap[shapeIndex][fieldIndex]] = v fieldIndex += 1 if inFeat.geometry() is not None: inGeom = QgsGeometry(inFeat.geometry()) outFeat.setGeometry(inGeom) outFeat.setAttributes(mergedAttrs) writer.addFeature(outFeat) self.emit(SIGNAL("featureProcessed()")) self.emit(SIGNAL("shapeProcessed()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break shapeIndex += 1 del writer if not interrupted: self.emit(SIGNAL("processingFinished()")) else: self.emit(SIGNAL("processingInterrupted()")) def stop(self): self.mutex.lock() self.stopMe = 1 self.mutex.unlock() QThread.wait(self)