def loadFrameLayer(self): """ Loads the frame layer case it is not loaded yet. """ loader = LayerLoaderFactory().makeLoader(self.iface, self.widget.abstractDb) if loader.provider == 'postgres': layerMeta = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOLYGON', 'lyrName': 'moldura_a', 'tableName': 'aux_moldura_a', 'tableSchema': 'public', 'tableType': 'BASE TABLE' } elif loader.provider == 'spatialite': layerMeta = { 'cat': 'aux', 'geom': 'GEOMETRY', 'geomType': 'MULTIPOLYGON', 'lyrName': 'moldura_a', 'tableName': 'aux_moldura_a', 'tableSchema': 'public', 'tableType': 'BASE TABLE' } else: layerMeta = None layerDict = loader.load([layerMeta], uniqueLoad=True) if layerMeta['lyrName'] in layerDict.keys(): layer = layerDict[layerMeta['lyrName']] else: layer = None return layer
def __init__(self, iface, parent=None): """Constructor.""" super(self.__class__, self).__init__(parent) self.iface = iface self.utils = Utils() self.setupUi(self) self.layerFactory = LayerLoaderFactory() self.customServerConnectionWidget.postgisCustomSelector.setTitle( self.tr('Select Databases')) self.customServerConnectionWidget.spatialiteCustomSelector.setTitle( self.tr('Selected Spatialites')) self.layersCustomSelector.setTitle( self.tr('Select layers to be loaded')) self.customServerConnectionWidget.dbDictChanged.connect( self.updateLayersFromDbs) self.customServerConnectionWidget.resetAll.connect(self.resetInterface) self.customServerConnectionWidget.styleChanged.connect( self.populateStyleCombo) self.headerList = [ self.tr('Category'), self.tr('Layer Name'), self.tr('Geometry\nColumn'), self.tr('Geometry\nType'), self.tr('Layer\nType') ] self.layersCustomSelector.setHeaders(self.headerList) self.customServerConnectionWidget.postgisEdgvComboFilter.currentIndexChanged.connect( self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.spatialiteEdgvComboFilter.currentIndexChanged.connect( self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.serverConnectionTab.currentChanged.connect( self.layersCustomSelector.setInitialState) self.lyrDict = dict()
def loadFlagLyr(self, layer): """ Loads the flag layer. It checks if the flag layer is already loaded, case not, it loads the flag layer into the TOC layer: layer name """ self.layerLoader = LayerLoaderFactory().makeLoader( self.iface, self.configWindow.widget.abstractDb) return self.layerLoader.load([layer], uniqueLoad=True)[layer['lyrName']]
def defineFactory(self, abstractDb): """ Defines the layer loader by its type :param abstractDb: :return: """ self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, abstractDb) try: self.populateConfigFromDb() except Exception as e: QgsMessageLog.logMessage(self.tr('Error getting stored configuration.\n')+':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL)
def __init__(self, postgisDb, iface, instantiating=False): """ Constructor """ super(ValidationProcess, self).__init__() self.abstractDb = postgisDb if self.getStatus() == None: self.setStatus(self.tr('Instantianting process'), 0) self.classesToBeDisplayedAfterProcess = [] self.parameters = None self.iface = iface self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, self.abstractDb) self.processAlias = self.tr('Validation Process') self.instantiating = instantiating
def defineFactory(self, abstractDb): """ Defines the layer loader by its type :param abstractDb: :return: """ self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, abstractDb) try: self.populateConfigFromDb() except Exception as e: QgsMessageLog.logMessage(self.tr('Error getting stored configuration.\n')+':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL)
def __init__(self, iface, parent=None): """ Constructor """ super(self.__class__, self).__init__(parent) self.setupUi(self) self.iface = iface self.layerFactory = LayerLoaderFactory() self.selectedClasses = [] self.widget.tabWidget.setTabEnabled(0,True) self.widget.tabWidget.setTabEnabled(1,False) self.widget.tabWidget.setCurrentIndex(0) self.bar = QgsMessageBar() self.setLayout(QtGui.QGridLayout(self)) self.layout().setContentsMargins(0,0,0,0) self.layout().setAlignment(QtCore.Qt.AlignTop) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) self.bar.setSizePolicy(sizePolicy) self.layout().addWidget(self.bar, 0,0,1,1) QtCore.QObject.connect(self.widget, QtCore.SIGNAL(("problemOccurred()")), self.pushMessage) self.widget.dbChanged.connect(self.widgetConv.setDatabase)
def __init__(self, iface, parent=None): """Constructor.""" super(self.__class__, self).__init__(parent) self.iface = iface self.utils = Utils() self.setupUi(self) self.layerFactory = LayerLoaderFactory() self.customServerConnectionWidget.postgisCustomSelector.setTitle(self.tr('Select Databases')) self.customServerConnectionWidget.spatialiteCustomSelector.setTitle(self.tr('Selected Spatialites')) self.layersCustomSelector.setTitle(self.tr('Select layers to be loaded')) self.customServerConnectionWidget.dbDictChanged.connect(self.updateLayersFromDbs) self.customServerConnectionWidget.resetAll.connect(self.resetInterface) self.customServerConnectionWidget.styleChanged.connect(self.populateStyleCombo) self.headerList = [self.tr('Category'), self.tr('Layer Name'), self.tr('Geometry\nColumn'), self.tr('Geometry\nType'), self.tr('Layer\nType')] self.layersCustomSelector.setHeaders(self.headerList) self.customServerConnectionWidget.postgisEdgvComboFilter.currentIndexChanged.connect(self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.spatialiteEdgvComboFilter.currentIndexChanged.connect(self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.serverConnectionTab.currentChanged.connect(self.layersCustomSelector.setInitialState) self.lyrDict = dict()
class ValidationProcess(QObject): def __init__(self, postgisDb, iface, instantiating=False): """ Constructor """ super(ValidationProcess, self).__init__() self.abstractDb = postgisDb if self.getStatus() == None: self.setStatus(self.tr('Instantianting process'), 0) self.classesToBeDisplayedAfterProcess = [] self.parameters = None self.iface = iface self.layerLoader = LayerLoaderFactory().makeLoader( self.iface, self.abstractDb) self.processAlias = self.tr('Validation Process') self.instantiating = instantiating self.totalTime = 0 self.startTime = 0 self.endTime = 0 self.dbUserName = None self.logMsg = None self.processName = None def getFlagLyr(self, dimension): if dimension == 0: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOINT', 'lyrName': 'flags_validacao_p', 'tableName': 'aux_flags_validacao_p', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 1: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTILINESTRING', 'lyrName': 'flags_validacao_l', 'tableName': 'aux_flags_validacao_l', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 2: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOLYGON', 'lyrName': 'flags_validacao_a', 'tableName': 'aux_flags_validacao_a', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } return self.loadLayerBeforeValidationProcess(layer) def setProcessName(self, processName): """ Identifies the process name as it is. """ self.processName = processName def setDbUserName(self, userName): """ Identifies the database username. """ self.dbUserName = userName def setParameters(self, params): """ Define the process parameteres """ self.parameters = params def execute(self): """ Abstract method. MUST be reimplemented. """ pass def shouldBeRunAgain(self): """ Defines if the method should run again later """ return False def getName(self): """ Gets the process name """ return str(self.__class__).split('.')[-1].replace('\'>', '') def getProcessGroup(self): """ Returns the process group """ return 'Ungrouped' def getClassesToBeDisplayedAfterProcess(self): """ Returns classes to be loaded to TOC after executing this process. """ return self.classesToBeDisplayedAfterProcess def addClassesToBeDisplayedList(self, className): """ Add a class into the class list that will be displayed after the process """ if className not in self.classesToBeDisplayedAfterProcess: self.classesToBeDisplayedAfterProcess.append(className) def clearClassesToBeDisplayedAfterProcess(self): """ Clears the class list to be displayed """ self.classesToBeDisplayedAfterProcess = [] def preProcess(self): """ Returns the name of the pre process that must run before, must be reimplemented in each process """ return None def postProcess(self): """ Returns the name of the post process that must run after, must be reimplemented in each process """ return None def addFlag(self, flagTupleList): """ Adds flags flagTUpleList: list of tuples to be added as flag """ try: return self.abstractDb.insertFlags(flagTupleList, self.getName()) except Exception as e: QMessageBox.critical( None, self.tr('Critical!'), self. tr('A problem occurred inserting flags! Check log for details.' )) QgsMessageLog.logMessage(str(e.args[0]), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def removeFeatureFlags(self, layer, featureId): """ Removes specific flags from process layer: Name of the layer that should be remove from the flags featureId: Feature id from layer name that must be removed """ try: return self.abstractDb.removeFeatureFlags(layer, featureId, self.getName()) except Exception as e: QMessageBox.critical( None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def getStatus(self): """ Gets the process status """ try: return self.abstractDb.getValidationStatus(self.getName()) except Exception as e: QMessageBox.critical( None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def getStatusMessage(self): """ Gets the status message text """ try: return self.abstractDb.getValidationStatusText(self.getName()) except Exception as e: QMessageBox.critical( None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def setStatus(self, msg, status): """ Sets the status message status: Status number msg: Status text message """ try: if status not in [ 0, 3 ]: # neither running nor instatiating status should be logged self.logProcess() if self.logMsg: msg += "\n" + self.logMsg elif not self.dbUserName: msg += self.tr("Database username: {}\n").format( self.abstractDb.db.userName()) self.abstractDb.setValidationProcessStatus(self.getName(), msg, status) except Exception as e: QMessageBox.critical( None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def finishedWithError(self): """ Sets the finished with error status (status number 2) Clears the classes to be displayed """ self.setStatus(self.tr('Process finished with errors.'), 2) #Failed status #drop temps try: classesWithElem = self.parameters['Classes'] for cl in classesWithElem: tempName = cl.split(':')[0] + '_temp' self.abstractDb.dropTempTable(tempName) except: pass self.clearClassesToBeDisplayedAfterProcess() def inputData(self): """ Returns current active layers """ return self.iface.mapCanvas().layers() def getTableNameFromLayer(self, lyr): """ Gets the layer name as present in the rules """ uri = lyr.dataProvider().dataSourceUri() dsUri = QgsDataSourceURI(uri) name = '.'.join([dsUri.schema(), dsUri.table()]) return name def mapInputLayer(self, inputLyr, selectedFeatures=False): """ Gets the layer features considering the edit buffer in the case the layer is already in edition mode """ #return dict featureMap = dict() #getting only selected features if selectedFeatures: for feat in inputLyr.selectedFeatures(): featureMap[feat.id()] = feat #getting all features else: for feat in inputLyr.getFeatures(): featureMap[feat.id()] = feat return featureMap def updateOriginalLayer(self, pgInputLayer, qgisOutputVector, featureList=None, featureTupleList=None): """ Updates the original layer using the grass output layer pgInputLyr: postgis input layer qgisOutputVector: qgis output layer Speed up tips: http://nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts/ """ provider = pgInputLayer.dataProvider() # getting keyColumn because we want to be generic uri = QgsDataSourceURI(pgInputLayer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() # starting edition mode pgInputLayer.startEditing() addList = [] idsToRemove = [] #making the changes and inserts for feature in pgInputLayer.getFeatures(): id = feature.id() outFeats = [] #getting the output features with the specific id if qgisOutputVector: for gf in qgisOutputVector.dataProvider().getFeatures( QgsFeatureRequest( QgsExpression("{0}={1}".format(keyColumn, id)))): outFeats.append(gf) elif featureTupleList: for gfid, gf in featureTupleList: if gfid == id and gf['classname'] == pgInputLayer.name(): outFeats.append(gf) else: for gf in [gf for gf in featureList if gf.id() == id]: outFeats.append(gf) #starting to make changes for i in range(len(outFeats)): if i == 0: #let's update this feature newGeom = outFeats[i].geometry() newGeom.convertToMultiType() feature.setGeometry(newGeom) pgInputLayer.updateFeature(feature) else: #for the rest, let's add them newFeat = QgsFeature(feature) newGeom = outFeats[i].geometry() newGeom.convertToMultiType() newFeat.setGeometry(newGeom) idx = newFeat.fieldNameIndex(keyColumn) newFeat.setAttribute(idx, provider.defaultValue(idx)) addList.append(newFeat) #in the case we don't find features in the output we should mark them to be removed if len(outFeats) == 0: idsToRemove.append(id) #pushing the changes into the edit buffer pgInputLayer.addFeatures(addList, True) #removing features from the layer. pgInputLayer.deleteFeatures(idsToRemove) def updateOriginalLayerV2(self, pgInputLayer, qgisOutputVector, featureList=None, featureTupleList=None, deleteFeatures=True): """ Updates the original layer using the grass output layer pgInputLyr: postgis input layer qgisOutputVector: qgis output layer Speed up tips: http://nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts/ 1- Make pgIdList, by querying it with flag QgsFeatureRequest.NoGeometry 2- Build output dict 3- Perform operation """ provider = pgInputLayer.dataProvider() # getting keyColumn because we want to be generic uri = QgsDataSourceURI(pgInputLayer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() # starting edition mode pgInputLayer.startEditing() pgInputLayer.beginEditCommand('Updating layer') addList = [] idsToRemove = [] inputDict = dict() #this is done to work generically with output layers that are implemented different from ours isMulti = QgsWKBTypes.isMultiType(int(pgInputLayer.wkbType())) # #making the changes and inserts #this request only takes ids to build inputDict request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) for feature in pgInputLayer.getFeatures(request): inputDict[feature.id()] = dict() inputDict[feature.id()]['featList'] = [] inputDict[feature.id()]['featWithoutGeom'] = feature inputDictKeys = inputDict.keys() if qgisOutputVector: for feat in qgisOutputVector.dataProvider().getFeatures(): if keyColumn == '': featid = feat.id() else: featid = feat[keyColumn] if featid in inputDictKeys: #verificar quando keyColumn = '' inputDict[featid]['featList'].append(feat) elif featureTupleList: for gfid, gf in featureTupleList: if gfid in inputDictKeys and gf[ 'classname'] == pgInputLayer.name(): inputDict[gfid]['featList'].append(gf) else: for feat in featureList: if keyColumn == '': featid = feat.id() else: featid = feat[keyColumn] if featid in inputDictKeys: inputDict[featid]['featList'].append(feat) #finally, do what must be done for id in inputDictKeys: outFeats = inputDict[id]['featList'] #starting to make changes for i in range(len(outFeats)): if i == 0: #let's update this feature newGeom = outFeats[i].geometry() if newGeom: if isMulti: newGeom.convertToMultiType() pgInputLayer.changeGeometry( id, newGeom) #It is faster according to the api else: if id not in idsToRemove: idsToRemove.append(id) else: #for the rest, let's add them newFeat = QgsFeature(inputDict[id]['featWithoutGeom']) newGeom = outFeats[i].geometry() if newGeom: if isMulti and newGeom: newGeom.convertToMultiType() newFeat.setGeometry(newGeom) if keyColumn != '': idx = newFeat.fieldNameIndex(keyColumn) newFeat.setAttribute(idx, provider.defaultValue(idx)) addList.append(newFeat) else: if id not in idsToRemove: idsToRemove.append(id) #in the case we don't find features in the output we should mark them to be removed if len(outFeats) == 0 and deleteFeatures: idsToRemove.append(id) #pushing the changes into the edit buffer pgInputLayer.addFeatures(addList, True) #removing features from the layer. pgInputLayer.deleteFeatures(idsToRemove) pgInputLayer.endEditCommand() def getProcessingErrors(self, layer): """ Gets processing errors layer: error layer (QgsVectorLayer) output made by grass """ recordList = [] for feature in layer.getFeatures(): recordList.append( (feature.id(), binascii.hexlify(feature.geometry().asWkb()))) return recordList def loadLayerBeforeValidationProcess(self, cl): """ Loads all layers to QGIS' TOC prior the validation process """ #creating vector layer if self.abstractDb.getDatabaseVersion() == 'Non_EDGV': isEdgv = False else: isEdgv = True if isinstance(cl, dict): lyr = self.layerLoader.load([cl], uniqueLoad=True, isEdgv=isEdgv)[cl['lyrName']] else: schema, layer_name = self.abstractDb.getTableSchema(cl) lyr = self.layerLoader.load([layer_name], uniqueLoad=True, isEdgv=isEdgv)[layer_name] return lyr def prepareExecution(self, cl, geometryColumn='geom', selectedFeatures=False): """ Prepare the process to be executed cl: table name """ # loading layer prior to execution lyr = self.loadLayerBeforeValidationProcess(cl) # getting keyColumn because we want to be generic uri = QgsDataSourceURI(lyr.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() #getting feature map including the edit buffer featureMap = self.mapInputLayer(lyr, selectedFeatures=selectedFeatures) #getting table name with schema if isinstance(cl, dict): tableSchema = cl['tableSchema'] tableName = cl['tableName'] geometryColumn = cl['geom'] fullTableName = '''{0}.{1}'''.format(cl['tableSchema'], cl['tableName']) else: fullTableName = cl tableSchema, tableName = cl.split('.') #setting temp table name processTableName = fullTableName + '_temp' # specific EPSG search parameters = { 'tableSchema': tableSchema, 'tableName': tableName, 'geometryColumn': geometryColumn } srid = self.abstractDb.findEPSG(parameters=parameters) #creating temp table self.abstractDb.createAndPopulateTempTableFromMap( fullTableName, featureMap, geometryColumn, keyColumn, srid) return processTableName, lyr, keyColumn def postProcessSteps(self, processTableName, lyr): """ Execute the final steps after the actual process """ #getting the output as a QgsVectorLayer outputLayer = QgsVectorLayer( self.abstractDb.getURI(processTableName, True).uri(), processTableName, "postgres") #updating the original layer (lyr) self.updateOriginalLayerV2(lyr, outputLayer) #dropping the temp table as we don't need it anymore self.abstractDb.dropTempTable(processTableName) def getGeometryTypeText(self, geomtype): if geomtype == QGis.WKBPoint: return 'Point' elif geomtype == QGis.WKBMultiPoint: return 'MultiPoint' elif geomtype == QGis.WKBLineString: return 'Linestring' elif geomtype == QGis.WKBMultiLineString: return 'MultiLinestring' elif geomtype == QGis.WKBPolygon: return 'Polygon' elif geomtype == QGis.WKBMultiPolygon: return 'MultiPolygon' else: raise Exception( self.tr('Operation not defined with provided geometry type!')) def createUnifiedLineLayer(self, layerList, onlySelected=False): """ For each layer in layerList, transforms it into lines if lyrType is polygon and adds features into layerList. Duplicates can happen in this process. """ srid = layerList[0].crs().authid().split(':')[-1] coverage = self.iface.addVectorLayer( "{0}?crs=epsg:{1}".format('Linestring', srid), "coverage_lines", "memory") provider = coverage.dataProvider() coverage.startEditing() coverage.beginEditCommand('Creating coverage lines layer') self.localProgress = ProgressWidget( 1, len(layerList) - 1, self.tr('Building unified layers with ') + ', '.join([i.name() for i in layerList]) + '.', parent=self.iface.mapCanvas()) addFeatureList = [] for lyr in layerList: isPolygon = True if lyr.dataProvider().geometryType() in [ QGis.WKBPolygon, QGis.WKBMultiPolygon ] else False isMulti = QgsWKBTypes.isMultiType(int(lyr.wkbType())) featureIterator = lyr.getFeatures( ) if not onlySelected else lyr.selectedFeatures() for feature in featureIterator: geom = feature.geometry() if geom: parts = geom.asGeometryCollection() if parts: for part in parts: if isPolygon: linestrings = part.asPolygon() for line in linestrings: newfeat = QgsFeature( coverage.pendingFields()) newfeat.setGeometry( QgsGeometry.fromPolyline(line)) addFeatureList.append(newfeat) else: newfeat = QgsFeature(coverage.pendingFields()) newfeat.setGeometry(part) addFeatureList.append(newfeat) self.localProgress.step() coverage.addFeatures(addFeatureList, True) coverage.endEditCommand() coverage.commitChanges() return coverage def createUnifiedLayer(self, layerList, attributeTupple=False, attributeBlackList='', onlySelected=False): """ Creates a unified layer from a list of layers """ #getting srid from something like 'EPSG:31983' srid = layerList[0].crs().authid().split(':')[ -1] #quem disse que tudo tem que ter mesmo srid? TODO: mudar isso # creating the layer geomtype = layerList[0].dataProvider().geometryType() for lyr in layerList: if lyr.dataProvider().geometryType() != geomtype: raise Exception( self.tr('Error! Different geometry primitives!')) coverage = self.iface.addVectorLayer( "{0}?crs=epsg:{1}".format(self.getGeometryTypeText(geomtype), srid), "coverage", "memory") provider = coverage.dataProvider() coverage.startEditing() coverage.beginEditCommand('Creating coverage layer') #speedup #defining fields if not attributeTupple: fields = [ QgsField('featid', QVariant.Int), QgsField('classname', QVariant.String) ] else: fields = [ QgsField('featid', QVariant.Int), QgsField('classname', QVariant.String), QgsField('tupple', QVariant.String), QgsField('blacklist', QVariant.String) ] provider.addAttributes(fields) coverage.updateFields() totalCount = 0 for layer in layerList: if onlySelected: totalCount += layer.selectedFeatureCount() else: totalCount += layer.pendingFeatureCount() self.localProgress = ProgressWidget( 1, totalCount - 1, self.tr('Building unified layers with ') + ', '.join([i.name() for i in layerList]) + '.', parent=self.iface.mapCanvas()) featlist = [] if attributeBlackList != '': bList = attributeBlackList.replace(' ', '').split(',') else: bList = [] for layer in layerList: # recording class name classname = layer.name() uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() if onlySelected: featureList = layer.selectedFeatures() else: featureList = layer.getFeatures() for feature in featureList: newfeat = QgsFeature(coverage.pendingFields()) newfeat.setGeometry(feature.geometry()) newfeat['featid'] = feature.id() newfeat['classname'] = classname if attributeTupple: attributeList = [] attributes = [ field.name() for field in feature.fields() if (field.type() != 6 and field.name() != keyColumn) ] attributes.sort() for attribute in attributes: if attribute not in bList: attributeList.append( u'{0}'.format(feature[attribute] )) #done due to encode problems tup = ','.join(attributeList) newfeat['tupple'] = tup featlist.append(newfeat) self.localProgress.step() #inserting new features into layer coverage.addFeatures(featlist, False) coverage.endEditCommand() coverage.commitChanges() return coverage def splitUnifiedLayer(self, outputLayer, layerList): """ Updates all original layers making requests with the class name """ for layer in layerList: classname = layer.name() tupplelist = [(feature['featid'], feature) for feature in outputLayer.getFeatures() if feature['classname'] == classname] self.updateOriginalLayerV2(layer, None, featureTupleList=tupplelist) def getGeometryColumnFromLayer(self, layer): uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) geomColumn = uri.geometryColumn() return geomColumn def startTimeCount(self): self.startTime = datetime.now() def endTimeCount(self, cummulative=True): self.endTime = datetime.now() elapsedTime = (self.endTime - self.startTime) if cummulative: if self.totalTime == 0: self.totalTime = elapsedTime else: self.totalTime += elapsedTime return elapsedTime def logLayerTime(self, lyr): time = self.endTimeCount() if self.startTime != 0 and self.endTime != 0: QgsMessageLog.logMessage( self.tr( 'Elapsed time for process {0} on layer {1}: {2}').format( self.processAlias, lyr, str(time)), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def logTotalTime(self): if self.startTime != 0 and self.endTime != 0 and self.totalTime != 0: QgsMessageLog.logMessage( self.tr('Elapsed time for process {0}: {1}').format( self.processAlias, str(self.totalTime)), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def jsonifyParameters(self, params): """ Sets a dict type feature to a json structure in order to make it visually better both to expose on log and to save it on validation history table. parameter params: a dict type variable returns: a json structured text """ return json.dumps(params, sort_keys=True, indent=4) def logProcess(self): """ Returns information to user: -userName (get information from abstractDb.db.userName()) -parameters (get parameters from parameter dict) *** -layersRun (the layers that were used) -flagNumber (number of flags) -elapsedTime """ # logging username logMsg = "" if self.dbUserName: logMsg += self.tr("\nDatabase username: {0}").format( self.dbUserName) else: logMsg += self.tr("\nUnable to get database username.") # logging process parameters if self.parameters: parametersString = self.tr( "\nParameters used on this execution of process {}\n").format( self.processAlias) parametersString += self.jsonifyParameters(self.parameters) logMsg += parametersString else: logMsg += self.tr( "\nUnable to get database parameters for process {}.").format( self.processAlias) # logging #Flag logMsg += self.tr("\nNumber of flags raised by the process: {}").format(\ str(self.abstractDb.getNumberOfFlagsByProcess(self.processName))) # logging total time elapsed self.endTimeCount() if self.totalTime: logMsg += self.tr( "\nTotal elapsed time for process {0}: {1}\n").format( self.processAlias, self.totalTime) else: logMsg += self.tr("\nUnable to get total elapsed time.") self.logMsg = logMsg QgsMessageLog.logMessage(logMsg, "DSG Tools Plugin", QgsMessageLog.CRITICAL) def raiseVectorFlags(self, flagLyr, featFlagList): flagLyr.startEditing() flagLyr.beginEditCommand('Raising flags') #speedup flagLyr.addFeatures(featFlagList, False) flagLyr.endEditCommand() flagLyr.commitChanges() return len(featFlagList) def buildFlagFeature(self, flagLyr, processName, tableSchema, tableName, feat_id, geometry_column, geom, reason): newFeat = QgsFeature(flagLyr.pendingFields()) newFeat['process_name'] = processName newFeat['layer'] = '{0}.{1}'.format(tableSchema, tableName) newFeat['feat_id'] = feat_id newFeat['reason'] = reason newFeat['geometry_column'] = geometry_column newFeat['user_fixed'] = False newFeat['dimension'] = geom.type() newFeat.setGeometry(geom) return newFeat def getFeatures(self, lyr, onlySelected=False, returnIterator=True, returnSize=True): if onlySelected: featureList = lyr.selectedFeatures() size = len(featureList) else: featureList = [i for i in lyr.getFeatures() ] if not returnIterator else lyr.getFeatures() size = len(lyr.allFeatureIds()) if returnIterator: return featureList, size else: return featureList
class LoadLayersFromServer(QtGui.QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" super(self.__class__, self).__init__(parent) self.iface = iface self.utils = Utils() self.setupUi(self) self.layerFactory = LayerLoaderFactory() self.customServerConnectionWidget.postgisCustomSelector.setTitle( self.tr('Select Databases')) self.customServerConnectionWidget.spatialiteCustomSelector.setTitle( self.tr('Selected Spatialites')) self.layersCustomSelector.setTitle( self.tr('Select layers to be loaded')) self.customServerConnectionWidget.dbDictChanged.connect( self.updateLayersFromDbs) self.customServerConnectionWidget.resetAll.connect(self.resetInterface) self.customServerConnectionWidget.styleChanged.connect( self.populateStyleCombo) self.headerList = [ self.tr('Category'), self.tr('Layer Name'), self.tr('Geometry\nColumn'), self.tr('Geometry\nType'), self.tr('Layer\nType') ] self.layersCustomSelector.setHeaders(self.headerList) self.customServerConnectionWidget.postgisEdgvComboFilter.currentIndexChanged.connect( self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.spatialiteEdgvComboFilter.currentIndexChanged.connect( self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.serverConnectionTab.currentChanged.connect( self.layersCustomSelector.setInitialState) self.lyrDict = dict() def resetInterface(self): """ Sets the initial state again """ self.layersCustomSelector.clearAll() self.styleComboBox.clear() #TODO: refresh optional parameters self.checkBoxOnlyWithElements.setCheckState(0) self.onlyParentsCheckBox.setCheckState(0) @pyqtSlot() def on_buttonBox_rejected(self): """ Closes the dialog """ self.close() def updateLayersFromDbs(self, type, dbList, showViews=False): """ """ errorDict = dict() if type == 'added': progress = ProgressWidget( 1, len(dbList), self.tr('Reading selected databases... '), parent=self) progress.initBar() for dbName in dbList: try: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) geomList = self.customServerConnectionWidget.selectedDbsDict[ dbName].getGeomColumnTupleList(showViews=showViews) for tableSchema, tableName, geom, geomType, tableType in geomList: if self.customServerConnectionWidget.edgvType == 'Non_EDGV': lyrName = tableName cat = tableSchema else: lyrName = '_'.join(tableName.split('_')[1::]) cat = tableName.split('_')[0] key = ','.join( [cat, lyrName, geom, geomType, tableType]) if key not in self.lyrDict.keys(): self.lyrDict[key] = dict() self.lyrDict[key][dbName] = { 'tableSchema': tableSchema, 'tableName': tableName, 'geom': geom, 'geomType': geomType, 'tableType': tableType, 'lyrName': lyrName, 'cat': cat } except Exception as e: errorDict[dbName] = ':'.join(e.args) QApplication.restoreOverrideCursor() progress.step() QApplication.restoreOverrideCursor() elif type == 'removed': for key in self.lyrDict.keys(): for db in self.lyrDict[key].keys(): if db in dbList: self.lyrDict[key].pop(db) if self.lyrDict[key] == dict(): self.lyrDict.pop(key) interfaceDictList = [] for key in self.lyrDict.keys(): cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDictList.append({ self.tr('Category'): cat, self.tr('Layer Name'): lyrName, self.tr('Geometry\nColumn'): geom, self.tr('Geometry\nType'): geomType, self.tr('Layer\nType'): tableType }) self.layersCustomSelector.setInitialState(interfaceDictList, unique=True) @pyqtSlot() def on_buttonBox_accepted(self): """ Loads the selected classes/categories """ #1- filter classes if categories is checked and build list. selectedKeys = self.layersCustomSelector.getSelectedNodes() if len(selectedKeys) == 0: QMessageBox.information( self, self.tr('Error!'), self.tr('Select at least one layer to be loaded!')) return #2- get parameters withElements = self.checkBoxOnlyWithElements.isChecked() selectedStyle = None if self.customServerConnectionWidget.edgvType == 'Non_EDGV': isEdgv = False else: isEdgv = True if self.styleComboBox.currentIndex() != 0: selectedStyle = self.customServerConnectionWidget.stylesDict[ self.styleComboBox.currentText()] uniqueLoad = self.uniqueLoadCheckBox.isChecked() onlyParents = self.onlyParentsCheckBox.isChecked() #3- Build factory dict factoryDict = dict() dbList = self.customServerConnectionWidget.selectedDbsDict.keys() for dbName in dbList: factoryDict[dbName] = self.layerFactory.makeLoader( self.iface, self.customServerConnectionWidget.selectedDbsDict[dbName]) #4- load for each db exceptionDict = dict() progress = ProgressWidget( 1, len(dbList), self.tr('Loading layers from selected databases... '), parent=self) for dbName in factoryDict.keys(): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: selectedClasses = [] for i in selectedKeys: if i in self.lyrDict.keys(): if dbName in self.lyrDict[i].keys(): selectedClasses.append(self.lyrDict[i][dbName]) factoryDict[dbName].load(selectedClasses, uniqueLoad=uniqueLoad, onlyWithElements=withElements, stylePath=selectedStyle, useInheritance=onlyParents, isEdgv=isEdgv, parent=self) progress.step() except Exception as e: exceptionDict[dbName] = ':'.join(e.args) QApplication.restoreOverrideCursor() progress.step() QApplication.restoreOverrideCursor() if factoryDict[dbName].errorLog != '': if dbName in exceptionDict.keys(): exceptionDict[ dbName] += '\n' + factoryDict[dbName].errorLog else: exceptionDict[dbName] = factoryDict[dbName].errorLog QApplication.restoreOverrideCursor() self.logInternalError(exceptionDict) self.close() def logInternalError(self, exceptionDict): """ Logs internal errors during the load process in QGIS' log """ msg = '' errorDbList = exceptionDict.keys() if len(errorDbList) > 0: msg += self.tr('\nDatabases with error:') msg += ', '.join(errorDbList) msg += self.tr( '\nError messages for each database were output in qgis log.') for errorDb in errorDbList: QgsMessageLog.logMessage( self.tr('Error for database ') + errorDb + ': ' + exceptionDict[errorDb], "DSG Tools Plugin", QgsMessageLog.CRITICAL) return msg def populateStyleCombo(self, styleDict): """ Loads styles saved in the database """ self.styleComboBox.clear() styleList = styleDict.keys() numberOfStyles = len(styleList) if numberOfStyles > 0: self.styleComboBox.addItem(self.tr('Select Style')) for i in range(numberOfStyles): self.styleComboBox.addItem(styleList[i]) else: self.styleComboBox.addItem(self.tr('No available styles'))
class LoadLayersFromServer(QtGui.QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" super(self.__class__, self).__init__(parent) self.iface = iface self.utils = Utils() self.setupUi(self) self.layerFactory = LayerLoaderFactory() self.customServerConnectionWidget.postgisCustomSelector.setTitle(self.tr('Select Databases')) self.customServerConnectionWidget.spatialiteCustomSelector.setTitle(self.tr('Selected Spatialites')) self.layersCustomSelector.setTitle(self.tr('Select layers to be loaded')) self.customServerConnectionWidget.dbDictChanged.connect(self.updateLayersFromDbs) self.customServerConnectionWidget.resetAll.connect(self.resetInterface) self.customServerConnectionWidget.styleChanged.connect(self.populateStyleCombo) self.headerList = [self.tr('Category'), self.tr('Layer Name'), self.tr('Geometry\nColumn'), self.tr('Geometry\nType'), self.tr('Layer\nType')] self.layersCustomSelector.setHeaders(self.headerList) self.customServerConnectionWidget.postgisEdgvComboFilter.currentIndexChanged.connect(self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.spatialiteEdgvComboFilter.currentIndexChanged.connect(self.layersCustomSelector.setInitialState) self.customServerConnectionWidget.serverConnectionTab.currentChanged.connect(self.layersCustomSelector.setInitialState) self.lyrDict = dict() def resetInterface(self): """ Sets the initial state again """ self.layersCustomSelector.clearAll() self.styleComboBox.clear() #TODO: refresh optional parameters self.checkBoxOnlyWithElements.setCheckState(0) self.onlyParentsCheckBox.setCheckState(0) @pyqtSlot() def on_buttonBox_rejected(self): """ Closes the dialog """ self.close() def updateLayersFromDbs(self, type, dbList, showViews = False): """ """ errorDict = dict() if type == 'added': progress = ProgressWidget(1, len(dbList), self.tr('Reading selected databases... '), parent=self) progress.initBar() for dbName in dbList: try: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) geomList = self.customServerConnectionWidget.selectedDbsDict[dbName].getGeomColumnTupleList(showViews = showViews) for tableSchema, tableName, geom, geomType, tableType in geomList: if self.customServerConnectionWidget.edgvType == 'Non_EDGV': lyrName = tableName cat = tableSchema else: lyrName = '_'.join(tableName.split('_')[1::]) cat = tableName.split('_')[0] key = ','.join([cat, lyrName, geom, geomType, tableType]) if key not in self.lyrDict.keys(): self.lyrDict[key] = dict() self.lyrDict[key][dbName] = {'tableSchema':tableSchema, 'tableName':tableName, 'geom':geom, 'geomType':geomType, 'tableType':tableType, 'lyrName':lyrName, 'cat':cat} except Exception as e: errorDict[dbName] = ':'.join(e.args) QApplication.restoreOverrideCursor() progress.step() QApplication.restoreOverrideCursor() elif type == 'removed': for key in self.lyrDict.keys(): for db in self.lyrDict[key].keys(): if db in dbList: self.lyrDict[key].pop(db) if self.lyrDict[key] == dict(): self.lyrDict.pop(key) interfaceDictList = [] for key in self.lyrDict.keys(): cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDictList.append({self.tr('Category'):cat, self.tr('Layer Name'):lyrName, self.tr('Geometry\nColumn'):geom, self.tr('Geometry\nType'):geomType, self.tr('Layer\nType'):tableType}) self.layersCustomSelector.setInitialState(interfaceDictList,unique = True) @pyqtSlot() def on_buttonBox_accepted(self): """ Loads the selected classes/categories """ #1- filter classes if categories is checked and build list. selectedKeys = self.layersCustomSelector.getSelectedNodes() if len(selectedKeys) == 0: QMessageBox.information(self, self.tr('Error!'), self.tr('Select at least one layer to be loaded!')) return #2- get parameters withElements = self.checkBoxOnlyWithElements.isChecked() selectedStyle = None if self.customServerConnectionWidget.edgvType == 'Non_EDGV': isEdgv = False else: isEdgv = True if self.styleComboBox.currentIndex() != 0: selectedStyle = self.customServerConnectionWidget.stylesDict[self.styleComboBox.currentText()] uniqueLoad = self.uniqueLoadCheckBox.isChecked() onlyParents = self.onlyParentsCheckBox.isChecked() #3- Build factory dict factoryDict = dict() dbList = self.customServerConnectionWidget.selectedDbsDict.keys() for dbName in dbList: factoryDict[dbName] = self.layerFactory.makeLoader(self.iface, self.customServerConnectionWidget.selectedDbsDict[dbName]) #4- load for each db exceptionDict = dict() progress = ProgressWidget(1, len(dbList), self.tr('Loading layers from selected databases... '), parent=self) for dbName in factoryDict.keys(): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: selectedClasses = [] for i in selectedKeys: if i in self.lyrDict.keys(): if dbName in self.lyrDict[i].keys(): selectedClasses.append(self.lyrDict[i][dbName]) factoryDict[dbName].load(selectedClasses, uniqueLoad=uniqueLoad, onlyWithElements=withElements, stylePath=selectedStyle, useInheritance=onlyParents, isEdgv=isEdgv, parent=self) progress.step() except Exception as e: exceptionDict[dbName] = ':'.join(e.args) QApplication.restoreOverrideCursor() progress.step() QApplication.restoreOverrideCursor() if factoryDict[dbName].errorLog != '': if dbName in exceptionDict.keys(): exceptionDict[dbName] += '\n'+factoryDict[dbName].errorLog else: exceptionDict[dbName] = factoryDict[dbName].errorLog QApplication.restoreOverrideCursor() self.logInternalError(exceptionDict) self.close() def logInternalError(self, exceptionDict): """ Logs internal errors during the load process in QGIS' log """ msg = '' errorDbList = exceptionDict.keys() if len(errorDbList) > 0: msg += self.tr('\nDatabases with error:') msg += ', '.join(errorDbList) msg += self.tr('\nError messages for each database were output in qgis log.') for errorDb in errorDbList: QgsMessageLog.logMessage(self.tr('Error for database ') + errorDb + ': ' +exceptionDict[errorDb], "DSG Tools Plugin", QgsMessageLog.CRITICAL) return msg def populateStyleCombo(self, styleDict): """ Loads styles saved in the database """ self.styleComboBox.clear() styleList = styleDict.keys() numberOfStyles = len(styleList) if numberOfStyles > 0: self.styleComboBox.addItem(self.tr('Select Style')) for i in range(numberOfStyles): self.styleComboBox.addItem(styleList[i]) else: self.styleComboBox.addItem(self.tr('No available styles'))
class ValidationProcess(QObject): def __init__(self, postgisDb, iface, instantiating=False): """ Constructor """ super(ValidationProcess, self).__init__() self.abstractDb = postgisDb if self.getStatus() == None: self.setStatus(self.tr('Instantianting process'), 0) self.classesToBeDisplayedAfterProcess = [] self.parameters = None self.iface = iface self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, self.abstractDb) self.processAlias = self.tr('Validation Process') self.instantiating = instantiating def setParameters(self, params): """ Define the process parameteres """ self.parameters = params def execute(self): """ Abstract method. MUST be reimplemented. """ pass def shouldBeRunAgain(self): """ Defines if the method should run again later """ return False def getName(self): """ Gets the process name """ return str(self.__class__).split('.')[-1].replace('\'>', '') def getProcessGroup(self): """ Returns the process group """ return 'Ungrouped' def getClassesToBeDisplayedAfterProcess(self): """ Returns classes to be loaded to TOC after executing this process. """ return self.classesToBeDisplayedAfterProcess def addClassesToBeDisplayedList(self,className): """ Add a class into the class list that will be displayed after the process """ if className not in self.classesToBeDisplayedAfterProcess: self.classesToBeDisplayedAfterProcess.append(className) def clearClassesToBeDisplayedAfterProcess(self): """ Clears the class list to be displayed """ self.classesToBeDisplayedAfterProcess = [] def preProcess(self): """ Returns the name of the pre process that must run before, must be reimplemented in each process """ return None def postProcess(self): """ Returns the name of the post process that must run after, must be reimplemented in each process """ return None def addFlag(self, flagTupleList): """ Adds flags flagTUpleList: list of tuples to be added as flag """ try: return self.abstractDb.insertFlags(flagTupleList, self.getName()) except Exception as e: QMessageBox.critical(None, self.tr('Critical!'), self.tr('A problem occurred inserting flags! Check log for details.')) QgsMessageLog.logMessage(str(e.args[0]), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def removeFeatureFlags(self, layer, featureId): """ Removes specific flags from process layer: Name of the layer that should be remove from the flags featureId: Feature id from layer name that must be removed """ try: return self.abstractDb.removeFeatureFlags(layer, featureId, self.getName()) except Exception as e: QMessageBox.critical(None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def getStatus(self): """ Gets the process status """ try: return self.abstractDb.getValidationStatus(self.getName()) except Exception as e: QMessageBox.critical(None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def getStatusMessage(self): """ Gets the status message text """ try: return self.abstractDb.getValidationStatusText(self.getName()) except Exception as e: QMessageBox.critical(None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def setStatus(self, msg, status): """ Sets the status message status: Status number msg: Status text message """ try: self.abstractDb.setValidationProcessStatus(self.getName(), msg, status) except Exception as e: QMessageBox.critical(None, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def finishedWithError(self): """ Sets the finished with error status (status number 2) Clears the classes to be displayed """ self.setStatus(self.tr('Process finished with errors.'), 2) #Failed status #drop temps try: classesWithElem = self.parameters['Classes'] for cl in classesWithElem: tempName = cl.split(':')[0]+'_temp' self.abstractDb.dropTempTable(tempName) except: pass self.clearClassesToBeDisplayedAfterProcess() def inputData(self): """ Returns current active layers """ return self.iface.mapCanvas().layers() def getTableNameFromLayer(self, lyr): """ Gets the layer name as present in the rules """ uri = lyr.dataProvider().dataSourceUri() dsUri = QgsDataSourceURI(uri) name = '.'.join([dsUri.schema(), dsUri.table()]) return name def mapInputLayer(self, inputLyr, selectedFeatures = False): """ Gets the layer features considering the edit buffer in the case the layer is already in edition mode """ #return dict featureMap = dict() #getting only selected features if selectedFeatures: for feat in inputLyr.selectedFeatures(): featureMap[feat.id()] = feat #getting all features else: for feat in inputLyr.getFeatures(): featureMap[feat.id()] = feat return featureMap def updateOriginalLayer(self, pgInputLayer, qgisOutputVector, featureList=None, featureTupleList=None): """ Updates the original layer using the grass output layer pgInputLyr: postgis input layer grassOutputLyr: grass output layer """ provider = pgInputLayer.dataProvider() # getting keyColumn because we want to be generic uri = QgsDataSourceURI(pgInputLayer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() # starting edition mode pgInputLayer.startEditing() addList = [] idsToRemove = [] #making the changes and inserts for feature in pgInputLayer.getFeatures(): id = feature.id() outFeats = [] #getting the output features with the specific id if qgisOutputVector: for gf in qgisOutputVector.dataProvider().getFeatures(QgsFeatureRequest(QgsExpression("{0}={1}".format(keyColumn, id)))): outFeats.append(gf) elif featureTupleList: for gfid, gf in featureTupleList: if gfid == id and gf['classname'] == pgInputLayer.name(): outFeats.append(gf) else: for gf in [gf for gf in featureList if gf.id() == id]: outFeats.append(gf) #starting to make changes for i in range(len(outFeats)): if i == 0: #let's update this feature newGeom = outFeats[i].geometry() newGeom.convertToMultiType() feature.setGeometry(newGeom) pgInputLayer.updateFeature(feature) else: #for the rest, let's add them newFeat = QgsFeature(feature) newGeom = outFeats[i].geometry() newGeom.convertToMultiType() newFeat.setGeometry(newGeom) idx = newFeat.fieldNameIndex(keyColumn) newFeat.setAttribute(idx, provider.defaultValue(idx)) addList.append(newFeat) #in the case we don't find features in the output we should mark them to be removed if len(outFeats) == 0: idsToRemove.append(id) #pushing the changes into the edit buffer pgInputLayer.addFeatures(addList, True) #removing features from the layer. pgInputLayer.deleteFeatures(idsToRemove) def getProcessingErrors(self, layer): """ Gets processing errors layer: error layer (QgsVectorLayer) output made by grass """ recordList = [] for feature in layer.getFeatures(): recordList.append((feature.id(), binascii.hexlify(feature.geometry().asWkb()))) return recordList def loadLayerBeforeValidationProcess(self, cl): """ Loads all layers to QGIS' TOC prior the validation process """ #creating vector layer if self.abstractDb.getDatabaseVersion() == 'Non_EDGV': isEdgv = False else: isEdgv = True if isinstance(cl, dict): lyr = self.layerLoader.load([cl], uniqueLoad=True, isEdgv=isEdgv)[cl['lyrName']] else: schema, layer_name = self.abstractDb.getTableSchema(cl) lyr = self.layerLoader.load([layer_name], uniqueLoad=True, isEdgv=isEdgv)[layer_name] return lyr def prepareExecution(self, cl, geometryColumn='geom', selectedFeatures = False): """ Prepare the process to be executed cl: table name """ # loading layer prior to execution lyr = self.loadLayerBeforeValidationProcess(cl) # getting keyColumn because we want to be generic uri = QgsDataSourceURI(lyr.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() #getting feature map including the edit buffer featureMap = self.mapInputLayer(lyr, selectedFeatures = selectedFeatures) #getting table name with schema if isinstance(cl, dict): tableSchema = cl['tableSchema'] tableName = cl['tableName'] geometryColumn = cl['geom'] fullTableName = '''{0}.{1}'''.format(cl['tableSchema'], cl['tableName']) else: fullTableName = cl tableSchema, tableName = cl.split('.') #setting temp table name processTableName = fullTableName+'_temp' # specific EPSG search parameters = {'tableSchema':tableSchema, 'tableName':tableName, 'geometryColumn':geometryColumn} srid = self.abstractDb.findEPSG(parameters=parameters) #creating temp table self.abstractDb.createAndPopulateTempTableFromMap(fullTableName, featureMap, geometryColumn, keyColumn, srid) return processTableName, lyr, keyColumn def postProcessSteps(self, processTableName, lyr): """ Execute the final steps after the actual process """ #getting the output as a QgsVectorLayer outputLayer = QgsVectorLayer(self.abstractDb.getURI(processTableName, True).uri(), processTableName, "postgres") #updating the original layer (lyr) self.updateOriginalLayer(lyr, outputLayer) #dropping the temp table as we don't need it anymore self.abstractDb.dropTempTable(processTableName) def getGeometryTypeText(self, geomtype): if geomtype == QGis.WKBPoint: return 'Point' elif geomtype == QGis.WKBMultiPoint: return 'MultiPoint' elif geomtype == QGis.WKBLineString: return 'Linestring' elif geomtype == QGis.WKBMultiLineString: return 'MultiLinestring' elif geomtype == QGis.WKBPolygon: return 'Polygon' elif geomtype == QGis.WKBMultiPolygon: return 'MultiPolygon' else: raise Exception(self.tr('Operation not defined with provided geometry type!')) def createUnifiedLayer(self, layerList, attributeTupple = False, attributeBlackList = ''): """ Creates a unified layer from a list of layers """ #getting srid from something like 'EPSG:31983' srid = layerList[0].crs().authid().split(':')[-1] # creating the layer geomtype = layerList[0].dataProvider().geometryType() for lyr in layerList: if lyr.dataProvider().geometryType() != geomtype: raise Exception(self.tr('Error! Different geometry primitives!')) coverage = self.iface.addVectorLayer("{0}?crs=epsg:{1}".format(self.getGeometryTypeText(geomtype),srid), "coverage", "memory") provider = coverage.dataProvider() coverage.startEditing() #defining fields if not attributeTupple: fields = [QgsField('featid', QVariant.Int), QgsField('classname', QVariant.String)] else: fields = [QgsField('featid', QVariant.Int), QgsField('classname', QVariant.String), QgsField('tupple', QVariant.String), QgsField('blacklist', QVariant.String)] provider.addAttributes(fields) coverage.updateFields() totalCount = 0 for layer in layerList: totalCount += layer.pendingFeatureCount() self.localProgress = ProgressWidget(1, totalCount - 1, self.tr('Building unified layers with ') + ', '.join([i.name() for i in layerList])+'.', parent=self.iface.mapCanvas()) featlist = [] if attributeBlackList != '': bList = attributeBlackList.replace(' ','').split(',') else: bList = [] for layer in layerList: # recording class name classname = layer.name() uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() for feature in layer.getFeatures(): newfeat = QgsFeature(coverage.pendingFields()) newfeat.setGeometry(feature.geometry()) newfeat['featid'] = feature.id() newfeat['classname'] = classname if attributeTupple: attributeList = [] attributes = [field.name() for field in feature.fields() if (field.type() != 6 and field.name() != keyColumn)] for attribute in attributes: if attribute not in bList: attributeList.append(u'{0}'.format(feature[attribute])) #done due to encode problems tup = ','.join(attributeList) newfeat['tupple'] = tup featlist.append(newfeat) self.localProgress.step() #inserting new features into layer coverage.addFeatures(featlist, True) coverage.commitChanges() return coverage def splitUnifiedLayer(self, outputLayer, layerList): """ Updates all original layers making requests with the class name """ for layer in layerList: classname = layer.name() tupplelist = [(feature['featid'], feature) for feature in outputLayer.getFeatures()] self.updateOriginalLayer(layer, None, featureTupleList=tupplelist) def getGeometryColumnFromLayer(self, layer): uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) geomColumn = uri.geometryColumn() return geomColumn
class FieldToolbox(QtGui.QDockWidget, FORM_CLASS): def __init__(self, iface, parent = None): """Constructor.""" super(self.__class__, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.iface = iface self.buttons = [] self.prevLayer = None self.buttonName = '' self.category = '' self.widget.dbChanged.connect(self.defineFactory) self.releaseButtonConected = False self.addedFeatures = [] self.configFromDbDict = dict() def defineFactory(self, abstractDb): """ Defines the layer loader by its type :param abstractDb: :return: """ self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, abstractDb) try: self.populateConfigFromDb() except Exception as e: QgsMessageLog.logMessage(self.tr('Error getting stored configuration.\n')+':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def setEditButtonEnabled(self, enabled): """ Edits the current configuration settings :param enabled: :return: """ self.editCurrentConfigButton.setEnabled(enabled) @pyqtSlot(bool, name='on_setupButton_clicked') @pyqtSlot(bool, name='on_editCurrentConfigButton_clicked') def populateWindow(self): """ Creates the buttons according to the field setup """ if self.widget.abstractDb == None: QtGui.QMessageBox.critical(self, self.tr('Error!'), self.tr('First select a database!')) return if isinstance(self.sender(), QPushButton): sender = self.sender().text() else: sender = '' dlg = FieldSetup(self.widget.abstractDb) if sender != self.tr('Setup'): dlg.loadReclassificationConf(self.reclassificationDict) if sender != '': result = dlg.exec_() else: result = 1 if result == 1: self.createButtonsOnInterface(dlg) self.setEditButtonEnabled(True) def createButtonsOnInterface(self, dlg): """ Creates button according to what is set in the configuration :param dlg: :return: """ #reclassification dictionary made from the field setup file self.reclassificationDict = dlg.makeReclassificationDict() #button size defined by the user self.size = dlg.slider.value() #check if the button must be grouped by category withTabs = dlg.checkBox.isChecked() #actual button creation step self.createButtons(self.reclassificationDict, withTabs) @pyqtSlot(bool, name = 'on_newFeatureRadioButton_toggled') def turnButtonsOn(self, enabled): """ Adjusts tool behavior. The default state makes the tool work with selected features but the user can choose to acquire a feature in real time. When working in real time the buttons must be checkable. """ if enabled: #connecting iface signals self.iface.currentLayerChanged.connect(self.acquire) for button in self.buttons: #disconnecting the clicked signal button.clicked.disconnect(self.reclassify) #changing button behavior button.setCheckable(True) else: #disconnecting iface signals self.disconnectLayerSignals() try:self.iface.currentLayerChanged.disconnect(self.acquire) except:pass for button in self.buttons: #connecting the clicked signal button.clicked.connect(self.reclassify) #changing button behavior button.setCheckable(False) def createWidgetWithoutTabs(self, formLayout): """ Adjusts the scroll area to receive the buttons directly (not grouped by category) formLayout: Layout used to receive all the buttons """ w = QtGui.QWidget() w.setLayout(formLayout) self.scrollArea.setWidget(w) def createWidgetWithTabs(self, formLayout): """ Creates a scroll area for each form layout. formLayout: Layout used to receive the buttons in each tab """ scrollArea = QtGui.QScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setFrameShape(QtGui.QFrame.Shape(0)) # no frame w = QtGui.QWidget() w.setLayout(formLayout) scrollArea.setWidget(w) return scrollArea def on_filterLineEdit_textChanged(self, text): for i in self.buttons: if text.lower() in i.text().lower(): i.show() else: i.hide() def createButton(self, button): """ Creates the buttons according to the user size definition button: Button name """ pushButton = QtGui.QPushButton(button) pushButton.clicked.connect(self.reclassify) pushButton.toggled.connect(self.acquire) if self.size == 0: pushButton.setMinimumSize(100, 25) pushButton.setStyleSheet('font-size:12px') elif self.size == 1: pushButton.setMinimumSize(100, 40) pushButton.setStyleSheet('font-size:20px') elif self.size == 2: pushButton.setMinimumSize(100, 80) pushButton.setStyleSheet('font-size:30px') self.buttons.append(pushButton) return pushButton def createButtons(self, reclassificationDict, createTabs=False): """ Convenience method to create buttons createTabs: Indicates if the buttons must be created within tabs """ self.buttons = [] widget = self.scrollArea.takeWidget() if createTabs: self.createButtonsWithTabs(reclassificationDict) else: self.createButtonsWithoutTabs(reclassificationDict) self.turnButtonsOn(self.newFeatureRadioButton.isChecked()) def createButtonsWithoutTabs(self, reclassificationDict): """ Specific method to create buttons without tabs reclassificationDict: dictionary used to create the buttons """ formLayout = QtGui.QFormLayout() self.createWidgetWithoutTabs(formLayout) sortedButtonNames = [] for category in reclassificationDict.keys(): if category in ['version', 'uiParameterJsonDict']: continue for edgvClass in reclassificationDict[category].keys(): for button in reclassificationDict[category][edgvClass].keys(): sortedButtonNames.append(button) sortedButtonNames.sort() for button in sortedButtonNames: pushButton = self.createButton(button) formLayout.addRow(pushButton) def createButtonsWithTabs(self, reclassificationDict): """ Specific method to create buttons with tabs reclassificationDict: dictionary used to create the buttons """ gridLayout = QtGui.QGridLayout() tabWidget = QtGui.QTabWidget() tabWidget.setTabPosition(QtGui.QTabWidget.West) gridLayout.addWidget(tabWidget) self.scrollArea.setWidget(tabWidget) for category in reclassificationDict.keys(): if category in ['version', 'uiParameterJsonDict']: continue sortedButtonNames = [] formLayout = QtGui.QFormLayout() scrollArea = self.createWidgetWithTabs(formLayout) tabWidget.addTab(scrollArea, category) for edgvClass in reclassificationDict[category].keys(): for button in reclassificationDict[category][edgvClass].keys(): sortedButtonNames.append(button) sortedButtonNames.sort() for button in sortedButtonNames: pushButton = self.createButton(button) formLayout.addRow(pushButton) def loadLayer(self, layer): """ Loads the layer used in the actual reclassification layer: Layer name """ try: return self.layerLoader.load([layer], uniqueLoad=True)[layer] except Exception as e: QtGui.QMessageBox.critical(self, self.tr('Error!'), self.tr('Could not load the selected classes!\n')+':'.join(e.args)) def checkConditions(self): """ Check the conditions to see if the tool can be used """ if not self.widget.abstractDb: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Please, select a database.')) return False try: version = self.widget.abstractDb.getDatabaseVersion() except Exception as e: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Problem obtaining database version! Please, check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) return False if self.reclassificationDict['version'] != version: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Database version does not match the field toolbox version.')) return False return True def getLayerFromButton(self, button): """ Gets the correct layer to be used in the tool """ #edgvClass found in the dictionary (this is made using the sqlite seed) (category, edgvClass) = self.findReclassificationClass(button) driverName = self.widget.abstractDb.getType() if driverName == "QSQLITE": #reclassification layer name reclassificationClass = '_'.join(edgvClass.split('_')[1::]) if driverName == "QPSQL": #reclassification layer name reclassificationClass = '_'.join(edgvClass.split('.')[1::]) #getting the QgsVectorLayer to perform the reclassification reclassificationLayer = self.loadLayer(reclassificationClass) if reclassificationLayer: self.iface.setActiveLayer(reclassificationLayer) #entering in editing mode if not reclassificationLayer.isEditable(): reclassificationLayer.startEditing() return (reclassificationLayer, category, edgvClass) @pyqtSlot(int) def setAttributesFromButton(self, featureId): """ Sets the attributes for the newly added feature featureId: added feature """ layer = self.sender() if isinstance(layer, QgsVectorLayer): layer.beginEditCommand(self.tr('DsgTools reclassification')) self.addedFeatures.append(featureId) def updateAttributesAfterAdding(self): """ Updates feature attributes according to the button configuration :return: """ layer = self.sender() while self.addedFeatures: featureId = self.addedFeatures.pop() #begining the edit command # layer.beginEditCommand(self.tr("DSG Tools reclassification tool: adjusting feature's attributes")) #accessing added features editBuffer = layer.editBuffer() features = editBuffer.addedFeatures() for key in features.keys(): #just checking the newly added feature, the other I don't care if key == featureId: feature = features[key] #setting the attributes using the reclassification dictionary self.setFeatureAttributes(feature, editBuffer) layer.endEditCommand() def setFeatureAttributes(self, newFeature, editBuffer=None): """ Changes attribute values according to the reclassification dict using the edit buffer newFeature: newly added editBuffer: layer edit buffer """ #setting the attributes using the reclassification dictionary for attribute in self.reclassificationDict[self.category][self.edgvClass][self.buttonName].keys(): idx = newFeature.fieldNameIndex(attribute) #value to be changed value = self.reclassificationDict[self.category][self.edgvClass][self.buttonName][attribute] if value == '': continue #actual attribute change if editBuffer: #this way we are working with the edit buffer editBuffer.changeAttributeValue(newFeature.id(), idx, value) else: #this way are working with selected features and inserting a new one in the layer newFeature.setAttribute(idx, value) if not editBuffer: # we should return when under the normal behavior return newFeature def disconnectLayerSignals(self): """ Disconnecting the signals from the previous layer """ if self.prevLayer: try: self.prevLayer.featureAdded.disconnect(self.setAttributesFromButton) self.prevLayer.editCommandEnded.disconnect(self.updateAttributesAfterAdding) self.prevLayer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOff) except: pass @pyqtSlot(bool) def acquire(self, pressed): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes. The difference here is the use of real time editing to make the reclassification """ if pressed: if not self.checkConditions(): return #getting the object that sent the signal sender = self.sender() #if the sender is the iface object, this means that the user made the click and changed the current layer #when this happens we should untoggle all buttons if isinstance(sender, QgisInterface): #checking if another button is checked for button in self.buttons: button.setChecked(False) #return and do nothing else return #button that sent the signal self.buttonName = sender.text() #checking if another button is checked for button in self.buttons: if button.text() != self.buttonName and button.isChecked(): button.setChecked(False) #disconnecting the previous layer self.disconnectLayerSignals() (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) #suppressing the form dialog reclassificationLayer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOn) #connecting addedFeature signal reclassificationLayer.featureAdded.connect(self.setAttributesFromButton) reclassificationLayer.editCommandEnded.connect(self.updateAttributesAfterAdding) #triggering the add feature tool self.iface.actionAddFeature().trigger() #setting the previous layer self.prevLayer = reclassificationLayer else: #disconnecting the previous layer self.disconnectLayerSignals() @pyqtSlot() def reclassify(self): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes """ if not self.checkConditions(): return somethingMade = False reclassifiedFeatures = 0 #button that sent the signal self.buttonName = self.sender().text() (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) geomType = reclassificationLayer.geometryType() hasMValues = QgsWKBTypes.hasM(int(reclassificationLayer.wkbType())) #generic check (not every database is implemented as ours) hasZValues = QgsWKBTypes.hasZ(int(reclassificationLayer.wkbType())) # isMulti = QgsWKBTypes.isMultiType(int(reclassificationLayer.wkbType())) # mapLayers = self.iface.mapCanvas().layers() crsSrc = QgsCoordinateReferenceSystem(self.widget.crs.authid()) deleteList = [] for mapLayer in mapLayers: if mapLayer.type() != QgsMapLayer.VectorLayer: continue #iterating over selected features featList = [] mapLayerCrs = mapLayer.crs() #creating a coordinate transformer (mapLayerCrs to crsSrc) coordinateTransformer = QgsCoordinateTransform(mapLayerCrs, crsSrc) for feature in mapLayer.selectedFeatures(): geomList = [] geom = feature.geometry() if geom.type() != geomType: continue if 'geometry' in dir(geom): if not hasMValues: geom.geometry().dropMValue() if not hasZValues: geom.geometry().dropZValue() if isMulti and not geom.isMultipart(): geom.convertToMultiType() geomList.append(geom) elif not isMulti and geom.isMultipart(): #deaggregate here parts = geom.asGeometryCollection() for part in parts: part.convertToSingleType() geomList.append(part) else: geomList.append(geom) for newGeom in geomList: #creating a new feature according to the reclassification layer newFeature = QgsFeature(reclassificationLayer.pendingFields()) #transforming the geometry to the correct crs geom.transform(coordinateTransformer) #setting the geometry newFeature.setGeometry(newGeom) #setting the attributes using the reclassification dictionary newFeature = self.setFeatureAttributes(newFeature) #adding the newly created feature to the addition list featList.append(newFeature) somethingMade = True deleteList.append({'originalLyr':mapLayer,'featid':feature.id()}) #actual feature insertion reclassificationLayer.addFeatures(featList, False) reclassifiedFeatures += len(featList) for item in deleteList: item['originalLyr'].startEditing() item['originalLyr'].deleteFeature(item['featid']) if somethingMade: self.iface.messageBar().pushMessage(self.tr('Information!'), self.tr('{} features reclassified with success!').format(reclassifiedFeatures), level=QgsMessageBar.INFO, duration=3) def findReclassificationClass(self, button): """ Finds the reclassification class according to the button button: Button clicked by the user to perform the reclassification """ for category in self.reclassificationDict.keys(): if category == 'version' or category == 'uiParameterJsonDict': continue for edgvClass in self.reclassificationDict[category].keys(): for buttonName in self.reclassificationDict[category][edgvClass].keys(): if button == buttonName: #returning the desired edgvClass return (category, edgvClass) return () def populateConfigFromDb(self): """ Populates configFromDbComboBox with config from public.field_toolbox_config """ driverName = self.widget.abstractDb.getType() self.configFromDbComboBox.clear() self.configFromDbComboBox.addItem(self.tr('Select Stored Config (optional)')) if driverName == 'QPSQL': self.configFromDbComboBox.setEnabled(True) propertyDict = self.widget.abstractDb.getPropertyDict('FieldToolBoxConfig') dbVersion = self.widget.abstractDb.getDatabaseVersion() if dbVersion in propertyDict.keys(): self.configFromDbDict = propertyDict[dbVersion] nameList = self.configFromDbDict.keys() nameList.sort() for name in nameList: self.configFromDbComboBox.addItem(name) else: self.configFromDbComboBox.setEnabled(False) self.configFromDbDict = dict() @pyqtSlot(int) def on_configFromDbComboBox_currentIndexChanged(self, idx): if idx != 0 and idx != -1: self.reclassificationDict = self.configFromDbDict[self.configFromDbComboBox.currentText()] self.populateWindow() if idx == 0: self.reclassificationDict = {} self.createButtonsWithTabs(self.reclassificationDict)
class ValidationToolbox(QtGui.QDockWidget, FORM_CLASS): def __init__(self, iface): """ Constructor """ super(ValidationToolbox, self).__init__() # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.edgvLayer = None self.flagLyr = None self.iface = iface self.databaseLineEdit.setReadOnly(True) self.configWindow = ValidationConfig() self.configWindow.widget.connectionChanged.connect( self.updateDbLineEdit) self.validationManager = None self.tableView.setContextMenuPolicy(Qt.CustomContextMenu) self.tableView.customContextMenuRequested.connect( self.createContextMenu) self.ruleEnforcer = None self.attributeRulesEditorPushButton.hide() self.itemList = [] self.filterDict = { self.tr('Process Name'): DsgEnums.ProcessName, self.tr('Class Name'): DsgEnums.ClassName } self.processChanged = False # for processes filtering classes mechanics def createContextMenu(self, position): """ Creates the flag menu """ menu = QMenu() item = self.tableView.indexAt(position) if item: menu.addAction(self.tr('Zoom to flag'), self.zoomToFlag) menu.addAction(self.tr('Remove flag'), self.removeCurrentFlag) # menu.addAction(self.tr('Set Visited'), self.setFlagVisited) # menu.addAction(self.tr('Set Unvisited'), self.setFlagUnvisited) menu.exec_(self.tableView.viewport().mapToGlobal(position)) def removeCurrentFlag(self): """ Creates the remove flag menu """ try: flagId = self.tableView.selectionModel().selection().indexes( )[0].data() except: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('No flags were selected!')) return self.configWindow.widget.abstractDb.deleteProcessFlags(flagId=flagId) self.refreshFlags() @pyqtSlot() def on_theSelectionModel_selectionChanged(self): """ To do. """ print 'mudou' def setFlagVisited(self): """ To do """ print 'visited' def setFlagUnvisited(self): """ To do """ print 'unvisited' def zoomToFlag(self): """ Zooms the map canvas to the current selected flag """ try: idx = self.tableView.selectionModel().selection().indexes( )[0].data() except: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('No flags were selected!')) return dimension = self.tableView.selectionModel().selection().indexes( )[6].data() if dimension == 0: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOINT', 'lyrName': 'flags_validacao_p', 'tableName': 'aux_flags_validacao_p', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 1: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTILINESTRING', 'lyrName': 'flags_validacao_l', 'tableName': 'aux_flags_validacao_l', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 2: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOLYGON', 'lyrName': 'flags_validacao_a', 'tableName': 'aux_flags_validacao_a', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } flagLyr = self.loadFlagLyr(layer) flagLyr.setLayerTransparency(50) flagLyr.removeSelection() self.iface.mapCanvas().refresh() flagLyr.select(idx) bbox = flagLyr.boundingBoxOfSelected() mapbox = self.iface.mapCanvas().mapSettings().layerToMapCoordinates( flagLyr, bbox) self.iface.mapCanvas().setExtent(mapbox) self.iface.mapCanvas().refresh() def loadAllFlagLayers(self): """ Loads all flags """ pointLayer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOINT', 'lyrName': 'flags_validacao_p', 'tableName': 'aux_flags_validacao_p', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } lineLayer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTILINESTRING', 'lyrName': 'flags_validacao_l', 'tableName': 'aux_flags_validacao_l', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } areaLayer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOLYGON', 'lyrName': 'flags_validacao_a', 'tableName': 'aux_flags_validacao_a', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } self.loadFlagLyr([pointLayer, lineLayer, areaLayer]) def loadFlagLyr(self, layer): """ Loads the flag layer. It checks if the flag layer is already loaded, case not, it loads the flag layer into the TOC layer: layer name """ self.layerLoader = LayerLoaderFactory().makeLoader( self.iface, self.configWindow.widget.abstractDb) if isinstance(layer, list): return self.layerLoader.load(layer, uniqueLoad=True) else: return self.layerLoader.load([layer], uniqueLoad=True)[layer['lyrName']] def checkFlagsLoaded(self, layer): """ Checks if the flag layer is already loaded layer: layer name """ loadedLayers = self.iface.mapCanvas().layers() candidateLyrs = [] for lyr in loadedLayers: if lyr.name() == layer: candidateLyrs.append(lyr) for lyr in candidateLyrs: if self.configWindow.widget.abstractDb.isLyrInDb(lyr): return True return False @pyqtSlot(bool) def on_openDbPushButton_clicked(self): """ Opend dialog for database connection """ self.configWindow.show() @pyqtSlot(bool) def on_historyButton_clicked(self): """ Shows the validation history """ historyWindow = ValidationHistory(self.configWindow.widget.abstractDb) historyWindow.exec_() @pyqtSlot() def updateDbLineEdit(self): """ Updates the database information """ database = '' try: self.configWindow.widget.abstractDb.checkAndOpenDb() database = self.configWindow.widget.comboBoxPostgis.currentText() self.databaseLineEdit.setText(database) self.validationManager = ValidationManager( self.configWindow.widget.abstractDb, self.iface) self.populateProcessList() self.databaseLineEdit.setText(database) # adjusting flags table model self.projectModel = QSqlTableModel( None, self.configWindow.widget.abstractDb.db) self.projectModel.setTable('validation.aux_flags_validacao') self.projectModel.select() self.tableView.setModel(self.projectModel) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage( self.tr('Error loading db: ') + ':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.processTreeWidget.clear() def on_filterLineEdit_textChanged(self, text): for i in self.itemList: if text.lower() in i.text(1).lower(): i.setHidden(False) else: i.setHidden(True) def populateProcessList(self): """ Populates the process list. It also checks the status of each available process """ self.processTreeWidget.clear() self.edgvLayer = None self.flagLyr = None self.itemList = [] rootItem = self.processTreeWidget.invisibleRootItem() procList = sorted(self.validationManager.processDict) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) for i in range(len(procList)): item = QtGui.QTreeWidgetItem(rootItem) item.setText(0, str(i + 1)) item.setText(1, procList[i]) status = None try: status = self.configWindow.widget.abstractDb.getValidationStatusText( self.validationManager.processDict[procList[i]]) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) status = 'Error! Check log!' if not status: item.setText(2, 'Not yet ran') else: item.setText(2, status) self.itemList.append(item) for i in range(3): self.processTreeWidget.resizeColumnToContents(i) self.filterLineEdit.clear() QApplication.restoreOverrideCursor() @pyqtSlot(bool) def on_reRunButton_clicked(self): """ Re-runs last process with the same attributes. """ if not self.validationManager: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Select a database to run process!')) return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: procReturn = self.validationManager.runLastProcess() except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) procReturn = 0 QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor() self.populateProcessList() if procReturn == 0: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Process error. Check log for details.')) elif procReturn == -1: QtGui.QMessageBox.information(self, self.tr('Information!'), self.tr('Process canceled by user!')) elif procReturn == -2: QtGui.QMessageBox.information( self, self.tr('Information!'), self.tr('No previous process run this session.')) else: QtGui.QMessageBox.warning( self, self.tr('Success!'), self.tr('Process successfully executed!')) #executou! show! @pyqtSlot(bool) def on_runButton_clicked(self): """ Runs the current selected process """ selectedItems = self.processTreeWidget.selectedItems() if len(selectedItems) == 0: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Select a process to run!')) return processName = selectedItems[0].text(1) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: procReturn = self.validationManager.executeProcessV2(processName) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) procReturn = 0 QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor() self.populateProcessList() if procReturn == 0: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Process error. Check log for details.')) elif procReturn == -1: QtGui.QMessageBox.information(self, self.tr('Information!'), self.tr('Process canceled by user!')) else: QtGui.QMessageBox.warning( self, self.tr('Success!'), self.tr('Process successfully executed!')) #executou! show! #load flag layers self.loadAllFlagLayers() self.iface.mapCanvas().refresh( ) #atualizar canvas para mostrar resultado para o usuário @pyqtSlot(int, name='on_validationTabWidget_currentChanged') def refreshFlags(self): """ Changes the current tab in the validation tool box """ if self.validationTabWidget.currentIndex( ) == 1 and self.configWindow.widget.abstractDb != None: self.projectModel.setTable('validation.filtered_flags') self.projectModel.select() self.configWindow.widget.abstractDb.createFilteredFlagsViewTable() # populates the comboBoxes self.classFilterComboBox.clear() self.processFilterComboBox.clear() if self.configWindow.widget.abstractDb: listProcesses = self.configWindow.widget.abstractDb.fillComboBoxProcessOrClasses( "process") listClasses = self.configWindow.widget.abstractDb.fillComboBoxProcessOrClasses( "class") self.classFilterComboBox.addItems(listClasses) self.processFilterComboBox.addItems(listProcesses) @pyqtSlot(bool) def on_rulesEditorButton_clicked(self): """ Opens the spatial rule editor """ try: self.configWindow.widget.abstractDb.checkAndOpenDb() dlg = RulesEditor(self.configWindow.widget.abstractDb) dlg.exec_() except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Database not loaded or a problem occurred.\n') + ':'.join(e.args)) @pyqtSlot(int, name='on_processFilterComboBox_currentIndexChanged') def refreshFlagListOnProcessSelection(self): """ Refreshs the list of processes available for filtering the view """ # marking as a change of process change for on_classFilterComboBox_currentIndexChanged self.processChanged = True className = self.classFilterComboBox.currentText() processName = self.processFilterComboBox.currentText() # classes should be repopulated for filtering purposes # clear classes available self.classFilterComboBox.clear() listClasses = self.configWindow.widget.abstractDb.fillComboBoxProcessOrClasses( "class", filteringProcess=processName) # repopulate classes available for selected class self.classFilterComboBox.addItems(listClasses) # getting text index for resetting after repopulation idx = max(0, self.classFilterComboBox.findText(className)) self.classFilterComboBox.setCurrentIndex(idx) className = self.classFilterComboBox.currentText() self.configWindow.widget.abstractDb.createFilteredFlagsViewTable( className=className, processName=processName) self.projectModel.select() @pyqtSlot(int, name='on_classFilterComboBox_currentIndexChanged') def refreshFlagListOnClassSelection(self): """ Refreshs the list of classes available for filtering the view """ if self.processChanged: # if index change is due to class change, the table update # is set by process changing signal. Nothing to be done here. self.processChanged = (not self.processChanged) return className = self.classFilterComboBox.currentText() processName = self.processFilterComboBox.currentText() self.configWindow.widget.abstractDb.createFilteredFlagsViewTable( className=className, processName=processName) self.projectModel.select() @pyqtSlot(bool) def on_ruleEnforcerRadio_toggled(self, checked): """ Toggles the spatial rule enforcer """ if checked: self.ruleEnforcer = SpatialRuleEnforcer( self.validationManager.postgisDb, self.iface) self.ruleEnforcer.connectEditingSignals() self.ruleEnforcer.ruleTested.connect(self.refreshFlags) else: self.ruleEnforcer.disconnectEditingSignals() self.ruleEnforcer.ruleTested.disconnect(self.refreshFlags) @pyqtSlot(bool) def on_attributeRulesEditorPushButton_clicked(self): """ Opens the attribute rule editor """ try: self.configWindow.widget.abstractDb.checkAndOpenDb() dlg = AttributeRulesEditor(self.configWindow.widget.abstractDb) dlg.exec_() except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Database not loaded or a problem occurred.\n') + ':'.join(e.args)) @pyqtSlot(bool) def on_clearAllPushButton_clicked(self): """ Deletes all flags from validation.aux_flags 1- Get abstractDb 2- Delete flag """ try: if QtGui.QMessageBox.question( self, self.tr('Question'), self.tr('Do you really want to clear all flags?'), QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel: return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.configWindow.widget.abstractDb.deleteProcessFlags() QApplication.restoreOverrideCursor() #refresh self.refreshFlags() self.iface.mapCanvas().refresh() except Exception as e: QApplication.restoreOverrideCursor() QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Flags not deleted.\n') + ':'.join(e.args)) @pyqtSlot(bool) def on_clearSelectedPushButton_clicked(self): """ Deletes selected flags on the panel from validation.aux_flags """ try: if QtGui.QMessageBox.question( self, self.tr('Question'), self.tr('Do you really want to clear those flags?'), QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel: return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # check what is filtered processName = self.processFilterComboBox.currentText() layerName = self.classFilterComboBox.currentText() if (processName or layerName): self.configWindow.widget.abstractDb.deleteProcessFlags( processName, layerName) else: QApplication.restoreOverrideCursor() QtGui.QMessageBox.critical( self, self.tr('Critical!'), self. tr('Flags not deleted as no Process nor Class was chosen.\n' )) return QApplication.restoreOverrideCursor() # refresh View Table with lasting flags self.refreshFlags() self.iface.mapCanvas().refresh() except Exception as e: QApplication.restoreOverrideCursor() QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Flags not deleted.\n') + ':'.join(e.args))
class ValidationToolbox(QtGui.QDockWidget, FORM_CLASS): def __init__(self, iface): """ Constructor """ super(ValidationToolbox, self).__init__() # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.edgvLayer = None self.flagLyr = None self.iface = iface self.databaseLineEdit.setReadOnly(True) self.configWindow = ValidationConfig() self.configWindow.widget.connectionChanged.connect( self.updateDbLineEdit) self.validationManager = None self.tableView.setContextMenuPolicy(Qt.CustomContextMenu) self.tableView.customContextMenuRequested.connect( self.createMenuEditFlagStatus) self.ruleEnforcer = None self.attributeRulesEditorPushButton.hide() self.itemList = [] def createMenuEditFlagStatus(self, position): """ Creates the flag menu """ menu = QMenu() item = self.tableView.indexAt(position) if item: menu.addAction(self.tr('Zoom to flag'), self.zoomToFlag) # menu.addAction(self.tr('Set Visited'), self.setFlagVisited) # menu.addAction(self.tr('Set Unvisited'), self.setFlagUnvisited) menu.exec_(self.tableView.viewport().mapToGlobal(position)) @pyqtSlot() def on_theSelectionModel_selectionChanged(self): """ To do. """ print 'mudou' def setFlagVisited(self): """ To do """ print 'visited' def setFlagUnvisited(self): """ To do """ print 'unvisited' def zoomToFlag(self): """ Zooms the map canvas to the current selected flag """ idx = self.tableView.selectionModel().selection().indexes()[0].data() dimension = self.tableView.selectionModel().selection().indexes( )[6].data() if dimension == 0: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOINT', 'lyrName': 'flags_validacao_p', 'tableName': 'aux_flags_validacao_p', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 1: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTILINESTRING', 'lyrName': 'flags_validacao_l', 'tableName': 'aux_flags_validacao_l', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } elif dimension == 2: layer = { 'cat': 'aux', 'geom': 'geom', 'geomType': 'MULTIPOLYGON', 'lyrName': 'flags_validacao_a', 'tableName': 'aux_flags_validacao_a', 'tableSchema': 'validation', 'tableType': 'BASE TABLE' } flagLyr = self.loadFlagLyr(layer) flagLyr.setLayerTransparency(50) flagLyr.removeSelection() self.iface.mapCanvas().refresh() flagLyr.select(idx) bbox = flagLyr.boundingBoxOfSelected() mapbox = self.iface.mapCanvas().mapSettings().layerToMapCoordinates( flagLyr, bbox) self.iface.mapCanvas().setExtent(mapbox) self.iface.mapCanvas().refresh() def loadFlagLyr(self, layer): """ Loads the flag layer. It checks if the flag layer is already loaded, case not, it loads the flag layer into the TOC layer: layer name """ self.layerLoader = LayerLoaderFactory().makeLoader( self.iface, self.configWindow.widget.abstractDb) return self.layerLoader.load([layer], uniqueLoad=True)[layer['lyrName']] def checkFlagsLoaded(self, layer): """ Checks if the flag layer is already loaded layer: layer name """ loadedLayers = self.iface.mapCanvas().layers() candidateLyrs = [] for lyr in loadedLayers: if lyr.name() == layer: candidateLyrs.append(lyr) for lyr in candidateLyrs: if self.configWindow.widget.abstractDb.isLyrInDb(lyr): return True return False @pyqtSlot(bool) def on_openDbPushButton_clicked(self): """ Opend dialog for database connection """ self.configWindow.show() @pyqtSlot(bool) def on_historyButton_clicked(self): """ Shows the validation history """ historyWindow = ValidationHistory(self.configWindow.widget.abstractDb) historyWindow.exec_() @pyqtSlot() def updateDbLineEdit(self): """ Updates the database information """ database = '' try: self.configWindow.widget.abstractDb.checkAndOpenDb() database = self.configWindow.widget.comboBoxPostgis.currentText() self.databaseLineEdit.setText(database) self.validationManager = ValidationManager( self.configWindow.widget.abstractDb, self.iface) self.populateProcessList() self.databaseLineEdit.setText(database) # adjusting flags table model self.projectModel = QSqlTableModel( None, self.configWindow.widget.abstractDb.db) self.projectModel.setTable('validation.aux_flags_validacao') self.projectModel.select() self.tableView.setModel(self.projectModel) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage( self.tr('Error loading db: ') + ':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.processTreeWidget.clear() def on_filterLineEdit_textChanged(self, text): for i in self.itemList: if text.lower() in i.text(1).lower(): i.setHidden(False) else: i.setHidden(True) def populateProcessList(self): """ Populates the process list. It also checks the status of each available process """ self.processTreeWidget.clear() self.edgvLayer = None self.flagLyr = None self.itemList = [] rootItem = self.processTreeWidget.invisibleRootItem() procList = sorted(self.validationManager.processDict) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) for i in range(len(procList)): item = QtGui.QTreeWidgetItem(rootItem) item.setText(0, str(i + 1)) item.setText(1, procList[i]) status = None try: status = self.configWindow.widget.abstractDb.getValidationStatusText( self.validationManager.processDict[procList[i]]) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) status = 'Error! Check log!' if not status: item.setText(2, 'Not yet ran') else: item.setText(2, status) self.itemList.append(item) for i in range(3): self.processTreeWidget.resizeColumnToContents(i) self.filterLineEdit.clear() QApplication.restoreOverrideCursor() @pyqtSlot(bool) def on_runButton_clicked(self): """ Runs the current selected process """ selectedItems = self.processTreeWidget.selectedItems() if len(selectedItems) == 0: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Select a process to run!')) return processName = selectedItems[0].text(1) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: procReturn = self.validationManager.executeProcess(processName) except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('A problem occurred! Check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) procReturn = 0 QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor() self.populateProcessList() if procReturn == 0: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Process error. Check log for details.')) elif procReturn == -1: QtGui.QMessageBox.information(self, self.tr('Information!'), self.tr('Process canceled by user!')) else: QtGui.QMessageBox.warning( self, self.tr('Success!'), self.tr('Process successfully executed!')) #executou! show! @pyqtSlot(int, name='on_validationTabWidget_currentChanged') def refreshFlags(self): """ Changes the current tab in the validation tool box """ if self.validationTabWidget.currentIndex( ) == 1 and self.configWindow.widget.abstractDb <> None: self.projectModel.select() @pyqtSlot(bool) def on_rulesEditorButton_clicked(self): """ Opens the spatial rule editor """ try: self.configWindow.widget.abstractDb.checkAndOpenDb() dlg = RulesEditor(self.configWindow.widget.abstractDb) dlg.exec_() except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Database not loaded or a problem occurred.\n') + ':'.join(e.args)) @pyqtSlot(bool) def on_ruleEnforcerRadio_toggled(self, checked): """ Toggles the spatial rule enforcer """ if checked: self.ruleEnforcer = SpatialRuleEnforcer( self.validationManager.postgisDb, self.iface) self.ruleEnforcer.connectEditingSignals() self.ruleEnforcer.ruleTested.connect(self.refreshFlags) else: self.ruleEnforcer.disconnectEditingSignals() self.ruleEnforcer.ruleTested.disconnect(self.refreshFlags) @pyqtSlot(bool) def on_attributeRulesEditorPushButton_clicked(self): """ Opens the attribute rule editor """ try: self.configWindow.widget.abstractDb.checkAndOpenDb() dlg = AttributeRulesEditor(self.configWindow.widget.abstractDb) dlg.exec_() except Exception as e: QtGui.QMessageBox.critical( self, self.tr('Critical!'), self.tr('Database not loaded or a problem occurred.\n') + ':'.join(e.args))
class LoadAuxStruct(QtGui.QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """ Constructor """ super(self.__class__, self).__init__(parent) self.setupUi(self) self.iface = iface self.layerFactory = LayerLoaderFactory() self.selectedClasses = [] self.widget.tabWidget.setTabEnabled(0,True) self.widget.tabWidget.setTabEnabled(1,False) self.widget.tabWidget.setCurrentIndex(0) self.bar = QgsMessageBar() self.setLayout(QtGui.QGridLayout(self)) self.layout().setContentsMargins(0,0,0,0) self.layout().setAlignment(QtCore.Qt.AlignTop) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) self.bar.setSizePolicy(sizePolicy) self.layout().addWidget(self.bar, 0,0,1,1) QtCore.QObject.connect(self.widget, QtCore.SIGNAL(("problemOccurred()")), self.pushMessage) self.widget.dbChanged.connect(self.widgetConv.setDatabase) @pyqtSlot(bool) def on_pushButtonCancel_clicked(self): """ Closes the dialog """ self.close() def pushMessage(self, msg): """ Pushes a message into message bar """ self.bar.pushMessage("", msg, level=QgsMessageBar.CRITICAL) @pyqtSlot(bool) def on_pushButtonOk_clicked(self): """ Checks the linee-centroid structure and loads the correspondent layers """ QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) if self.widgetConv.settingDict == dict(): QApplication.restoreOverrideCursor() self.bar.pushMessage(self.tr("Error!"), self.tr("Could not load auxiliary classes! Check log for details!"), level=QgsMessageBar.CRITICAL) else: self.loadLayers() QApplication.restoreOverrideCursor() self.close() def loadLayers(self): """ Loads the layers defined in the line-centroid structure """ try: if self.widget.abstractDb.getDatabaseVersion() == 'Non_EDGV': isEdgv = False else: isEdgv = True auxClassesDict = self.widgetConv.settingDict['earthCoverageDict'] auxClasses = [] for key in auxClassesDict.keys(): for cl in auxClassesDict[key]: if cl not in auxClasses: if '.' in cl: classToLoad = cl.split('.')[-1] else: classToLoad = cl auxClasses.append(classToLoad) auxCentroids = self.widgetConv.abstractDb.getEarthCoverageCentroids() auxClasses = auxClasses + auxCentroids auxClasses.sort(reverse=True) auxClasses = [self.widgetConv.settingDict['frameLayer'].split('.')[-1]]+auxClasses factory = self.layerFactory.makeLoader(self.iface, self.widget.abstractDb, loadCentroids=True) factory.load(auxClasses, uniqueLoad = True, isEdgv = isEdgv) except Exception as e: QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.bar.pushMessage(self.tr("Error!"), self.tr("Could not load auxiliary classes! Check log for details!"), level=QgsMessageBar.CRITICAL)
class FieldToolbox(QtGui.QDockWidget, FORM_CLASS): def __init__(self, iface, parent = None): """Constructor.""" super(self.__class__, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.iface = iface self.buttons = [] self.prevLayer = None self.buttonName = '' self.category = '' self.widget.dbChanged.connect(self.defineFactory) self.releaseButtonConected = False self.addedFeatures = [] self.configFromDbDict = dict() def defineFactory(self, abstractDb): """ Defines the layer loader by its type :param abstractDb: :return: """ self.layerLoader = LayerLoaderFactory().makeLoader(self.iface, abstractDb) try: self.populateConfigFromDb() except Exception as e: QgsMessageLog.logMessage(self.tr('Error getting stored configuration.\n')+':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) def setEditButtonEnabled(self, enabled): """ Edits the current configuration settings :param enabled: :return: """ self.editCurrentConfigButton.setEnabled(enabled) @pyqtSlot(bool, name='on_setupButton_clicked') @pyqtSlot(bool, name='on_editCurrentConfigButton_clicked') def populateWindow(self): """ Creates the buttons according to the field setup """ if self.widget.abstractDb == None: QtGui.QMessageBox.critical(self, self.tr('Error!'), self.tr('First select a database!')) return if isinstance(self.sender(), QPushButton): sender = self.sender().text() else: sender = '' dlg = FieldSetup(self.widget.abstractDb) if sender != self.tr('Setup'): dlg.loadReclassificationConf(self.reclassificationDict) if sender != '': result = dlg.exec_() else: result = 1 if result == 1: self.createButtonsOnInterface(dlg) self.setEditButtonEnabled(True) def createButtonsOnInterface(self, dlg): """ Creates button according to what is set in the configuration :param dlg: :return: """ #reclassification dictionary made from the field setup file self.reclassificationDict = dlg.makeReclassificationDict() #button size defined by the user self.size = dlg.slider.value() #check if the button must be grouped by category withTabs = dlg.checkBox.isChecked() #actual button creation step self.createButtons(self.reclassificationDict, withTabs) @pyqtSlot(bool, name = 'on_newFeatureRadioButton_toggled') def turnButtonsOn(self, enabled): """ Adjusts tool behavior. The default state makes the tool work with selected features but the user can choose to acquire a feature in real time. When working in real time the buttons must be checkable. """ if enabled: #connecting iface signals self.iface.currentLayerChanged.connect(self.acquire) for button in self.buttons: #disconnecting the clicked signal button.clicked.disconnect(self.reclassify) #changing button behavior button.setCheckable(True) else: #disconnecting iface signals self.disconnectLayerSignals() try:self.iface.currentLayerChanged.disconnect(self.acquire) except:pass for button in self.buttons: #connecting the clicked signal button.clicked.connect(self.reclassify) #changing button behavior button.setCheckable(False) def createWidgetWithoutTabs(self, formLayout): """ Adjusts the scroll area to receive the buttons directly (not grouped by category) formLayout: Layout used to receive all the buttons """ w = QtGui.QWidget() w.setLayout(formLayout) self.scrollArea.setWidget(w) def createWidgetWithTabs(self, formLayout): """ Creates a scroll area for each form layout. formLayout: Layout used to receive the buttons in each tab """ scrollArea = QtGui.QScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setFrameShape(QtGui.QFrame.Shape(0)) # no frame w = QtGui.QWidget() w.setLayout(formLayout) scrollArea.setWidget(w) return scrollArea def on_filterLineEdit_textChanged(self, text): for i in self.buttons: if text.lower() in i.text().lower(): i.show() else: i.hide() def createButton(self, button, propertyDict = dict()): """ Creates the buttons according to the user size definition button: Button name propertyDict: optional dict parameters that may contain other properties to button, such as color, tooltip and custom category """ pushButton = QtGui.QPushButton(button) keys = propertyDict.keys() styleSheet = '' if 'buttonColor' in keys: r, g, b, a = propertyDict['buttonColor'].split(',') styleSheet += "background-color:rgba({0},{1},{2},{3});".format(r, g, b, a) if 'buttonToolTip' in keys: pushButton.setToolTip(propertyDict['buttonToolTip']) if 'buttonShortcut' in keys: keySequence = QKeySequence(propertyDict['buttonShortcut']) pushButton.setText('{0} [{1}]'.format(button, keySequence.toString(format = QKeySequence.NativeText))) pushButton.setShortcut(keySequence) pushButton.clicked.connect(self.reclassify) pushButton.toggled.connect(self.acquire) if self.size == 0: pushButton.setMinimumSize(100, 25) styleSheet += 'font-size:12px;' elif self.size == 1: pushButton.setMinimumSize(100, 40) styleSheet += 'font-size:20px;' elif self.size == 2: pushButton.setMinimumSize(100, 80) styleSheet += 'font-size:30px;' pushButton.setStyleSheet(styleSheet) self.buttons.append(pushButton) return pushButton def createButtons(self, reclassificationDict, createTabs=False): """ Convenience method to create buttons createTabs: Indicates if the buttons must be created within tabs """ self.buttons = [] widget = self.scrollArea.takeWidget() if createTabs: self.createButtonsWithTabs(reclassificationDict) else: self.createButtonsWithoutTabs(reclassificationDict) self.turnButtonsOn(self.newFeatureRadioButton.isChecked()) def createButtonsWithoutTabs(self, reclassificationDict): """ Specific method to create buttons without tabs reclassificationDict: dictionary used to create the buttons """ formLayout = QtGui.QFormLayout() self.createWidgetWithoutTabs(formLayout) sortedButtonNames = [] propertyDict = dict() for category in reclassificationDict.keys(): if category in ['version', 'uiParameterJsonDict']: continue for edgvClass in reclassificationDict[category].keys(): for button in reclassificationDict[category][edgvClass].keys(): item = reclassificationDict[category][edgvClass][button] propertyDict[button] = dict() if isinstance(item, dict): if 'buttonProp' in item.keys(): propertyDict[button] = item['buttonProp'] sortedButtonNames.append(button) sortedButtonNames.sort() for button in sortedButtonNames: pushButton = self.createButton(button, propertyDict = propertyDict[button]) formLayout.addRow(pushButton) def createButtonsWithTabs(self, reclassificationDict): """ Specific method to create buttons with tabs reclassificationDict: dictionary used to create the buttons """ gridLayout = QtGui.QGridLayout() tabWidget = QtGui.QTabWidget() tabWidget.setTabPosition(QtGui.QTabWidget.West) gridLayout.addWidget(tabWidget) self.scrollArea.setWidget(tabWidget) propertyDict = dict() for category in reclassificationDict.keys(): if category in ['version', 'uiParameterJsonDict']: continue sortedButtonNames = [] formLayout = QtGui.QFormLayout() scrollArea = self.createWidgetWithTabs(formLayout) tabWidget.addTab(scrollArea, category) for edgvClass in reclassificationDict[category].keys(): for button in reclassificationDict[category][edgvClass].keys(): item = reclassificationDict[category][edgvClass][button] propertyDict[button] = dict() if isinstance(item, dict): if 'buttonProp' in item.keys(): propertyDict[button] = item['buttonProp'] sortedButtonNames.append(button) sortedButtonNames.sort() for button in sortedButtonNames: pushButton = self.createButton(button, propertyDict = propertyDict[button]) formLayout.addRow(pushButton) def loadLayer(self, layer): """ Loads the layer used in the actual reclassification layer: Layer name """ try: return self.layerLoader.load([layer], uniqueLoad=True)[layer] except Exception as e: QtGui.QMessageBox.critical(self, self.tr('Error!'), self.tr('Could not load the selected classes!\n')+':'.join(e.args)) def checkConditions(self): """ Check the conditions to see if the tool can be used """ if not self.widget.abstractDb: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Please, select a database.')) return False try: version = self.widget.abstractDb.getDatabaseVersion() except Exception as e: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Problem obtaining database version! Please, check log for details.')) QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) return False if 'version' not in self.reclassificationDict.keys(): QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('File not formated propperly.')) return False if self.reclassificationDict['version'] != version: QtGui.QMessageBox.critical(self, self.tr('Critical!'), self.tr('Database version does not match the field toolbox version.')) return False return True def getLayerFromButton(self, buttonText): """ Gets the correct layer to be used in the tool """ #edgvClass found in the dictionary (this is made using the sqlite seed) button = buttonText.split(' [')[0] (category, edgvClass) = self.findReclassificationClass(button) driverName = self.widget.abstractDb.getType() if driverName == "QSQLITE": #reclassification layer name reclassificationClass = '_'.join(edgvClass.split('_')[1::]) if driverName == "QPSQL": #reclassification layer name reclassificationClass = '_'.join(edgvClass.split('.')[1::]) #getting the QgsVectorLayer to perform the reclassification reclassificationLayer = self.loadLayer(reclassificationClass) if reclassificationLayer: self.iface.setActiveLayer(reclassificationLayer) #entering in editing mode if not reclassificationLayer.isEditable(): reclassificationLayer.startEditing() lyrAttributes = [i.name() for i in reclassificationLayer.pendingFields()] for attr in self.reclassificationDict[category][edgvClass][button].keys(): if attr == 'buttonProp': continue candidateDict = self.reclassificationDict[category][edgvClass][button][attr] if isinstance(candidateDict, dict): if candidateDict['isEditable'] == '0' and attr in lyrAttributes: attrIdx = lyrAttributes.index(attr) reclassificationLayer.setFieldEditable(attrIdx, False) return (reclassificationLayer, category, edgvClass) @pyqtSlot(int) def setAttributesFromButton(self, featureId): """ Sets the attributes for the newly added feature featureId: added feature """ layer = self.sender() if isinstance(layer, QgsVectorLayer): layer.beginEditCommand(self.tr('DsgTools reclassification')) self.addedFeatures.append(featureId) def updateAttributesAfterAdding(self): """ Updates feature attributes according to the button configuration :return: """ layer = self.sender() while self.addedFeatures: featureId = self.addedFeatures.pop() #begining the edit command # layer.beginEditCommand(self.tr("DSG Tools reclassification tool: adjusting feature's attributes")) #accessing added features editBuffer = layer.editBuffer() features = editBuffer.addedFeatures() for key in features.keys(): #just checking the newly added feature, the other I don't care if key == featureId: feature = features[key] #setting the attributes using the reclassification dictionary self.setFeatureAttributes(feature, editBuffer) layer.endEditCommand() def setFeatureAttributes(self, newFeature, editBuffer=None, oldFeat = None): """ Changes attribute values according to the reclassification dict using the edit buffer newFeature: newly added editBuffer: layer edit buffer """ #setting the attributes using the reclassification dictionary for attribute in self.reclassificationDict[self.category][self.edgvClass][self.buttonName].keys(): if attribute == 'buttonProp': continue idx = newFeature.fieldNameIndex(attribute) #value to be changed reclass = self.reclassificationDict[self.category][self.edgvClass][self.buttonName][attribute] if isinstance(reclass, dict): value = reclass['value'] if reclass['isIgnored'] == '1': #ignore clause if oldFeat: value = oldFeat[attribute] else: value = reclass if value == '': continue #actual attribute change if editBuffer: #this way we are working with the edit buffer editBuffer.changeAttributeValue(newFeature.id(), idx, value) else: #this way are working with selected features and inserting a new one in the layer newFeature.setAttribute(idx, value) if not editBuffer: # we should return when under the normal behavior return newFeature def disconnectLayerSignals(self): """ Disconnecting the signals from the previous layer """ if self.prevLayer: try: self.prevLayer.featureAdded.disconnect(self.setAttributesFromButton) self.prevLayer.editCommandEnded.disconnect(self.updateAttributesAfterAdding) self.prevLayer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOff) except: pass @pyqtSlot(bool) def acquire(self, pressed): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes. The difference here is the use of real time editing to make the reclassification """ if pressed: if not self.checkConditions(): return #getting the object that sent the signal sender = self.sender() #if the sender is the iface object, this means that the user made the click and changed the current layer #when this happens we should untoggle all buttons if isinstance(sender, QgisInterface): #checking if another button is checked for button in self.buttons: button.setChecked(False) #return and do nothing else return #button that sent the signal self.buttonName = sender.text().split(' [')[0] #checking if another button is checked for button in self.buttons: if button.text().split(' [')[0] != self.buttonName and button.isChecked(): button.setChecked(False) #disconnecting the previous layer self.disconnectLayerSignals() (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) #suppressing the form dialog if 'buttonProp' in self.reclassificationDict[self.category][self.edgvClass][self.buttonName].keys() and \ 'openForm' in self.reclassificationDict[self.category][self.edgvClass][self.buttonName]['buttonProp'].keys(): reclassificationLayer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOff) else: reclassificationLayer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOn) #connecting addedFeature signal reclassificationLayer.featureAdded.connect(self.setAttributesFromButton) reclassificationLayer.editCommandEnded.connect(self.updateAttributesAfterAdding) #triggering the add feature tool self.iface.actionAddFeature().trigger() #setting the previous layer self.prevLayer = reclassificationLayer else: #disconnecting the previous layer self.disconnectLayerSignals() @pyqtSlot() def reclassify(self): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes """ if not self.checkConditions(): return somethingMade = False reclassifiedFeatures = 0 #button that sent the signal self.buttonName = self.sender().text().split(' [')[0] (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) geomType = reclassificationLayer.geometryType() hasMValues = QgsWKBTypes.hasM(int(reclassificationLayer.wkbType())) #generic check (not every database is implemented as ours) hasZValues = QgsWKBTypes.hasZ(int(reclassificationLayer.wkbType())) # isMulti = QgsWKBTypes.isMultiType(int(reclassificationLayer.wkbType())) # mapLayers = self.iface.mapCanvas().layers() #we need to get the authid that thefines the ref system of destination layer crsSrc = QgsCoordinateReferenceSystem(reclassificationLayer.crs().authid()) deleteList = [] for mapLayer in mapLayers: if mapLayer.type() != QgsMapLayer.VectorLayer: continue #iterating over selected features featList = [] mapLayerCrs = mapLayer.crs() #creating a coordinate transformer (mapLayerCrs to crsSrc) coordinateTransformer = QgsCoordinateTransform(mapLayerCrs, crsSrc) for feature in mapLayer.selectedFeatures(): geomList = [] geom = feature.geometry() if geom.type() != geomType: continue if 'geometry' in dir(geom): if not hasMValues: geom.geometry().dropMValue() if not hasZValues: geom.geometry().dropZValue() if isMulti and not geom.isMultipart(): geom.convertToMultiType() geomList.append(geom) if not isMulti and geom.isMultipart(): #deaggregate here parts = geom.asGeometryCollection() for part in parts: part.convertToSingleType() geomList.append(part) else: geomList.append(geom) for newGeom in geomList: #creating a new feature according to the reclassification layer newFeature = QgsFeature(reclassificationLayer.pendingFields()) #transforming the geometry to the correct crs geom.transform(coordinateTransformer) #setting the geometry newFeature.setGeometry(newGeom) #setting the attributes using the reclassification dictionary newFeature = self.setFeatureAttributes(newFeature, oldFeat = feature) #adding the newly created feature to the addition list featList.append(newFeature) somethingMade = True deleteList.append({'originalLyr':mapLayer,'featid':feature.id()}) #actual feature insertion reclassificationLayer.addFeatures(featList, False) reclassifiedFeatures += len(featList) for item in deleteList: item['originalLyr'].startEditing() item['originalLyr'].deleteFeature(item['featid']) if somethingMade: self.iface.messageBar().pushMessage(self.tr('Information!'), self.tr('{} features reclassified with success!').format(reclassifiedFeatures), level=QgsMessageBar.INFO, duration=3) def findReclassificationClass(self, button): """ Finds the reclassification class according to the button button: Button clicked by the user to perform the reclassification """ for category in self.reclassificationDict.keys(): if category == 'version' or category == 'uiParameterJsonDict': continue for edgvClass in self.reclassificationDict[category].keys(): for buttonName in self.reclassificationDict[category][edgvClass].keys(): if button == buttonName: #returning the desired edgvClass return (category, edgvClass) return () def populateConfigFromDb(self): """ Populates configFromDbComboBox with config from public.field_toolbox_config """ driverName = self.widget.abstractDb.getType() self.configFromDbComboBox.clear() self.configFromDbComboBox.addItem(self.tr('Select Stored Config (optional)')) if driverName == 'QPSQL': self.configFromDbComboBox.setEnabled(True) propertyDict = self.widget.abstractDb.getPropertyDict('FieldToolBoxConfig') dbVersion = self.widget.abstractDb.getDatabaseVersion() if dbVersion in propertyDict.keys(): self.configFromDbDict = propertyDict[dbVersion] nameList = self.configFromDbDict.keys() nameList.sort() for name in nameList: self.configFromDbComboBox.addItem(name) else: self.configFromDbComboBox.setEnabled(False) self.configFromDbDict = dict() @pyqtSlot(int) def on_configFromDbComboBox_currentIndexChanged(self, idx): if idx != 0 and idx != -1: self.reclassificationDict = self.configFromDbDict[self.configFromDbComboBox.currentText()] self.populateWindow() if idx == 0: self.reclassificationDict = {} self.createButtonsWithTabs(self.reclassificationDict)