2010-03-30 20:37:55 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2012-10-04 13:59:41 +03:00
|
|
|
"""
|
|
|
|
***************************************************************************
|
|
|
|
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. *
|
|
|
|
* *
|
|
|
|
***************************************************************************
|
|
|
|
"""
|
|
|
|
|
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
|
|
|
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
|
2010-03-30 20:37:55 +00:00
|
|
|
|
|
|
|
import ftools_utils
|
|
|
|
|
|
|
|
from ui_frmMergeShapes import Ui_Dialog
|
|
|
|
|
2015-08-22 14:29:41 +02:00
|
|
|
|
|
|
|
class Dialog(QDialog, Ui_Dialog):
|
|
|
|
|
|
|
|
def __init__(self, iface):
|
|
|
|
QDialog.__init__(self, iface.mainWindow())
|
|
|
|
self.setupUi(self)
|
|
|
|
self.iface = iface
|
|
|
|
|
|
|
|
self.mergeThread = None
|
2010-07-25 16:14:36 +00:00
|
|
|
self.inputFiles = None
|
2015-08-22 14:29:41 +02:00
|
|
|
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)
|