QGIS/python/plugins/fTools/tools/doMergeShapes.py
2015-08-22 14:29:41 +02:00

359 lines
14 KiB
Python

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