# -*- coding: utf-8 -*- #----------------------------------------------------------- # # Join Attributes # # A QGIS plugin for performing an attribute join between vector layers. # Attributes from one vector layer are appended to the attribute table of # another layer through a field common to both vector layers. # # Copyright (C) 2008 Carson Farmer # # EMAIL: carson.farmer (at) gmail.com # WEB : www.geog.uvic.ca/spar/carson # #----------------------------------------------------------- # # 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 * from PyQt4.QtGui import * from qgis.core import * import struct, itertools, datetime, decimal, ftools_utils from ui_frmJoinAttributes import Ui_Dialog class Dialog(QDialog, Ui_Dialog): def __init__(self, iface): QDialog.__init__(self) self.iface = iface # Set up the user interface from Designer. self.setupUi(self) QObject.connect(self.toolOut, SIGNAL("clicked()"), self.outFile) QObject.connect(self.inShape, SIGNAL("currentIndexChanged(QString)"), self.iupdate) QObject.connect(self.joinShape, SIGNAL("currentIndexChanged(QString)"), self.jupdate) QObject.connect(self.toolTable, SIGNAL("clicked()"), self.inFile) QObject.connect(self.rdoTable, SIGNAL("clicked()"), self.updateTableFields) QObject.connect(self.rdoVector, SIGNAL("clicked()"), self.jupdate) QObject.connect(self.cmbEncoding, SIGNAL("currentIndexChanged(QString)"), self.updateTableFields) self.setWindowTitle( self.tr("Join attributes") ) self.buttonOk = self.buttonBox_2.button( QDialogButtonBox.Ok ) # populate layer list self.progressBar.setValue(0) mapCanvas = self.iface.mapCanvas() layers = ftools_utils.getLayerNames([QGis.Point, QGis.Line, QGis.Polygon]) encodings = QgsVectorDataProvider.availableEncodings() self.cmbEncoding.addItems(encodings) id = encodings.indexOf(QSettings().value( "/UI/encoding", "System").toString()) if id < 0: id = len(encodings)-1 self.cmbEncoding.setCurrentIndex(id) self.inShape.addItems(layers) self.joinShape.addItems(layers) def iupdate(self, inputLayer): changedLayer = ftools_utils.getVectorLayerByName(unicode(inputLayer)) changedField = ftools_utils.getFieldList(changedLayer) self.inField.clear() for i in changedField: self.inField.addItem(unicode(changedField[i].name())) def jupdate(self): inputLayer = self.joinShape.currentText() if inputLayer != "": changedLayer = ftools_utils.getVectorLayerByName(unicode(inputLayer)) changedField = ftools_utils.getFieldList(changedLayer) self.joinField.clear() for i in changedField: self.joinField.addItem(unicode(changedField[i].name())) def accept(self): self.buttonOk.setEnabled( False ) if self.inShape.currentText() == "": QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify target vector layer")) elif self.outShape.text() == "": QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify output shapefile")) elif self.joinShape.currentText() == "" and self.rdoVector.isChecked(): QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify join vector layer")) elif self.inField.currentText() == "": QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify target join field")) elif self.joinField.currentText() == "": QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify join field")) elif self.inTable.text() == "" and self.rdoTable.isChecked(): QMessageBox.information(self, self.tr("Join Attributes"), self.tr("Please specify input table")) else: keep = self.rdoKeep.isChecked() inName = self.inShape.currentText() inField = self.inField.currentText() if self.rdoVector.isChecked(): joinName = self.joinShape.currentText() useTable = False else: joinName = self.inTable.text() useTable = True joinField = self.joinField.currentText() outPath = self.outShape.text() encoding = self.cmbEncoding.currentText() QSettings().setValue( "/UI/encoding", encoding) res = self.compute(inName, inField, joinName, joinField, outPath, keep, useTable, encoding, self.progressBar) self.outShape.clear() if res: addToTOC = QMessageBox.question(self, self.tr("Join Attributes"), self.tr("Created output shapefile:\n%1\n\nWould you like to add the new layer to the TOC?") .arg( unicode(self.shapefileName) ), QMessageBox.Yes, QMessageBox.No, QMessageBox.NoButton) if addToTOC == QMessageBox.Yes: if not ftools_utils.addShapeToCanvas( unicode( outPath ) ): QMessageBox.warning( self, self.tr("Geoprocessing"), self.tr( "Error loading output shapefile:\n%1" ) .arg( unicode( outPath ) )) self.progressBar.setValue(0) self.buttonOk.setEnabled( True ) def outFile(self): self.outShape.clear() ( self.shapefileName, self.encoding ) = ftools_utils.saveDialog( self ) if self.shapefileName is None or self.encoding is None: return self.outShape.setText( QString( self.shapefileName ) ) def inFile(self): self.outShape.clear() fileDialog = QFileDialog() fileDialog.setConfirmOverwrite(False) outName = fileDialog.getOpenFileName(self, self.tr("Join Table"), ".", "Tables (*.dbf *.csv)") fileCheck = QFile(outName) if fileCheck.exists(): filePath = QFileInfo(outName).absoluteFilePath() if not outName.isEmpty(): self.inTable.clear() self.inTable.insert(filePath) self.updateTableFields() else: QMessageBox.warning(self, self.tr("Join Attributes"), self.tr("Input table does not exist")) def updateTableFields(self): if self.inTable.text() != "": filePath = self.inTable.text() joinInfo = QFileInfo(filePath) joinPath = joinInfo.absoluteFilePath() joinName = joinInfo.completeBaseName() self.joinField.clear() changedLayer = QgsVectorLayer(joinPath, joinName, 'ogr') encoding = self.cmbEncoding.currentText() changedLayer.setProviderEncoding(encoding) try: changedField = ftools_utils.getFieldList(changedLayer) except: QMessageBox.warning(self, self.tr("Join Attributes"), self.tr("Unable to read input table!")) return for i in changedField: self.joinField.addItem(unicode(changedField[i].name())) def compute(self, inName, inField, joinName, joinField, outName, keep, useTable, encoding, progressBar): layer1 = ftools_utils.getVectorLayerByName(inName) provider1 = layer1.dataProvider() allAttrs = provider1.attributeIndexes() provider1.select(allAttrs) fieldList1 = ftools_utils.getFieldList(layer1).values() index1 = provider1.fieldNameIndex(inField) if useTable: joinInfo = QFileInfo(joinName) joinPath = joinInfo.absoluteFilePath() joinName = joinInfo.completeBaseName() layer2 = QgsVectorLayer(joinPath, joinName, 'ogr') layer2.setProviderEncoding(encoding) useTable = False else: layer2 = ftools_utils.getVectorLayerByName(joinName) provider2 = layer2.dataProvider() allAttrs = provider2.attributeIndexes() provider2.select(allAttrs, QgsRectangle(), False, False) fieldList2 = ftools_utils.getFieldList(layer2) index2 = provider2.fieldNameIndex(joinField) fieldList2 = ftools_utils.testForUniqueness(fieldList1, fieldList2.values()) seq = range(0, len(fieldList1) + len(fieldList2)) fieldList1.extend(fieldList2) fieldList1 = dict(zip(seq, fieldList1)) # check for correct field names longNames = ftools_utils.checkFieldNameLength( fieldList1 ) if not longNames.isEmpty(): QMessageBox.warning( self, self.tr( 'Incorrect field names' ), self.tr( 'No output will be created.\nFollowing field names are longer than 10 characters:\n%1' ) .arg( longNames.join( '\n' ) ) ) return False sRs = provider1.crs() progressBar.setValue(13) check = QFile(self.shapefileName) if check.exists(): if not QgsVectorFileWriter.deleteShapeFile(self.shapefileName): QMessageBox.warning( self, self.tr( 'Error deleting shapefile' ), self.tr( "Can't delete existing shapefile\n%1" ).arg( self.shapefileName ) ) return False writer = QgsVectorFileWriter(self.shapefileName, self.encoding, fieldList1, provider1.geometryType(), sRs) inFeat = QgsFeature() outFeat = QgsFeature() joinFeat = QgsFeature() inGeom = QgsGeometry() nElement = 0 nFeats = provider1.featureCount() progressBar.setRange(nElement, nFeats) count = 0 provider1.rewind() while provider1.nextFeature(inFeat): inGeom = QgsGeometry(inFeat.geometry()) atMap1 = inFeat.attributeMap() outFeat.setAttributeMap(atMap1) outFeat.setGeometry(inGeom) none = True provider2.select(allAttrs, QgsRectangle(), False, False) while provider2.nextFeature(joinFeat): atMap2 = joinFeat.attributeMap() if atMap1[index1] == atMap2[index2]: none = False atMap = atMap1.values() atMap2 = atMap2.values() atMap.extend(atMap2) atMap = dict(zip(seq, atMap)) break if none: outFeat.setAttributeMap(atMap1) else: outFeat.setAttributeMap(atMap) if keep: # keep all records writer.addFeature(outFeat) else: # keep only matching records if not none: writer.addFeature(outFeat) nElement += 1 progressBar.setValue(nElement) del writer return True def createFieldList(self, table, joinField): fieldList = {} item = 0 for i in table[0]: if unicode(i) == unicode(joinField): index2 = item info = table[1][item] name = i if info[0] == "C" or info[0] == "M" or info[0] == "D": qtype = QVariant.String ntype = "string" else: ntype = "double" qtype = QVariant.Double length = info[1] prec = info[2] field = QgsField(name, qtype, ntype, length, prec, self.tr("joined fields")) fieldList[item] = field item = item + 1 return (fieldList, index2)