def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression}
def _buildfromlayer(self, widget, layerconfig): layername = layerconfig['layer'] keyfield = layerconfig['key'] valuefield = layerconfig['value'] filterexp = layerconfig.get('filter', None) try: layer = QgsMapLayerRegistry.instance().mapLayersByName(layername)[0] except IndexError: roam.utils.warning("Can't find layer {} in project".format(layername)) return keyfieldindex = layer.fieldNameIndex(keyfield) valuefieldindex = layer.fieldNameIndex(valuefield) if keyfieldindex == -1 or valuefieldindex == -1: roam.utils.warning("Can't find key or value column") return if not filterexp and valuefieldindex == keyfieldindex: values = layer.uniqueValues(keyfieldindex) for value in values: widget.addItem(value, value) return attributes = {keyfieldindex, valuefieldindex} flags = QgsFeatureRequest.NoGeometry expression = None if filterexp: expression = QgsExpression(filterexp) expression.prepare(layer.pendingFields()) if expression.hasParserError(): roam.utils.warning("Expression has parser error: {}".format(expression.parserErrorString())) return if expression.needsGeometry(): flags = QgsFeatureRequest.NoFlags for field in expression.referencedColumns(): index = layer.fieldNameIndex(field) attributes.add(index) values = set() request = QgsFeatureRequest().setFlags(flags).setSubsetOfAttributes(list(attributes)) for feature in layer.getFeatures(request): if expression and not expression.evaluate(feature): continue widget.addItem(feature[keyfieldindex], feature[valuefield]) if self.allownulls: widget.insertItem(0, '(no selection)', None)
def calculate( self, layer, fieldName, expression ): if ( layer.featureCount() == 0 ): self.msg.show( "[Info] * No existing features on layer " + layer.name() + " to calculate expression.", 'info', True ) return expression = QgsExpression( expression ) if expression.hasParserError(): self.msg.show( QApplication.translate( "AutoFields-FieldCalculator", "[Error] (Parsing) " ) + \ expression.parserErrorString(), 'critical' ) return context = QgsExpressionContext() context.appendScope( QgsExpressionContextUtils.globalScope() ) context.appendScope( QgsExpressionContextUtils.projectScope() ) context.appendScope( QgsExpressionContextUtils.layerScope( layer ) ) context.setFields( layer.fields() ) if expression.needsGeometry(): if self.iface: # This block was borrowed from QGIS/python/plugins/processing/algs/qgis/FieldsCalculator.py da = QgsDistanceArea() da.setSourceCrs( layer.crs().srsid() ) da.setEllipsoidalMode( self.iface.mapCanvas().mapSettings().hasCrsTransformEnabled() ) da.setEllipsoid( QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE )[0] ) expression.setGeomCalculator( da ) if QGis.QGIS_VERSION_INT >= 21400: # Methods added in QGIS 2.14 expression.setDistanceUnits( QgsProject.instance().distanceUnits() ) expression.setAreaUnits( QgsProject.instance().areaUnits() ) expression.prepare( context ) fieldIndex = layer.fieldNameIndex( fieldName ) if fieldIndex == -1: return field = layer.fields()[fieldIndex] dictResults = {} for feature in layer.getFeatures(): context.setFeature( feature ) result = expression.evaluate( context ) if expression.hasEvalError(): self.msg.show( QApplication.translate( "AutoFields-FieldCalculator", "[Error] (Evaluating) " ) + \ expression.evalErrorString(), 'critical' ) return dictResults[feature.id()] = { fieldIndex: field.convertCompatible( result ) } layer.dataProvider().changeAttributeValues( dictResults ) self.msg.show( "[Info] * An expression was calculated on existing features of layer " + layer.name() + ", field " + fieldName + ".", 'info', True )
def _buildfromlayer(self, widget, layerconfig): layername = layerconfig['layer'] keyfield = layerconfig['key'] valuefield = layerconfig['value'] filterexp = layerconfig.get('filter', None) try: layer = QgsMapLayerRegistry.instance().mapLayersByName( layername)[0] except IndexError: roam.utils.warning( "Can't find layer {} in project".format(layername)) return keyfieldindex = layer.fieldNameIndex(keyfield) valuefieldindex = layer.fieldNameIndex(valuefield) if keyfieldindex == -1 or valuefieldindex == -1: roam.utils.warning("Can't find key or value column") return if self.allownulls: item = QStandardItem('(no selection)') item.setData(None, Qt.UserRole) self.listmodel.appendRow(item) attributes = {keyfieldindex, valuefieldindex} iconfieldindex = layer.fieldNameIndex('icon') if iconfieldindex > -1: attributes.add(iconfieldindex) if not filterexp and valuefieldindex == keyfieldindex and iconfieldindex == -1: values = layer.uniqueValues(keyfieldindex) for value in values: value = nullconvert(value) item = QStandardItem(value) item.setData(value, Qt.UserRole) self.listmodel.appendRow(item) return flags = QgsFeatureRequest.NoGeometry expression = None if filterexp: expression = QgsExpression(filterexp) expression.prepare(layer.pendingFields()) if expression.hasParserError(): roam.utils.warning("Expression has parser error: {}".format( expression.parserErrorString())) return if expression.needsGeometry(): flags = QgsFeatureRequest.NoFlags for field in expression.referencedColumns(): index = layer.fieldNameIndex(field) attributes.add(index) request = QgsFeatureRequest().setFlags(flags).setSubsetOfAttributes( list(attributes)) for feature in layer.getFeatures(request): if expression and not expression.evaluate(feature): continue keyvalue = nullconvert(feature[keyfieldindex]) valuvalue = nullconvert(feature[valuefield]) try: path = feature[iconfieldindex] icon = QIcon(path) except KeyError: icon = QIcon() item = QStandardItem(unicode(keyvalue)) item.setData(unicode(valuvalue), Qt.UserRole) item.setIcon(icon) self.listmodel.appendRow(item)
def _buildfromlayer(self, widget, layerconfig): layername = layerconfig['layer'] keyfield = layerconfig['key'] valuefield = layerconfig['value'] filterexp = layerconfig.get('filter', None) try: layer = QgsMapLayerRegistry.instance().mapLayersByName(layername)[0] except IndexError: roam.utils.warning("Can't find layer {} in project".format(layername)) return keyfieldindex = layer.fieldNameIndex(keyfield) valuefieldindex = layer.fieldNameIndex(valuefield) if keyfieldindex == -1 or valuefieldindex == -1: roam.utils.warning("Can't find key or value column") return if self.allownulls: item = QStandardItem('(no selection)') item.setData(None, Qt.UserRole) self.listmodel.appendRow(item) attributes = {keyfieldindex, valuefieldindex} iconfieldindex = layer.fieldNameIndex('icon') if iconfieldindex > -1: attributes.add(iconfieldindex) if not filterexp and valuefieldindex == keyfieldindex and iconfieldindex == -1: values = layer.uniqueValues(keyfieldindex) for value in values: value = nullconvert(value) item = QStandardItem(value) item.setData(value, Qt.UserRole) self.listmodel.appendRow(item) return flags = QgsFeatureRequest.NoGeometry expression = None if filterexp: expression = QgsExpression(filterexp) expression.prepare(layer.pendingFields()) if expression.hasParserError(): roam.utils.warning("Expression has parser error: {}".format(expression.parserErrorString())) return if expression.needsGeometry(): flags = QgsFeatureRequest.NoFlags for field in expression.referencedColumns(): index = layer.fieldNameIndex(field) attributes.add(index) request = QgsFeatureRequest().setFlags(flags).setSubsetOfAttributes(list(attributes)) for feature in layer.getFeatures(request): if expression and not expression.evaluate(feature): continue keyvalue = nullconvert(feature[keyfieldindex]) valuvalue = nullconvert(feature[valuefield]) try: path = feature[iconfieldindex] icon = QIcon(path) except KeyError: icon = QIcon() item = QStandardItem(unicode(keyvalue)) item.setData(unicode(valuvalue), Qt.UserRole) item.setIcon(icon) self.listmodel.appendRow(item)
def expressionBasedUpdate( self, layer, dictProperties, featureId, index=None, value=None ): """ Defines the logic of the expression-based update to be applied. This SLOT listens to featureAdded, geometryChanged, and attributeValueChanged SIGNALS. """ # Check if AutoField is there, otherwise return fieldIndex = layer.fieldNameIndex( dictProperties['field'] ) if fieldIndex == -1: self.msg.show( QApplication.translate( "EventManager", "[Error] Updating AutoField " ) + \ dictProperties['field'] + \ QApplication.translate( "EventManager", " in layer " ) + \ layer.name() + QApplication.translate( "EventManager", " was NOT possible." ) + \ QApplication.translate( "EventManager", " Perhaps you just removed it but haven't saved the changes yet?" ), 'warning' ) return event = "" result = None expression = QgsExpression( dictProperties['expression'] ) if expression.hasParserError(): self.msg.show( QApplication.translate( "EventManager", "[Error] (Parsing) " ) + \ expression.parserErrorString(), 'critical' ) result = NULL # Avoid infinite recursion (changing the same attribute value infinitely). if not index is None: # Filters out the featureAdded SIGNAL if type( index ) == int: # Filters out the geometryChanged SIGNAL if index == fieldIndex: # This call comes from the same AutoField, so return return if self.afm.isFieldAnAutoField( layer, layer.fields()[index].name() ): # Call from AutoField, don't listen # This is to prevent corrupting the layerEditBuffer and being bitten by: # Fatal: ASSERT: "mChangedAttributeValues.isEmpty()" in file /tmp/buildd/qgis-2.14.2+20trusty/src/core/qgsvectorlayereditbuffer.cpp, line 585 return #if type(value)==QPyNullVariant: # Vector layers with numeric field whose value for 1st feature is NULL # trigger an attributeValueChanged SIGNAL when start editing from the # attribute table window. We use this conditional to avoid such SIGNAL. # The ideal case is that such NULL valued SIGNAL shouldn't be emitted by QGIS. # return # While the previous block reduces the number of times attributeValueChanged # is called from the attribute table, it leads to a QGIS bug: # Fatal: ASSERT: "mChangedAttributeValues.isEmpty()" in file /tmp/buildd/qgis-2.14.2+20trusty/src/core/qgsvectorlayereditbuffer.cpp, line 585 # I prefer the attributeValueChanged to be called multiple # times (inefficient) than to open the possibility to a bug. # As soon as QGIS bug #15272 is solved, the number of calls will be reduced! event = "attributeValueChanged" else: event = "geometryChanged" else: event = "featureAdded" feature = layer.getFeatures( QgsFeatureRequest( featureId ) ).next() if result is None: context = QgsExpressionContext() context.appendScope( QgsExpressionContextUtils.globalScope() ) context.appendScope( QgsExpressionContextUtils.projectScope() ) context.appendScope( QgsExpressionContextUtils.layerScope( layer ) ) context.setFields( feature.fields() ) context.setFeature( feature ) if expression.needsGeometry(): if self.iface: # This block was borrowed from QGIS/python/plugins/processing/algs/qgis/FieldsCalculator.py da = QgsDistanceArea() da.setSourceCrs( layer.crs().srsid() ) da.setEllipsoidalMode( self.iface.mapCanvas().mapSettings().hasCrsTransformEnabled() ) da.setEllipsoid( QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE )[0] ) expression.setGeomCalculator( da ) if QGis.QGIS_VERSION_INT >= 21400: # Methods added in QGIS 2.14 expression.setDistanceUnits( QgsProject.instance().distanceUnits() ) expression.setAreaUnits( QgsProject.instance().areaUnits() ) expression.prepare( context ) result = expression.evaluate( context ) if expression.hasEvalError(): self.msg.show( QApplication.translate( "EventManager", "[Error] (Evaluating) " ) + \ expression.evalErrorString(), 'critical' ) result = NULL field = layer.fields()[fieldIndex] res = field.convertCompatible( result ) # If result is None, res will be None, but even in that case, QGIS knows # what to do with it while saving, it seems it's treated as NULL. # TODO when bug #15311 is fixed, this block should work better #if dictProperties['expression'] in self.listProviderExpressions: # # Save directly to provider # layer.dataProvider().changeAttributeValues( { featureId : { fieldIndex : res } } ) #else: # Save to layer # layer.changeAttributeValue( featureId, fieldIndex, res ) # Workaround if event == 'featureAdded': # Save directly to the provider layer.dataProvider().changeAttributeValues( { featureId : { fieldIndex : res } } ) else: # Save to layer layer.changeAttributeValue( featureId, fieldIndex, res ) self.msg.show( "[Info] * AutoField's value updated to " + unicode(res) + \ ", (" + layer.name() + "." + dictProperties['field'] + ") by " + event +".", 'info', True )
def expressionBasedUpdate(self, layer, dictProperties, featureId, index=None, value=None): """ Defines the logic of the expression-based update to be applied. This SLOT listens to featureAdded, geometryChanged, and attributeValueChanged SIGNALS. """ # Check if AutoField is there, otherwise return fieldIndex = layer.fieldNameIndex(dictProperties['field']) if fieldIndex == -1: self.msg.show( QApplication.translate( "EventManager", "[Error] Updating AutoField " ) + \ dictProperties['field'] + \ QApplication.translate( "EventManager", " in layer " ) + \ layer.name() + QApplication.translate( "EventManager", " was NOT possible." ) + \ QApplication.translate( "EventManager", " Perhaps you just removed it but haven't saved the changes yet?" ), 'warning' ) return event = "" result = None expression = QgsExpression(dictProperties['expression']) if expression.hasParserError(): self.msg.show( QApplication.translate( "EventManager", "[Error] (Parsing) " ) + \ expression.parserErrorString(), 'critical' ) result = NULL # Avoid infinite recursion (changing the same attribute value infinitely). if not index is None: # Filters out the featureAdded SIGNAL if type(index) == int: # Filters out the geometryChanged SIGNAL if index == fieldIndex: # This call comes from the same AutoField, so return return if self.afm.isFieldAnAutoField( layer, layer.fields() [index].name()): # Call from AutoField, don't listen # This is to prevent corrupting the layerEditBuffer and being bitten by: # Fatal: ASSERT: "mChangedAttributeValues.isEmpty()" in file /tmp/buildd/qgis-2.14.2+20trusty/src/core/qgsvectorlayereditbuffer.cpp, line 585 return #if type(value)==QPyNullVariant: # Vector layers with numeric field whose value for 1st feature is NULL # trigger an attributeValueChanged SIGNAL when start editing from the # attribute table window. We use this conditional to avoid such SIGNAL. # The ideal case is that such NULL valued SIGNAL shouldn't be emitted by QGIS. # return # While the previous block reduces the number of times attributeValueChanged # is called from the attribute table, it leads to a QGIS bug: # Fatal: ASSERT: "mChangedAttributeValues.isEmpty()" in file /tmp/buildd/qgis-2.14.2+20trusty/src/core/qgsvectorlayereditbuffer.cpp, line 585 # I prefer the attributeValueChanged to be called multiple # times (inefficient) than to open the possibility to a bug. # As soon as QGIS bug #15272 is solved, the number of calls will be reduced! event = "attributeValueChanged" else: event = "geometryChanged" else: event = "featureAdded" feature = layer.getFeatures(QgsFeatureRequest(featureId)).next() if result is None: context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) context.appendScope(QgsExpressionContextUtils.layerScope(layer)) context.setFields(feature.fields()) context.setFeature(feature) if expression.needsGeometry(): if self.iface: # This block was borrowed from QGIS/python/plugins/processing/algs/qgis/FieldsCalculator.py da = QgsDistanceArea() da.setSourceCrs(layer.crs().srsid()) da.setEllipsoidalMode(self.iface.mapCanvas().mapSettings(). hasCrsTransformEnabled()) da.setEllipsoid(QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE)[0]) expression.setGeomCalculator(da) if QGis.QGIS_VERSION_INT >= 21400: # Methods added in QGIS 2.14 expression.setDistanceUnits( QgsProject.instance().distanceUnits()) expression.setAreaUnits( QgsProject.instance().areaUnits()) expression.prepare(context) result = expression.evaluate(context) if expression.hasEvalError(): self.msg.show( QApplication.translate( "EventManager", "[Error] (Evaluating) " ) + \ expression.evalErrorString(), 'critical' ) result = NULL field = layer.fields()[fieldIndex] res = field.convertCompatible(result) # If result is None, res will be None, but even in that case, QGIS knows # what to do with it while saving, it seems it's treated as NULL. # TODO when bug #15311 is fixed, this block should work better #if dictProperties['expression'] in self.listProviderExpressions: # # Save directly to provider # layer.dataProvider().changeAttributeValues( { featureId : { fieldIndex : res } } ) #else: # Save to layer # layer.changeAttributeValue( featureId, fieldIndex, res ) # Workaround if event == 'featureAdded': # Save directly to the provider layer.dataProvider().changeAttributeValues( {featureId: { fieldIndex: res }}) else: # Save to layer layer.changeAttributeValue(featureId, fieldIndex, res) self.msg.show( "[Info] * AutoField's value updated to " + unicode(res) + \ ", (" + layer.name() + "." + dictProperties['field'] + ") by " + event +".", 'info', True )