Example #1
0
    def processAlgorithm(self, progress):
        ns = {}
        ns["progress"] = progress
        ns["scriptDescriptionFile"] = self.descriptionFile

        for param in self.parameters:
            ns[param.name] = param.value

        for out in self.outputs:
            ns[out.name] = out.value

        variables = re.findall("@[a-zA-Z0-9_]*", self.script)
        script = "import processing\n"
        script += self.script

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope())
        for var in variables:
            varname = var[1:]
            if context.hasVariable(varname):
                script = script.replace(var, context.variable(varname))
            else:
                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, "Cannot find variable: %s" % varname)

        exec((script), ns)
        for out in self.outputs:
            out.setValue(ns[out.name])
Example #2
0
    def processAlgorithm(self, parameters, context, feedback):
        ns = {}
        ns['feedback'] = feedback
        ns['scriptDescriptionFile'] = self.descriptionFile
        ns['context'] = context

        for param in self.parameterDefinitions():
            ns[param.name] = parameters[param.name()]

        for out in self.outputs:
            ns[out.name] = out.value

        variables = re.findall('@[a-zA-Z0-9_]*', self.script)
        script = 'import processing\n'
        script += self.script

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
        for var in variables:
            varname = var[1:]
            if context.hasVariable(varname):
                script = script.replace(var, context.variable(varname))
            else:
                QgsMessageLog.logMessage(self.tr('Cannot find variable: {0}').format(varname), self.tr('Processing'), QgsMessageLog.WARNING)

        exec((script), ns)
        for out in self.outputs:
            out.setValue(ns[out.name])
Example #3
0
    def processAlgorithm(self, feedback):
        ns = {}
        ns['feedback'] = feedback
        ns['scriptDescriptionFile'] = self.descriptionFile

        for param in self.parameters:
            ns[param.name] = param.value

        for out in self.outputs:
            ns[out.name] = out.value

        variables = re.findall('@[a-zA-Z0-9_]*', self.script)
        script = 'import processing\n'
        script += self.script

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
        for var in variables:
            varname = var[1:]
            if context.hasVariable(varname):
                script = script.replace(var, context.variable(varname))
            else:
                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Cannot find variable: %s' % varname)

        exec((script), ns)
        for out in self.outputs:
            out.setValue(ns[out.name])
Example #4
0
 def update_variables_gui(self):
     variables_scope = QgsExpressionContextScope(self.tr('Model Variables'))
     for k, v in self.model.variables().items():
         variables_scope.setVariable(k, v)
     variables_context = QgsExpressionContext()
     variables_context.appendScope(variables_scope)
     self.variables_editor.setContext(variables_context)
     self.variables_editor.setEditableScopeIndex(0)
Example #5
0
    def check_for_update_events(self, widget, value):
        if not self.feature:
            return

        from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextScope
        # If we don't have any events for this widgets just get out now
        if not widget.id in self.events:
            return

        events = self.events[widget.id]
        events = [event for event in events if event['event'].lower() == 'update']
        if not events:
            return

        feature = self.to_feature(no_defaults=True)

        for event in events:
            action = event['action'].lower()
            targetid = event['target']
            if targetid == widget.id:
                utils.log("Can't connect events to the same widget. ID {}".format(targetid))
                continue

            widget = self.get_widget_from_id(targetid)

            if not widget:
                utils.log("Can't find widget for id {} in form".format(targetid))
                continue

            condition = event['condition']
            expression = event['value']

            context = QgsExpressionContext()
            scope = QgsExpressionContextScope()
            scope.setVariable("value", value)
            scope.setVariable("field", widget.field)
            context.setFeature(feature)
            context.appendScope(scope)

            conditionexp = QgsExpression(condition)
            exp = QgsExpression(expression)

            if action.lower() == "show":
                widget.hidden = not conditionexp.evaluate(context)
            if action.lower() == "hide":
                widget.hidden = conditionexp.evaluate(context)
            if action == 'widget expression':
                if conditionexp.evaluate(context):
                    newvalue = self.widget_default(field, feature=feature)
                    widget.setvalue(newvalue)
            if action == 'set value':
                if conditionexp.evaluate(context):
                    newvalue = exp.evaluate(context)
                    widget.setvalue(newvalue)
Example #6
0
 def evaluateExpression(self, text):
     context = QgsExpressionContext()
     context.appendScope(QgsExpressionContextUtils.globalScope())
     context.appendScope(QgsExpressionContextUtils.projectScope())
     exp = QgsExpression(text)
     if exp.hasParserError():
         raise Exception(exp.parserErrorString())
     result = exp.evaluate(context)
     if exp.hasEvalError():
         raise ValueError(exp.evalErrorString())
     return result
Example #7
0
    def runGetFeatureTests(self, source):
        FeatureSourceTestCase.runGetFeatureTests(self, source)

        # combination of an uncompilable expression and limit
        feature = next(self.vl.getFeatures('pk=4'))
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('parent', feature)
        context.appendScope(scope)

        request = QgsFeatureRequest()
        request.setExpressionContext(context)
        request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
        request.setLimit(1)

        values = [f['pk'] for f in self.vl.getFeatures(request)]
        self.assertEqual(values, [4])
    def test_length_expression(self):
        # compare length using the ellipsoid in kms and the planimetric distance in meters
        self.lyr.fieldName = "round($length,5) || ' - ' || round(length($geometry),2)"
        self.lyr.isExpression = True

        QgsProject.instance().setCrs(QgsCoordinateReferenceSystem("EPSG:32613"))
        QgsProject.instance().setEllipsoid("WGS84")
        QgsProject.instance().setDistanceUnits(QgsUnitTypes.DistanceKilometers)

        ctxt = QgsExpressionContext()
        ctxt.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
        ctxt.appendScope(QgsExpressionContextUtils.layerScope(self.layer))
        self._TestMapSettings.setExpressionContext(ctxt)

        self.lyr.placement = QgsPalLayerSettings.Curved
        self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.MapOrientation
        self.checkTest()
Example #9
0
def add25dAttributes(cleanLayer, layer, canvas):
    provider = cleanLayer.dataProvider()
    provider.addAttributes([QgsField("height", QVariant.Double),
                            QgsField("wallColor", QVariant.String),
                            QgsField("roofColor", QVariant.String)])
    cleanLayer.updateFields()
    fields = cleanLayer.fields()
    renderer = layer.renderer()
    renderContext = QgsRenderContext.fromMapSettings(canvas.mapSettings())
    feats = layer.getFeatures()
    context = QgsExpressionContext()
    context.appendScope(QgsExpressionContextUtils.layerScope(layer))
    expression = QgsExpression('eval(@qgis_25d_height)')
    heightField = fields.indexFromName("height")
    wallField = fields.indexFromName("wallColor")
    roofField = fields.indexFromName("roofColor")
    renderer.startRender(renderContext, fields)
    cleanLayer.startEditing()
    for feat in feats:
        context.setFeature(feat)
        height = expression.evaluate(context)
        if isinstance(renderer, QgsCategorizedSymbolRenderer):
            classAttribute = renderer.classAttribute()
            attrValue = feat.attribute(classAttribute)
            catIndex = renderer.categoryIndexForValue(attrValue)
            categories = renderer.categories()
            symbol = categories[catIndex].symbol()
        elif isinstance(renderer, QgsGraduatedSymbolRenderer):
            classAttribute = renderer.classAttribute()
            attrValue = feat.attribute(classAttribute)
            ranges = renderer.ranges()
            for range in ranges:
                if (attrValue >= range.lowerValue() and
                        attrValue <= range.upperValue()):
                    symbol = range.symbol().clone()
        else:
            symbol = renderer.symbolForFeature(feat, renderContext)
        sl1 = symbol.symbolLayer(1)
        sl2 = symbol.symbolLayer(2)
        wallColor = sl1.subSymbol().color().name()
        roofColor = sl2.subSymbol().color().name()
        provider.changeAttributeValues({feat.id() + 1: {heightField: height,
                                                        wallField: wallColor,
                                                        roofField: roofColor}})
    cleanLayer.commitChanges()
    renderer.stopRender(renderContext)
Example #10
0
class PyFeatureSource(QgsAbstractFeatureSource):

    def __init__(self, provider):
        super(PyFeatureSource, self).__init__()
        self._provider = provider
        self._features = provider._features

        self._expression_context = QgsExpressionContext()
        self._expression_context.appendScope(QgsExpressionContextUtils.globalScope())
        self._expression_context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
        self._expression_context.setFields(self._provider.fields())
        if self._provider.subsetString():
            self._subset_expression = QgsExpression(self._provider.subsetString())
            self._subset_expression.prepare(self._expression_context)
        else:
            self._subset_expression = None

    def getFeatures(self, request):
        return QgsFeatureIterator(PyFeatureIterator(self, request))
    def processAlgorithm(self, progress):
        layer = layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))

        expression = self.getParameterValue(self.EXPRESSION)
        qExp = QgsExpression(expression)
        if qExp.hasParserError():
            raise GeoAlgorithmExecutionException(qExp.parserErrorString())

        writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
            layer.fields(), layer.wkbType(), layer.crs())

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope())
        context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        count = layer.featureCount()
        step = 100.0 / count if count else 1

        request = QgsFeatureRequest(qExp, context)

        for current, f in enumerate(layer.getFeatures(request)):
            writer.addFeature(f)
            progress.setPercentage(int(current * step))

        del writer
Example #12
0
    def processAlgorithm(self, progress):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_LAYER))

        geometry_type = self.getParameterValue(self.OUTPUT_GEOMETRY)
        wkb_type = None
        if geometry_type == 0:
            wkb_type = QgsWkbTypes.Polygon
        elif geometry_type == 1:
            wkb_type = QgsWkbTypes.LineString
        else:
            wkb_type = QgsWkbTypes.Point
        if self.getParameterValue(self.WITH_Z):
            wkb_type = QgsWkbTypes.addZ(wkb_type)
        if self.getParameterValue(self.WITH_M):
            wkb_type = QgsWkbTypes.addM(wkb_type)

        writer = self.getOutputFromName(
            self.OUTPUT_LAYER).getVectorWriter(
                layer.fields(),
                wkb_type,
                layer.crs())

        expression = QgsExpression(self.getParameterValue(self.EXPRESSION))
        if expression.hasParserError():
            raise GeoAlgorithmExecutionException(expression.parserErrorString())

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        if not expression.prepare(exp_context):
            raise GeoAlgorithmExecutionException(
                self.tr('Evaluation error: %s' % expression.evalErrorString()))

        features = vector.features(layer)
        total = 100.0 / len(features)
        for current, input_feature in enumerate(features):
            output_feature = input_feature

            exp_context.setFeature(input_feature)
            value = expression.evaluate(exp_context)
            if expression.hasEvalError():
                raise GeoAlgorithmExecutionException(
                    self.tr('Evaluation error: %s' % expression.evalErrorString()))

            if not value:
                output_feature.setGeometry(QgsGeometry())
            else:
                if not isinstance(value, QgsGeometry):
                    raise GeoAlgorithmExecutionException(
                        self.tr('{} is not a geometry').format(value))
                output_feature.setGeometry(value)

            writer.addFeature(output_feature)
            progress.setPercentage(int(current * total))

        del writer
Example #13
0
def _expressionContext(alg):
    context = QgsExpressionContext()
    context.appendScope(QgsExpressionContextUtils.globalScope())
    context.appendScope(QgsExpressionContextUtils.projectScope())
    processingScope = QgsExpressionContextScope()
    for param in alg.parameters:
        processingScope.setVariable('%s_value' % param.name, '')
    context.appendScope(processingScope)
    return context
Example #14
0
 def expressionContext(self):
     context = QgsExpressionContext()
     context.appendScope(QgsExpressionContextUtils.globalScope())
     context.appendScope(QgsExpressionContextUtils.projectScope())
     processingScope = QgsExpressionContextScope()
     for param in self.alg.parameters:
         processingScope.setVariable("%s_value" % param.name, "")
     context.appendScope(processingScope)
     return context
    def testVariables(self):
        """ check through widget model to ensure it is populated with variables """
        w = QgsExpressionBuilderWidget()
        m = w.model()

        s = QgsExpressionContextScope()
        s.setVariable('my_var1', 'x')
        s.setVariable('my_var2', 'y')
        c = QgsExpressionContext()
        c.appendScope(s)

        # check that variables are added when setting context
        w.setExpressionContext(c)
        items = m.findItems('my_var1', Qt.MatchRecursive)
        self.assertEqual(len(items), 1)
        items = m.findItems('my_var2', Qt.MatchRecursive)
        self.assertEqual(len(items), 1)
        items = m.findItems('not_my_var', Qt.MatchRecursive)
        self.assertEqual(len(items), 0)
        # double check that functions are still only there once
        items = m.findItems('lower', Qt.MatchRecursive)
        self.assertEqual(len(items), 1)
        items = m.findItems('upper', Qt.MatchRecursive)
        self.assertEqual(len(items), 1)
Example #16
0
def createExpressionContext():
    context = QgsExpressionContext()
    context.appendScope(QgsExpressionContextUtils.globalScope())
    context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))

    if iface.mapCanvas():
        context.appendScope(QgsExpressionContextUtils.mapSettingsScope(iface.mapCanvas().mapSettings()))

    processingScope = QgsExpressionContextScope()

    extent = iface.mapCanvas().fullExtent()
    processingScope.setVariable('fullextent_minx', extent.xMinimum())
    processingScope.setVariable('fullextent_miny', extent.yMinimum())
    processingScope.setVariable('fullextent_maxx', extent.xMaximum())
    processingScope.setVariable('fullextent_maxy', extent.yMaximum())
    context.appendScope(processingScope)
    return context
Example #17
0
 def _layer_tooltip(self, layer, feat):
     try:
         df = layer.displayField()
         if df:
             return str(feat.attribute(df))
         else:
             context = QgsExpressionContext()
             context.appendScope(QgsExpressionContextUtils.globalScope())
             context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
             context.appendScope(QgsExpressionContextUtils.layerScope(layer))
             context.appendScope(QgsExpressionContextUtils.mapSettingsScope( self.canvas.mapSettings() ) )
             context.setFeature( feat )
             
             x = QgsExpression(layer.displayExpression())
             x.prepare(context)
             return x.evaluate(context).replace('\n', "<br/>")
     except:
         return ""
Example #18
0
    def pointPicked(self, event):
        for b in self.rubberBands:
            del b
        self.rubberBands[:] = []

        fieldX = self.cmbXField.currentField()
        fieldY = self.cmbYField.currentField()

        artist = event.artist
        indices = event.ind
        for i in indices:
            x = self.xData[artist.name][i]
            y = self.yData[artist.name][i]

            if isinstance(x, int):
                expr = '"{}" = {} AND '.format(fieldX, x)
            elif isinstance(x, float):
                expr = 'abs("{}" - {}) <= 0.0000001 AND '.format(fieldX, x)
            elif isinstance(x, (str, unicode)):
                expr = """"{}" = '{}' AND """.format(fieldX, x)
            else:
                expr = """"{}" = '{}' AND """.format(fieldX, x.toString('yyyy-MM-dd'))

            if isinstance(y, float):
                expr += 'abs("{}" - {}) <= 0.0000001'.format(fieldY, y)
            elif isinstance(y, (str, unicode)):
                expr += """"{}" = '{}'""".format(fieldY, y)
            else:
                expr += '"{}" = {}'.format(fieldY, y)

            layer = self.cmbLayer.currentLayer()
            expression = QgsExpression(expr)
            context = QgsExpressionContext()
            context.appendScope(QgsExpressionContextUtils.globalScope())
            context.appendScope(QgsExpressionContextUtils.projectScope())
            context.appendScope(QgsExpressionContextUtils.mapSettingsScope(self.canvas.mapSettings()))
            context.appendScope(QgsExpressionContextUtils.layerScope(layer))

            request = QgsFeatureRequest(expression, context)
            for f in layer.getFeatures(request):
                hl = QgsHighlight(self.canvas, f.geometry(), layer)
                hl.setColor(QColor(255, 0, 0))
                hl.setWidth(2)
                self.rubberBands.append(hl)
Example #19
0
    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 )
Example #20
0
    def doAction(self, layer, uid, feature):
        if layer.actions().action(uid).name() == 'openFeatureForm':
            self.plugin.iface.openFeatureForm(layer, feature)
        else :
            ctxt = QgsExpressionContext()
            ctxt.appendScope(QgsExpressionContextUtils.globalScope())
            ctxt.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
            ctxt.appendScope(QgsExpressionContextUtils.mapSettingsScope(self.canvas.mapSettings()))

            # Add click_x and click_y to context
            p = self.toLayerCoordinates(layer, self.pos())            
            QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'click_x', p.x())
            QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'click_y', p.y())

            layer.actions().doAction(uid, feature, ctxt)
    def updateLayer(self):
        self.layer = dataobjects.getObject(self.cmbInputLayer.currentText())

        self.builder.setLayer(self.layer)
        self.builder.loadFieldNames()

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(self.layer))
        exp_context.lastScope().setVariable("row_number", 1)
        exp_context.setHighlightedVariables(["row_number"])
        self.builder.setExpressionContext(exp_context)

        self.populateFields()
Example #22
0
    def expressionContext(self):
        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope())
        processingScope = QgsExpressionContextScope()
        layers = dataobjects.getAllLayers()
        for layer in layers:
            name = layer.name()
            processingScope.setVariable('%s_minx' % name, layer.extent().xMinimum())
            processingScope.setVariable('%s_miny' % name, layer.extent().yMinimum())
            processingScope.setVariable('%s_maxx' % name, layer.extent().xMaximum())
            processingScope.setVariable('%s_maxy' % name, layer.extent().yMaximum())
            if isinstance(layer, QgsRasterLayer):
                cellsize = (layer.extent().xMaximum()
                            - layer.extent().xMinimum()) / layer.width()
                processingScope.setVariable('%s_cellsize' % name, cellsize)

        layers = dataobjects.getRasterLayers()
        for layer in layers:
            for i in range(layer.bandCount()):
                stats = layer.dataProvider().bandStatistics(i + 1)
                processingScope.setVariable('%s_band%i_avg' % (name, i + 1), stats.mean)
                processingScope.setVariable('%s_band%i_stddev' % (name, i + 1), stats.stdDev)
                processingScope.setVariable('%s_band%i_min' % (name, i + 1), stats.minimumValue)
                processingScope.setVariable('%s_band%i_max' % (name, i + 1), stats.maximumValue)

        extent = iface.mapCanvas().extent()
        processingScope.setVariable('canvasextent_minx', extent.xMinimum())
        processingScope.setVariable('canvasextent_miny', extent.yMinimum())
        processingScope.setVariable('canvasextent_maxx', extent.xMaximum())
        processingScope.setVariable('canvasextent_maxy', extent.yMaximum())

        extent = iface.mapCanvas().fullExtent()
        processingScope.setVariable('fullextent_minx', extent.xMinimum())
        processingScope.setVariable('fullextent_miny', extent.yMinimum())
        processingScope.setVariable('fullextent_maxx', extent.xMaximum())
        processingScope.setVariable('fullextent_maxy', extent.yMaximum())
        context.appendScope(processingScope)
        return context
    def setCustomExpression( self ):
        """ Initialize and show the expression builder dialog """
        layer = None
        if len( self.tblLayers.selectedItems() ) / 3 == 1: # Single layer selected?
            for item in self.tblLayers.selectedItems():
                if item.column() == 1: # It's the layer name item
                    layer = QgsMapLayerRegistry.instance().mapLayer( item.data( Qt.UserRole ) )

        if not self.expressionDlg:
            self.expressionDlg = ExpressionBuilderDialog( self.iface.mainWindow() )
            context = QgsExpressionContext()
            context.appendScope( QgsExpressionContextUtils.globalScope() )
            context.appendScope( QgsExpressionContextUtils.projectScope() )

            # Initialize dialog with layer-based names and variables if single layer selected
            if len( self.tblLayers.selectedItems() ) / 3 == 1:
                context.appendScope( QgsExpressionContextUtils.layerScope( layer ) )
                self.expressionDlg.expressionBuilderWidget.setLayer( layer )
                self.expressionDlg.expressionBuilderWidget.loadFieldNames()

                # 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] )
                self.expressionDlg.expressionBuilderWidget.setGeomCalculator( da )

                # If this layer-field is an AutoField, get its expression
                if self.optExistingField.isChecked():
                    fieldName = self.cboField.currentText()
                    expression = self.autoFieldManager.getFieldExpression( layer, fieldName )
                    self.expressionDlg.expressionBuilderWidget.setExpressionText( expression )
                    self.expressionDlg.expression = expression # To remember it when closing/opening

            self.expressionDlg.expressionBuilderWidget.setExpressionContext( context )

        self.expressionDlg.show()
Example #24
0
    def processAlgorithm(self, progress):
        layer = self.getParameterValue(self.INPUT_LAYER)
        mapping = self.getParameterValue(self.FIELDS_MAPPING)
        output = self.getOutputFromName(self.OUTPUT_LAYER)

        layer = dataobjects.getObjectFromUri(layer)
        fields = []
        expressions = []

        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs().srsid())
        da.setEllipsoidalMode(
            iface.mapCanvas().mapSettings().hasCrsTransformEnabled())
        da.setEllipsoid(QgsProject.instance().readEntry(
            'Measure', '/Ellipsoid', GEO_NONE)[0])

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        for field_def in mapping:
            fields.append(QgsField(name=field_def['name'],
                                   type=field_def['type'],
                                   len=field_def['length'],
                                   prec=field_def['precision']))

            expression = QgsExpression(field_def['expression'])
            expression.setGeomCalculator(da)
            expression.setDistanceUnits(QgsProject.instance().distanceUnits())
            expression.setAreaUnits(QgsProject.instance().areaUnits())

            if expression.hasParserError():
                raise GeoAlgorithmExecutionException(
                    self.tr(u'Parser error in expression "{}": {}')
                    .format(str(field_def['expression']),
                            str(expression.parserErrorString())))
            expression.prepare(exp_context)
            if expression.hasEvalError():
                raise GeoAlgorithmExecutionException(
                    self.tr(u'Evaluation error in expression "{}": {}')
                    .format(str(field_def['expression']),
                            str(expression.evalErrorString())))
            expressions.append(expression)

        writer = output.getVectorWriter(fields,
                                        layer.wkbType(),
                                        layer.crs())

        # Create output vector layer with new attributes
        error = ''
        calculationSuccess = True
        inFeat = QgsFeature()
        outFeat = QgsFeature()
        features = vector.features(layer)
        total = 100.0 / len(features)
        for current, inFeat in enumerate(features):
            rownum = current + 1

            geometry = inFeat.geometry()
            outFeat.setGeometry(geometry)

            attrs = []
            for i in range(0, len(mapping)):
                field_def = mapping[i]
                expression = expressions[i]
                exp_context.setFeature(inFeat)
                exp_context.lastScope().setVariable("row_number", rownum)
                value = expression.evaluate(exp_context)
                if expression.hasEvalError():
                    calculationSuccess = False
                    error = expression.evalErrorString()
                    break

                attrs.append(value)
            outFeat.setAttributes(attrs)

            writer.addFeature(outFeat)

            progress.setPercentage(int(current * total))

        del writer

        if not calculationSuccess:
            raise GeoAlgorithmExecutionException(
                self.tr('An error occurred while evaluating the calculation'
                        ' string:\n') + error)
Example #25
0
    def runGetFeatureTests(self, provider):
        assert len([f for f in provider.getFeatures()]) == 5
        self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
        self.assert_query(provider, '"name" IS NULL', [5])
        self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
        self.assert_query(provider, 'name = \'Apple\'', [2])
        self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'name = \'apple\'', [])
        self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4])
        self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4])
        self.assert_query(provider, 'name LIKE \'Apple\'', [2])
        self.assert_query(provider, 'name LIKE \'aPple\'', [])
        self.assert_query(provider, 'name ILIKE \'aPple\'', [2])
        self.assert_query(provider, 'name ILIKE \'%pp%\'', [2])
        self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4])
        self.assert_query(provider, '-cnt > 0', [5])
        self.assert_query(provider, 'cnt < 0', [5])
        self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt <= 100', [1, 5])
        self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
        self.assert_query(provider, 'cnt = 50 * 2', [1])
        self.assert_query(provider, 'cnt = 99 + 1', [1])
        self.assert_query(provider, 'cnt = 101 - 1', [1])
        self.assert_query(provider, 'cnt - 1 = 99', [1])
        self.assert_query(provider, '-cnt - 1 = -101', [1])
        self.assert_query(provider, '-(-cnt) = 100', [1])
        self.assert_query(provider, '-(cnt) = -(100)', [1])
        self.assert_query(provider, 'cnt + 1 = 101', [1])
        self.assert_query(provider, 'cnt = 1100 % 1000', [1])
        self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
        self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
        self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '\'x\' || "name" IS NULL', [5])
        self.assert_query(provider, 'cnt = 10 ^ 2', [1])
        self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1])
        self.assert_query(provider, '"name"="name2"', [2, 4])  # mix of matched and non-matched case sensitive names
        self.assert_query(provider, 'true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false', [])

        # Three value logic
        self.assert_query(provider, 'false and false', [])
        self.assert_query(provider, 'false and true', [])
        self.assert_query(provider, 'false and NULL', [])
        self.assert_query(provider, 'true and false', [])
        self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true and NULL', [])
        self.assert_query(provider, 'NULL and false', [])
        self.assert_query(provider, 'NULL and true', [])
        self.assert_query(provider, 'NULL and NULL', [])
        self.assert_query(provider, 'false or false', [])
        self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false or NULL', [])
        self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or false', [])
        self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or NULL', [])
        self.assert_query(provider, 'not true', [])
        self.assert_query(provider, 'not false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'not null', [])

        # not
        self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4])
        self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3])
        self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5])

        # type conversion - QGIS expressions do not mind that we are comparing a string
        # against numeric literals
        self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5])

        # geometry
        self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])

        # combination of an uncompilable expression and limit
        feature = next(self.vl.getFeatures('pk=4'))
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('parent', feature)
        context.appendScope(scope)

        request = QgsFeatureRequest()
        request.setExpressionContext(context)
        request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
        request.setLimit(1)

        values = [f['pk'] for f in self.vl.getFeatures(request)]
        self.assertEqual(values, [4])
Example #26
0
class VectorPropertyReader:

  def __init__(self, objectTypeManager, renderContext, layer, properties):
    assert(properties is not None)
    self.renderContext = renderContext
    self.expressionContext = QgsExpressionContext()
    self.expressionContext.appendScope(QgsExpressionContextUtils.layerScope(layer))
    self.layer = layer
    properties = properties or {}
    self.properties = properties

    if properties:
      self.item_index = properties["comboBox_ObjectType"]
      typeitem = objectTypeManager.objectTypeItem(layer.geometryType(), self.item_index)  #
      self.type_name = typeitem.name
      self.mod_index = typeitem.mod_index
      self.type_index = typeitem.type_index
      self.visible = properties.get("visible", True)
    else:
      self.visible = False

    self._exprs = {}
    self.exprZ = QgsExpression(properties.get("fieldExpressionWidget_zCoordinate") or "0")

  def evaluateExpression(self, expr_str, f):
    if expr_str not in self._exprs:
      self._exprs[expr_str] = QgsExpression(expr_str)

    self.expressionContext.setFeature(f)
    return self._exprs[expr_str].evaluate(self.expressionContext)

  def readFillColor(self, vals, f):
    return self._readColor(vals, f)

  def readBorderColor(self, vals, f):
    return self._readColor(vals, f, isBorder=True)

  # read color from COLOR or OPTIONAL_COLOR widget
  def _readColor(self, widgetValues, f, isBorder=False):
    global colorNames

    mode = widgetValues["comboData"]
    if mode == OptionalColorWidgetFunc.NONE:
      return None

    if mode == ColorWidgetFunc.EXPRESSION:
      return self.evaluateExpression(widgetValues["editText"], f)

    if mode == ColorWidgetFunc.RANDOM or f is None:
      if len(colorNames) == 0:
        colorNames = QColor.colorNames()
      colorName = random.choice(colorNames)
      colorNames.remove(colorName)
      return QColor(colorName).name().replace("#", "0x")

    # feature color
    symbol = self.layer.renderer().symbolForFeature(f, self.renderContext)
    if symbol is None:
      logMessage('Symbol for feature cannot be found: {0}'.format(self.layer.name()))
      symbol = self.layer.renderer().symbols()[0]
    else:
      sl = symbol.symbolLayer(0)
      if sl and isBorder:
        return sl.strokeColor().name().replace("#", "0x")

      if sl and symbol.hasDataDefinedProperties():
        expr = sl.dataDefinedProperty("color")    #TODO: QGIS 3
        if expr:
          # data defined color
          rgb = expr.evaluate(f, f.fields())

          # "rrr,ggg,bbb" (dec) to "0xRRGGBB" (hex)
          r, g, b = [max(0, min(int(c), 255)) for c in rgb.split(",")[:3]]
          return "0x{0:02x}{1:02x}{2:02x}".format(r, g, b)

    return symbol.color().name().replace("#", "0x")

  def readOpacity(self, widgetValues, f):
    vals = widgetValues

    if vals["comboData"] == OpacityWidgetFunc.VALUE:
      try:
        val = self.evaluateExpression(widgetValues["editText"], f)
        return min(max(0, val), 100) / 100
      except ValueError:
        return 1

    alpha = None
    symbol = self.layer.renderer().symbolForFeature(f, self.renderContext)
    if symbol is None:
      logMessage('Symbol for feature cannot be found: {0}'.format(self.layer.name()))
      symbol = self.layer.renderer().symbols()[0]
    else:
      sl = symbol.symbolLayer(0)
      if sl and symbol.hasDataDefinedProperties():
        expr = sl.dataDefinedProperty("color")    #TODO: QGIS 3
        if expr:
          # data defined opacity
          cs_rgba = expr.evaluate(f, f.fields())
          rgba = cs_rgba.split(",")
          if len(rgba) == 4:
            alpha = rgba[3] / 255

    if alpha is None:
      alpha = 1         #TODO: QGIS 3 symbol.alpha()
                        # 'QgsMarkerSymbol' object has no attribute 'alpha'

    return self.layer.opacity() * alpha    # opacity = layer_opacity * feature_opacity

  @classmethod
  def toFloat(cls, val):
    try:
      return float(val)
    except Exception as e:
      logMessage('{0} (value: {1})'.format(e.message, str(val)))
      return 0

  # functions to read values from height widget (z coordinate)
  def useZ(self):
    return self.properties.get("radioButton_zValue", False)

  def useM(self):
    return self.properties.get("radioButton_mValue", False)

  def isHeightRelativeToDEM(self):
    return self.properties.get("radioButton_Relative", False)

  def relativeHeight(self):
    return self.exprZ.evaluate(self.expressionContext)

  def setContextFeature(self, f):
    self.expressionContext.setFeature(f)

  # read values from style widgets
  #TODO: rename this to styleValues
  def values(self, f):
    assert(f is not None)
    vals = []
    for i in range(32):   # big number for style count
      p = "styleWidget" + str(i)
      if p not in self.properties:
        break

      widgetValues = self.properties[p]
      if len(widgetValues) == 0:
        break

      widgetType = widgetValues["type"]
      comboData = widgetValues.get("comboData")
      if widgetType == StyleWidget.COLOR:
        vals.append(self.readFillColor(widgetValues, f))

      elif widgetType == StyleWidget.OPTIONAL_COLOR:
        vals.append(self.readBorderColor(widgetValues, f))

      elif widgetType == StyleWidget.COLOR_TEXTURE:
        if comboData == ColorTextureWidgetFunc.MAP_CANVAS:
          vals.append(comboData)
        elif comboData == ColorTextureWidgetFunc.LAYER:
          vals.append(widgetValues.get("layerIds", []))
        else:
          vals.append(self.readFillColor(widgetValues, f))

      elif widgetType == StyleWidget.OPACITY:
        vals.append(self.readOpacity(widgetValues, f))

      elif widgetType == StyleWidget.CHECKBOX:
        vals.append(widgetValues["checkBox"])

      elif widgetType == StyleWidget.HEIGHT:
        #TODO
        if widgetValues["comboData"] in [HeightWidgetFunc.RELATIVE, HeightWidgetFunc.ABSOLUTE, HeightWidgetFunc.Z_VALUE] or f is None:
          vals.append(self.toFloat(widgetValues["editText"]))
        else:
          # attribute value + addend
          fieldName = widgetValues["comboText"].lstrip("+").strip(' "')
          vals.append(self.toFloat(f.attribute(fieldName)) + self.toFloat(widgetValues["editText"]))

      else:
        expr = widgetValues["editText"]
        val = self.evaluateExpression(expr, f)
        if val is None:
          logMessage("Failed to evaluate expression: " + expr)
          if widgetType == StyleWidget.FILEPATH:
            val = ""
          else:
            val = 0
        vals.append(val)
    return vals
Example #27
0
    def replaceExpressionText(self, params: Dict[str, str],
                              response: QgsServerResponse,
                              project: QgsProject) -> None:
        """ Replace expression texts against layer or features
        In parameters:
            LAYER=wms-layer-name
            STRING=A string with expression between [% and %]
            or
            STRINGS=["first string with expression", "second string with expression"]
            or
            STRINGS={"key1": "first string with expression", "key2": "second string with expression"}
            // optionals
            FEATURE={"type": "Feature", "geometry": {}, "properties": {}}
            or
            FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}]
            FORM_SCOPE=boolean to add formScope based on provided features
        """
        layername = params.get('LAYER', '')
        if not layername:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'ReplaceExpressionText' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = findVectorLayer(layername, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'ReplaceExpressionText': {} provided"
                .format(layername), 400)

        # get strings
        strings = params.get('STRINGS', '')
        if not strings:
            the_string = params.get('STRING', '')
            if not the_string:
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid 'ReplaceExpressionText' REQUEST: STRING or STRINGS parameter is mandatory",
                    400)
            strings = '["{}"]'.format(the_string)

        # try to load expressions list or dict
        str_json = None
        try:
            str_json = json.loads(strings)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads strings '{}' exception:\n{}".format(
                    strings, traceback.format_exc()), "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'ReplaceExpressionText' REQUEST: STRINGS '{}' are not well formed"
                .format(strings), 400)

        # get features
        features = params.get('FEATURES', '')
        if not features:
            feature = params.get('FEATURE', '')
            if feature:
                features = '[' + feature + ']'

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # organized strings
        str_map = {}
        str_items = []
        if isinstance(str_json, list):
            str_items = enumerate(str_json)
        elif isinstance(str_json, dict):
            str_items = str_json.items()
        for k, s in str_items:
            str_map[k] = s

        # create the body
        body = {
            'status': 'success',
            'results': [],
            'errors': [],
            'features': 0
        }

        # without features just replace expression string with layer context
        if not features:
            result = {}
            for k, s in str_map.items():
                value = QgsExpression.replaceExpressionText(s, exp_context, da)
                result[k] = json.loads(QgsJsonUtils.encodeValue(value))
            body['results'].append(result)
            write_json_response(body, response)
            return

        # Check features
        try:
            geojson = json.loads(features)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads features '{}' exception:\n{}".format(
                    features, traceback.format_exc()), "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed"
                .format(features), 400)

        if not geojson or not isinstance(geojson, list) or len(geojson) == 0:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed"
                .format(features), 400)

        if ('type' not in geojson[0]) or geojson[0]['type'] != 'Feature':
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not Feature."
                .format(features), 400)

        # try to load features
        # read fields
        feature_fields = QgsJsonUtils.stringToFields(
            '{ "type": "FeatureCollection","features":' + features + '}',
            QTextCodec.codecForName("UTF-8"))
        # read features
        feature_list = QgsJsonUtils.stringToFeatureList(
            '{ "type": "FeatureCollection","features":' + features + '}',
            feature_fields, QTextCodec.codecForName("UTF-8"))

        # features not well formed
        if not feature_list:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FEATURES for 'ReplaceExpressionText': not GeoJSON features array provided\n{}"
                .format(features), 400)

        # Extend layer fields with this provided in GeoJSON Features
        feat_fields = QgsFields(layer.fields())
        feat_fields.extend(feature_fields)

        # form scope
        addFormScope = params.get('FORM_SCOPE',
                                  '').lower() in ['true', '1', 't']

        # loop through provided features to replace expression strings
        for f in feature_list:
            # clone the features with all attributes
            # those defined in layer + fields from GeoJSON Features
            feat = QgsFeature(feat_fields)
            feat.setGeometry(f.geometry())
            for field in f.fields():
                fname = field.name()
                if feat_fields.indexOf(fname) != -1:
                    feat.setAttribute(fname, f[fname])

            # Add form scope to expression context
            if addFormScope:
                exp_context.appendScope(
                    QgsExpressionContextUtils.formScope(feat))

            exp_context.setFeature(feat)
            exp_context.setFields(feat.fields())

            # replace expression strings with the new feature
            result = {}
            for k, s in str_map.items():
                value = QgsExpression.replaceExpressionText(s, exp_context, da)
                result[k] = json.loads(QgsJsonUtils.encodeValue(value))
            body['results'].append(result)

        write_json_response(body, response)
        return
Example #28
0
    def testExpression(self):
        """ test aggregate calculation using an expression """

        # numeric
        layer = QgsVectorLayer("Point?field=fldint:integer", "layer", "memory")
        pr = layer.dataProvider()

        int_values = [4, 2, 3, 2, 5, None, 8]

        features = []
        for v in int_values:
            f = QgsFeature()
            f.setFields(layer.fields())
            f.setAttributes([v])
            features.append(f)
        assert pr.addFeatures(features)

        # int
        agg = QgsAggregateCalculator(layer)
        val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 2')
        self.assertTrue(ok)
        self.assertEqual(val, 48)

        # double
        val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 1.5')
        self.assertTrue(ok)
        self.assertEqual(val, 36)

        # datetime
        val, ok = agg.calculate(
            QgsAggregateCalculator.Max,
            "to_date('2012-05-04') + to_interval( fldint || ' day' )")
        self.assertTrue(ok)
        self.assertEqual(val, QDateTime(QDate(2012, 5, 12), QTime(0, 0, 0)))

        # date
        val, ok = agg.calculate(
            QgsAggregateCalculator.Min,
            "to_date(to_date('2012-05-04') + to_interval( fldint || ' day' ))")
        self.assertTrue(ok)
        self.assertEqual(val, QDateTime(QDate(2012, 5, 6), QTime(0, 0, 0)))

        # string
        val, ok = agg.calculate(QgsAggregateCalculator.Max,
                                "fldint || ' oranges'")
        self.assertTrue(ok)
        self.assertEqual(val, '8 oranges')

        # geometry
        val, ok = agg.calculate(QgsAggregateCalculator.GeometryCollect,
                                "make_point( coalesce(fldint,0), 2 )")
        self.assertTrue(ok)
        self.assertTrue(val.asWkt(),
                        'MultiPoint((4 2, 2 2, 3 2, 2 2,5 2, 0 2,8 2))')

        # try a bad expression
        val, ok = agg.calculate(QgsAggregateCalculator.Max,
                                "not_a_field || ' oranges'")
        self.assertFalse(ok)
        val, ok = agg.calculate(QgsAggregateCalculator.Max, "5+")
        self.assertFalse(ok)

        # test expression context

        # check default context first
        # should have layer variables:
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@layer_name")
        self.assertTrue(ok)
        self.assertEqual(val, 'layer')
        # but not custom variables:
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var")
        self.assertTrue(ok)
        self.assertEqual(val, NULL)

        # test with manual expression context
        scope = QgsExpressionContextScope()
        scope.setVariable('my_var', 5)
        context = QgsExpressionContext()
        context.appendScope(scope)
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var", context)
        self.assertTrue(ok)
        self.assertEqual(val, 5)
Example #29
0
    def virtualFields(self, params: Dict[str,
                                         str], response: QgsServerResponse,
                      project: QgsProject) -> None:
        """ Get virtual fields for features
        In parameters:
            LAYER=wms-layer-name
            VIRTUALS={"key1": "first expression", "key2": "second expression"}
            // optionals
            FILTER=An expression to filter layer
            FIELDS=list of requested field separated by comma
            WITH_GEOMETRY=False
        """
        layername = params.get('LAYER', '')
        if not layername:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = findVectorLayer(layername, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'VirtualFields': {} provided".
                format(layername), 400)

        # get virtuals
        virtuals = params.get('VIRTUALS', '')
        if not virtuals:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS parameter is mandatory",
                400)

        # try to load virtuals dict
        vir_json = None
        try:
            vir_json = json.loads(virtuals)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads virtuals '{}' exception:\n{}".format(
                    virtuals, traceback.format_exc()), "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed"
                .format(virtuals), 400)

        if not isinstance(vir_json, dict):
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed"
                .format(virtuals), 400)

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # parse virtuals
        exp_map = {}
        exp_parser_errors = []
        for k, e in vir_json.items():
            exp = QgsExpression(e)
            exp.setGeomCalculator(da)
            exp.setDistanceUnits(project.distanceUnits())
            exp.setAreaUnits(project.areaUnits())

            if exp.hasParserError():
                exp_parser_errors.append('Error "{}": {}'.format(
                    e, exp.parserErrorString()))
                continue

            if not exp.isValid():
                exp_parser_errors.append('Expression not valid "{}"'.format(e))
                continue

            exp.prepare(exp_context)
            exp_map[k] = exp

        # expression parser errors found
        if exp_parser_errors:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid VIRTUALS for 'VirtualFields':\n{}".format(
                    '\n'.join(exp_parser_errors)), 400)

        req = QgsFeatureRequest()

        # get filter
        req_filter = params.get('FILTER', '')
        if req_filter:
            req_exp = QgsExpression(req_filter)
            req_exp.setGeomCalculator(da)
            req_exp.setDistanceUnits(project.distanceUnits())
            req_exp.setAreaUnits(project.areaUnits())

            if req_exp.hasParserError():
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid FILTER for 'VirtualFields' Error \"{}\": {}".
                    format(req_filter, req_exp.parserErrorString()), 400)

            if not req_exp.isValid():
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid FILTER for 'VirtualFields' Expression not valid \"{}\""
                    .format(req_filter), 400)

            req_exp.prepare(exp_context)
            req = QgsFeatureRequest(req_exp, exp_context)

        # With geometry
        withGeom = params.get('WITH_GEOMETRY',
                              '').lower() in ['true', '1', 't']
        if not withGeom:
            req.setFlags(QgsFeatureRequest.NoGeometry)

        # Fields
        pkAttributes = layer.primaryKeyAttributes()
        attributeList = [i for i in pkAttributes]
        fields = layer.fields()
        r_fields = [
            f.strip() for f in params.get('FIELDS', '').split(',') if f
        ]
        for f in r_fields:
            attributeList.append(fields.indexOf(f))

        # response
        response.setStatusCode(200)
        response.setHeader("Content-Type", "application/json")
        response.write('{ "type": "FeatureCollection","features":[')
        response.flush()

        jsonExporter = QgsJsonExporter(layer)
        if attributeList:
            jsonExporter.setAttributes(attributeList)

        separator = ''
        for feat in layer.getFeatures(req):
            fid = layername + '.' + getServerFid(feat, pkAttributes)

            extra = {}

            # Update context
            exp_context.setFeature(feat)
            exp_context.setFields(feat.fields())

            # Evaluate expressions for virtual fields
            errors = {}
            for k, exp in exp_map.items():
                value = exp.evaluate(exp_context)
                if exp.hasEvalError():
                    extra[k] = None
                    errors[k] = exp.evalErrorString()
                else:
                    extra[k] = json.loads(QgsJsonUtils.encodeValue(value))
                    errors[k] = exp.expression()

            response.write(separator +
                           jsonExporter.exportFeature(feat, extra, fid))
            response.flush()
            separator = ',\n'
        response.write(']}')
        return
Example #30
0
    def fetch_values_from_layer(self):  # pylint: disable=too-many-locals, too-many-branches, too-many-statements
        """
        (Re)fetches plot values from the source layer.
        """

        # Note: we keep things nice and efficient and only iterate a single time over the layer!

        if not self.context_generator:
            context = QgsExpressionContext()
            context.appendScopes(
                QgsExpressionContextUtils.globalProjectLayerScopes(
                    self.source_layer))
        else:
            context = self.context_generator.createExpressionContext()
            # add a new scope corresponding to the source layer -- this will potentially overwrite any other
            # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure
            # that source layer fields and attributes are present in the context
            context.appendScope(
                self.source_layer.createExpressionContextScope())

        self.settings.data_defined_properties.prepare(context)

        self.fetch_layout_properties(context)

        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}

        x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \
            self.settings.properties[
                'x_name'] else (None, False, set())
        y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \
            self.settings.properties[
                'y_name'] else (None, False, set())
        z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \
            self.settings.properties[
                'z_name'] else (None, False, set())
        additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression(
            self.settings.layout['additional_info_expression']
        ) if self.settings.layout['additional_info_expression'] else (None,
                                                                      False,
                                                                      set())

        attrs = set().union(
            self.settings.data_defined_properties.referencedFields(), x_attrs,
            y_attrs, z_attrs, additional_attrs)

        request = QgsFeatureRequest()

        if self.settings.data_defined_properties.property(
                PlotSettings.PROPERTY_FILTER).isActive():
            expression = self.settings.data_defined_properties.property(
                PlotSettings.PROPERTY_FILTER).asExpression()
            request.setFilterExpression(expression)
            request.setExpressionContext(context)

        request.setSubsetOfAttributes(attrs, self.source_layer.fields())

        if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties(
        ):
            request.setFlags(QgsFeatureRequest.NoGeometry)

        visible_geom_engine = None
        if self.settings.properties.get(
                'visible_features_only',
                False) and self.visible_region is not None:
            ct = QgsCoordinateTransform(
                self.visible_region.crs(), self.source_layer.crs(),
                QgsProject.instance().transformContext())
            try:
                rect = ct.transformBoundingBox(self.visible_region)
                request.setFilterRect(rect)
            except QgsCsException:
                pass
        elif self.settings.properties.get(
                'visible_features_only',
                False) and self.polygon_filter is not None:
            ct = QgsCoordinateTransform(
                self.polygon_filter.crs(), self.source_layer.crs(),
                QgsProject.instance().transformContext())
            try:
                rect = ct.transformBoundingBox(
                    self.polygon_filter.geometry.boundingBox())
                request.setFilterRect(rect)
                g = self.polygon_filter.geometry
                g.transform(ct)

                visible_geom_engine = QgsGeometry.createGeometryEngine(
                    g.constGet())
                visible_geom_engine.prepareGeometry()
            except QgsCsException:
                pass

        if self.selected_features_only:
            it = self.source_layer.getSelectedFeatures(request)
        else:
            it = self.source_layer.getFeatures(request)

        # Some plot types don't draw individual glyphs for each feature, but aggregate them instead.
        # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each
        # feature. Instead, the evaluation should be executed only once for these settings.
        aggregating = self.settings.plot_type in ['box', 'histogram']
        executed = False

        xx = []
        yy = []
        zz = []
        additional_hover_text = []
        marker_sizes = []
        colors = []
        stroke_colors = []
        stroke_widths = []
        for f in it:
            if visible_geom_engine and not visible_geom_engine.intersects(
                    f.geometry().constGet()):
                continue

            self.settings.feature_ids.append(f.id())
            context.setFeature(f)

            x = None
            if x_expression:
                x = x_expression.evaluate(context)
                if x == NULL or x is None:
                    continue
            elif self.settings.properties['x_name']:
                x = f[self.settings.properties['x_name']]
                if x == NULL or x is None:
                    continue

            y = None
            if y_expression:
                y = y_expression.evaluate(context)
                if y == NULL or y is None:
                    continue
            elif self.settings.properties['y_name']:
                y = f[self.settings.properties['y_name']]
                if y == NULL or y is None:
                    continue

            z = None
            if z_expression:
                z = z_expression.evaluate(context)
                if z == NULL or z is None:
                    continue
            elif self.settings.properties['z_name']:
                z = f[self.settings.properties['z_name']]
                if z == NULL or z is None:
                    continue

            if additional_info_expression:
                additional_hover_text.append(
                    additional_info_expression.evaluate(context))
            elif self.settings.layout['additional_info_expression']:
                additional_hover_text.append(
                    f[self.settings.layout['additional_info_expression']])

            if x is not None:
                xx.append(x)
            if y is not None:
                yy.append(y)
            if z is not None:
                zz.append(z)

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_MARKER_SIZE):
                default_value = self.settings.properties['marker_size']
                context.setOriginalValueVariable(default_value)
                value, _ = self.settings.data_defined_properties.valueAsDouble(
                    PlotSettings.PROPERTY_MARKER_SIZE, context, default_value)
                marker_sizes.append(value)
            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_STROKE_WIDTH):
                default_value = self.settings.properties['marker_width']
                context.setOriginalValueVariable(default_value)
                value, _ = self.settings.data_defined_properties.valueAsDouble(
                    PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value)
                stroke_widths.append(value)

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_COLOR) and (not aggregating
                                                      or not executed):
                default_value = QColor(self.settings.properties['in_color'])
                value, conversion_success = self.settings.data_defined_properties.valueAsColor(
                    PlotSettings.PROPERTY_COLOR, context, default_value)
                if conversion_success:
                    # We were given a valid color specification, use that color
                    colors.append(value.name())
                else:
                    try:
                        # Attempt to interpret the value as a list of color specifications
                        value_list = self.settings.data_defined_properties.value(
                            PlotSettings.PROPERTY_COLOR, context)
                        color_list = [
                            QgsSymbolLayerUtils.decodeColor(item).name()
                            for item in value_list
                        ]
                        colors.extend(color_list)
                    except TypeError:
                        # Not a list of color specifications, use the default color instead
                        colors.append(default_value.name())

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating
                                                             or not executed):
                default_value = QColor(self.settings.properties['out_color'])
                value, conversion_success = self.settings.data_defined_properties.valueAsColor(
                    PlotSettings.PROPERTY_STROKE_COLOR, context, default_value)
                if conversion_success:
                    # We were given a valid color specification, use that color
                    stroke_colors.append(value.name())
                else:
                    try:
                        # Attempt to interpret the value as a list of color specifications
                        value_list = self.settings.data_defined_properties.value(
                            PlotSettings.PROPERTY_STROKE_COLOR, context)
                        color_list = [
                            QgsSymbolLayerUtils.decodeColor(item).name()
                            for item in value_list
                        ]
                        stroke_colors.extend(color_list)
                    except TypeError:
                        # Not a list of color specifications, use the default color instead
                        stroke_colors.append(default_value.name())

            executed = True

        self.settings.additional_hover_text = additional_hover_text
        self.settings.x = xx
        self.settings.y = yy
        self.settings.z = zz
        if marker_sizes:
            self.settings.data_defined_marker_sizes = marker_sizes
        if colors:
            self.settings.data_defined_colors = colors
        if stroke_colors:
            self.settings.data_defined_stroke_colors = stroke_colors
        if stroke_widths:
            self.settings.data_defined_stroke_widths = stroke_widths
Example #31
0
    def processAlgorithm(self, parameters, context, feedback):
        if DEBUG_MODE:
            logMessage(
                "processAlgorithm(): {}".format(self.__class__.__name__),
                False)

        clayer = self.parameterAsLayer(parameters, self.INPUT, context)
        title_field = self.parameterAsString(parameters, self.TITLE_FIELD,
                                             context)
        cf_filter = self.parameterAsBool(parameters, self.CF_FILTER, context)
        fixed_scale = self.parameterAsEnum(parameters, self.SCALE,
                                           context)  # == 1
        buf = self.parameterAsDouble(parameters, self.BUFFER, context)
        tex_width = self.parameterAsInt(parameters, self.TEX_WIDTH, context)
        orig_tex_height = self.parameterAsInt(parameters, self.TEX_HEIGHT,
                                              context)

        header_exp = QgsExpression(
            self.parameterAsExpression(parameters, self.HEADER, context))
        footer_exp = QgsExpression(
            self.parameterAsExpression(parameters, self.FOOTER, context))

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(clayer))

        out_dir = self.parameterAsString(parameters, self.OUTPUT, context)
        if not QDir(out_dir).exists():
            QDir().mkpath(out_dir)

        if DEBUG_MODE:
            openDirectory(out_dir)

        mapSettings = self.settings.mapSettings
        baseExtent = self.settings.baseExtent
        rotation = mapSettings.rotation()
        orig_size = mapSettings.outputSize()

        if cf_filter:
            cf_layer = QgsMemoryProviderUtils.createMemoryLayer(
                "current feature", clayer.fields(), clayer.wkbType(),
                clayer.crs())
            layers = [
                cf_layer if lyr == clayer else lyr
                for lyr in mapSettings.layers()
            ]
            mapSettings.setLayers(layers)

            doc = QDomDocument("qgis")
            clayer.exportNamedStyle(doc)
            cf_layer.importNamedStyle(doc)

        total = clayer.featureCount()
        for current, feature in enumerate(clayer.getFeatures()):
            if feedback.isCanceled():
                break

            if cf_filter:
                cf_layer.startEditing()
                cf_layer.deleteFeatures(
                    [f.id() for f in cf_layer.getFeatures()])
                cf_layer.addFeature(feature)
                cf_layer.commitChanges()

            title = feature.attribute(title_field)
            feedback.setProgressText("({}/{}) Exporting {}...".format(
                current + 1, total, title))
            logMessage("Exporting {}...".format(title), False)

            # extent
            geometry = QgsGeometry(feature.geometry())
            geometry.transform(self.transform)
            center = geometry.centroid().asPoint()

            if fixed_scale or geometry.type() == QgsWkbTypes.PointGeometry:
                tex_height = orig_tex_height or int(
                    tex_width * orig_size.height() / orig_size.width())
                extent = MapExtent(center, baseExtent.width(),
                                   baseExtent.width() * tex_height / tex_width,
                                   rotation).scale(1 + buf / 100)
            else:
                geometry.rotate(rotation, center)
                rect = geometry.boundingBox().scaled(1 + buf / 100)
                center = MapExtent.rotateQgsPoint(rect.center(), rotation,
                                                  center)
                if orig_tex_height:
                    tex_height = orig_tex_height
                    tex_ratio = tex_width / tex_height
                    rect_ratio = rect.width() / rect.height()
                    if tex_ratio > rect_ratio:
                        extent = MapExtent(center,
                                           rect.height() * tex_ratio,
                                           rect.height(), rotation)
                    else:
                        extent = MapExtent(center, rect.width(),
                                           rect.width() / tex_ratio, rotation)
                else:
                    # fit to buffered geometry bounding box
                    extent = MapExtent(center, rect.width(), rect.height(),
                                       rotation)
                    tex_height = tex_width * rect.height() / rect.width()

            extent.toMapSettings(mapSettings)
            mapSettings.setOutputSize(QSize(tex_width, tex_height))

            self.settings.setMapSettings(mapSettings)

            # labels
            exp_context.setFeature(feature)
            self.settings.setHeaderLabel(header_exp.evaluate(exp_context))
            self.settings.setFooterLabel(footer_exp.evaluate(exp_context))

            self.export(title, out_dir, feedback)

            feedback.setProgress(int(current / total * 100))

        if P_OPEN_DIRECTORY and not DEBUG_MODE:
            openDirectory(out_dir)

        return {}
Example #32
0
def exportLayers(iface, layers, folder, precision, optimize, popupField, json,
                 restrictToExtent, extent, feedback):
    canvas = iface.mapCanvas()
    epsg4326 = QgsCoordinateReferenceSystem("EPSG:4326")
    layersFolder = os.path.join(folder, "layers")
    QDir().mkpath(layersFolder)
    for count, (layer, encode2json,
                popup) in enumerate(zip(layers, json, popupField)):
        if (layer.type() == layer.VectorLayer
                and (layer.providerType() != "WFS" or encode2json)):
            feedback.showFeedback("Exporting %s to JSON..." % layer.name())
            cleanLayer = writeTmpLayer(layer, popup, restrictToExtent, iface,
                                       extent)
            fields = layer.pendingFields()
            for field in fields:
                exportImages(layer, field.name(), layersFolder + "/tmp.tmp")
            if is25d(layer, canvas, restrictToExtent, extent):
                provider = cleanLayer.dataProvider()
                provider.addAttributes([
                    QgsField("height", QVariant.Double),
                    QgsField("wallColor", QVariant.String),
                    QgsField("roofColor", QVariant.String)
                ])
                cleanLayer.updateFields()
                fields = cleanLayer.pendingFields()
                renderer = layer.rendererV2()
                renderContext = QgsRenderContext.fromMapSettings(
                    canvas.mapSettings())
                feats = layer.getFeatures()
                context = QgsExpressionContext()
                context.appendScope(
                    QgsExpressionContextUtils.layerScope(layer))
                expression = QgsExpression('eval(@qgis_25d_height)')
                heightField = fields.indexFromName("height")
                wallField = fields.indexFromName("wallColor")
                roofField = fields.indexFromName("roofColor")
                renderer.startRender(renderContext, fields)
                cleanLayer.startEditing()
                for feat in feats:
                    context.setFeature(feat)
                    height = expression.evaluate(context)
                    if isinstance(renderer, QgsCategorizedSymbolRendererV2):
                        classAttribute = renderer.classAttribute()
                        attrValue = feat.attribute(classAttribute)
                        catIndex = renderer.categoryIndexForValue(attrValue)
                        categories = renderer.categories()
                        symbol = categories[catIndex].symbol()
                    elif isinstance(renderer, QgsGraduatedSymbolRendererV2):
                        classAttribute = renderer.classAttribute()
                        attrValue = feat.attribute(classAttribute)
                        ranges = renderer.ranges()
                        for range in ranges:
                            if (attrValue >= range.lowerValue()
                                    and attrValue <= range.upperValue()):
                                symbol = range.symbol().clone()
                    else:
                        symbol = renderer.symbolForFeature2(
                            feat, renderContext)
                    sl1 = symbol.symbolLayer(1)
                    sl2 = symbol.symbolLayer(2)
                    wallColor = sl1.subSymbol().color().name()
                    roofColor = sl2.subSymbol().color().name()
                    provider.changeAttributeValues({
                        feat.id() + 1: {
                            heightField: height,
                            wallField: wallColor,
                            roofField: roofColor
                        }
                    })
                cleanLayer.commitChanges()
                renderer.stopRender(renderContext)

            sln = safeName(cleanLayer.name()) + unicode(count)
            tmpPath = os.path.join(layersFolder, sln + ".json")
            path = os.path.join(layersFolder, sln + ".js")
            options = []
            if precision != "maintain":
                options.append("COORDINATE_PRECISION=" + unicode(precision))
            QgsVectorFileWriter.writeAsVectorFormat(cleanLayer,
                                                    tmpPath,
                                                    "utf-8",
                                                    epsg4326,
                                                    'GeoJson',
                                                    0,
                                                    layerOptions=options)
            with open(path, "w") as f:
                f.write("var %s = " % ("geojson_" + sln))
                with open(tmpPath, "r") as f2:
                    for line in f2:
                        if optimize:
                            line = line.strip("\n\t ")
                            line = removeSpaces(line)
                        f.write(line)
            os.remove(tmpPath)

        elif (layer.type() == layer.RasterLayer
              and layer.providerType() != "wms"):
            exportRaster(layer, count, layersFolder, feedback)
    feedback.completeStep()
Example #33
0
    def runGetFeatureTests(self, provider):
        assert len([f for f in provider.getFeatures()]) == 5
        self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
        self.assert_query(provider, '"name" IS NULL', [5])
        self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
        self.assert_query(provider, 'name = \'Apple\'', [2])
        self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'name = \'apple\'', [])
        self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4])
        self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4])
        self.assert_query(provider, 'name LIKE \'Apple\'', [2])
        self.assert_query(provider, 'name LIKE \'aPple\'', [])
        self.assert_query(provider, 'name ILIKE \'aPple\'', [2])
        self.assert_query(provider, 'name ILIKE \'%pp%\'', [2])
        self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4])
        self.assert_query(provider, '-cnt > 0', [5])
        self.assert_query(provider, 'cnt < 0', [5])
        self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt <= 100', [1, 5])
        self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
        self.assert_query(provider, 'cnt = 50 * 2', [1])
        self.assert_query(provider, 'cnt = 150 / 1.5', [1])
        self.assert_query(provider, 'cnt = 1000 / 10', [1])
        self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int
        self.assert_query(provider, 'pk = 9 // 4', [2]) # int division
        self.assert_query(provider, 'cnt = 99 + 1', [1])
        self.assert_query(provider, 'cnt = 101 - 1', [1])
        self.assert_query(provider, 'cnt - 1 = 99', [1])
        self.assert_query(provider, '-cnt - 1 = -101', [1])
        self.assert_query(provider, '-(-cnt) = 100', [1])
        self.assert_query(provider, '-(cnt) = -(100)', [1])
        self.assert_query(provider, 'cnt + 1 = 101', [1])
        self.assert_query(provider, 'cnt = 1100 % 1000', [1])
        self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
        self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
        self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '\'x\' || "name" IS NULL', [5])
        self.assert_query(provider, 'cnt = 10 ^ 2', [1])
        self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1])
        self.assert_query(provider, '"name"="name2"', [2, 4])  # mix of matched and non-matched case sensitive names
        self.assert_query(provider, 'true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false', [])

        # Three value logic
        self.assert_query(provider, 'false and false', [])
        self.assert_query(provider, 'false and true', [])
        self.assert_query(provider, 'false and NULL', [])
        self.assert_query(provider, 'true and false', [])
        self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true and NULL', [])
        self.assert_query(provider, 'NULL and false', [])
        self.assert_query(provider, 'NULL and true', [])
        self.assert_query(provider, 'NULL and NULL', [])
        self.assert_query(provider, 'false or false', [])
        self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false or NULL', [])
        self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or false', [])
        self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or NULL', [])
        self.assert_query(provider, 'not true', [])
        self.assert_query(provider, 'not false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'not null', [])

        # not
        self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4])
        self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3])
        self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5])

        # type conversion - QGIS expressions do not mind that we are comparing a string
        # against numeric literals
        self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5])

        #function
        self.assert_query(provider, 'sqrt(pk) >= 2', [4, 5])
        self.assert_query(provider, 'radians(cnt) < 2', [1, 5])
        self.assert_query(provider, 'degrees(pk) <= 200', [1, 2, 3])
        self.assert_query(provider, 'abs(cnt) <= 200', [1, 2, 5])
        self.assert_query(provider, 'cos(pk) < 0', [2, 3, 4])
        self.assert_query(provider, 'sin(pk) < 0', [4, 5])
        self.assert_query(provider, 'tan(pk) < 0', [2, 3, 5])
        self.assert_query(provider, 'acos(-1) < pk', [4, 5])
        self.assert_query(provider, 'asin(1) < pk', [2, 3, 4, 5])
        self.assert_query(provider, 'atan(3.14) < pk', [2, 3, 4, 5])
        self.assert_query(provider, 'atan2(3.14, pk) < 1', [3, 4, 5])
        self.assert_query(provider, 'exp(pk) < 10', [1, 2])
        self.assert_query(provider, 'ln(pk) <= 1', [1, 2])
        self.assert_query(provider, 'log(3, pk) <= 1', [1, 2, 3])
        self.assert_query(provider, 'log10(pk) < 0.5', [1, 2, 3])
        self.assert_query(provider, 'round(3.14) <= pk', [3, 4, 5])
        self.assert_query(provider, 'round(0.314,1) * 10 = pk', [3])
        self.assert_query(provider, 'floor(3.14) <= pk', [3, 4, 5])
        self.assert_query(provider, 'ceil(3.14) <= pk', [4, 5])
        self.assert_query(provider, 'pk < pi()', [1, 2, 3])

        self.assert_query(provider, 'round(cnt / 66.67) <= 2', [1, 5])
        self.assert_query(provider, 'floor(cnt / 66.67) <= 2', [1, 2, 5])
        self.assert_query(provider, 'ceil(cnt / 66.67) <= 2', [1, 5])
        self.assert_query(provider, 'pk < pi() / 2', [1])
        self.assert_query(provider, 'pk = char(51)', [3])
        self.assert_query(provider, 'pk = coalesce(NULL,3,4)', [3])
        self.assert_query(provider, 'lower(name) = \'apple\'', [2])
        self.assert_query(provider, 'upper(name) = \'APPLE\'', [2])
        self.assert_query(provider, 'name = trim(\'   Apple   \')', [2])

        # geometry
        # azimuth and touches tests are deactivated because they do not pass for WFS provider
        #self.assert_query(provider, 'azimuth($geometry,geom_from_wkt( \'Point (-70 70)\')) < pi()', [1, 5])
        self.assert_query(provider, 'x($geometry) < -70', [1, 5])
        self.assert_query(provider, 'y($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'xmin($geometry) < -70', [1, 5])
        self.assert_query(provider, 'ymin($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'xmax($geometry) < -70', [1, 5])
        self.assert_query(provider, 'ymax($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [4, 5])
        self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])
        #self.assert_query(provider, 'touches($geometry,geom_from_wkt( \'Polygon ((-70.332 66.33, -65.32 66.33, -65.32 78.3, -70.332 78.3, -70.332 66.33))\'))', [1, 4])
        self.assert_query(provider, 'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)', [1, 2])
        self.assert_query(provider, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [4, 5])
        self.assert_query(provider, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2])

        # combination of an uncompilable expression and limit
        feature = next(self.vl.getFeatures('pk=4'))
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('parent', feature)
        context.appendScope(scope)

        request = QgsFeatureRequest()
        request.setExpressionContext(context)
        request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
        request.setLimit(1)

        values = [f['pk'] for f in self.vl.getFeatures(request)]
        self.assertEqual(values, [4])
Example #34
0
def print_layout(
    project: QgsProject,
    layout_name: str,
    output_format: OutputFormat,
    feature_filter: str = None,
    scales: list = None,
    scale: int = None,
    **kwargs,
):
    """Generate a PDF for an atlas or a report.

    :param project: The QGIS project.
    :type project: QgsProject

    :param layout_name: Name of the layout of the atlas or report.
    :type layout_name: basestring

    :param feature_filter: QGIS Expression to use to select the feature.
    It can return many features, a multiple pages PDF will be returned.
    This is required to print atlas, not report
    :type feature_filter: basestring

    :param scale: A scale to force in the atlas context. Default to None.
    :type scale: int

    :param scales: A list of predefined list of scales to force in the atlas context.
    Default to None.
    :type scales: list

    :param output_format: The output format, default to PDF if not provided.

    :return: Path to the PDF.
    :rtype: basestring
    """
    canvas = QgsMapCanvas()
    bridge = QgsLayerTreeMapCanvasBridge(project.layerTreeRoot(), canvas)
    bridge.setCanvasLayers()
    manager = project.layoutManager()
    master_layout = manager.layoutByName(layout_name)

    if output_format == OutputFormat.Svg:
        settings = QgsLayoutExporter.SvgExportSettings()
    elif output_format in (OutputFormat.Png, OutputFormat.Jpeg):
        settings = QgsLayoutExporter.ImageExportSettings()
    else:
        # PDF by default
        settings = QgsLayoutExporter.PdfExportSettings()

    atlas = None
    atlas_layout = None
    report_layout = None

    logger = Logger()

    if not master_layout:
        raise AtlasPrintException("Layout `{}` not found".format(layout_name))

    if master_layout.layoutType() == QgsMasterLayoutInterface.PrintLayout:
        for _print_layout in manager.printLayouts():
            if _print_layout.name() == layout_name:
                atlas_layout = _print_layout
                break

        atlas = atlas_layout.atlas()
        if not atlas.enabled():
            raise AtlasPrintException("The layout is not enabled for an atlas")

        layer = atlas.coverageLayer()

        if feature_filter is None:
            raise AtlasPrintException(
                "EXP_FILTER is mandatory to print an atlas layout")

        feature_filter = optimize_expression(layer, feature_filter)

        expression = QgsExpression(feature_filter)
        if expression.hasParserError():
            raise AtlasPrintException(
                "Expression is invalid, parser error: {}".format(
                    expression.parserErrorString()))

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope(project))
        context.appendScope(
            QgsExpressionContextUtils.layoutScope(atlas_layout))
        context.appendScope(QgsExpressionContextUtils.atlasScope(atlas))
        context.appendScope(QgsExpressionContextUtils.layerScope(layer))
        expression.prepare(context)
        if expression.hasEvalError():
            raise AtlasPrintException(
                "Expression is invalid, eval error: {}".format(
                    expression.evalErrorString()))

        atlas.setFilterFeatures(True)
        atlas.setFilterExpression(feature_filter)
        atlas.updateFeatures()

        if scale:
            atlas_layout.referenceMap().setAtlasScalingMode(
                QgsLayoutItemMap.Fixed)
            atlas_layout.referenceMap().setScale(scale)

        if scales:
            atlas_layout.referenceMap().setAtlasScalingMode(
                QgsLayoutItemMap.Predefined)
            settings.predefinedMapScales = scales

        if (not scales and atlas_layout.referenceMap().atlasScalingMode()
                == QgsLayoutItemMap.Predefined):
            use_project = project.useProjectScales()
            map_scales = project.mapScales()
            if not use_project or len(map_scales) == 0:
                logger.info(
                    "Map scales not found in project, fetching predefined map scales in global config"
                )
                map_scales = global_scales()
            settings.predefinedMapScales = map_scales

    elif master_layout.layoutType() == QgsMasterLayoutInterface.Report:
        report_layout = master_layout

    else:
        raise AtlasPrintException("The layout is not supported by the plugin")

    for key, value in kwargs.items():
        found = False
        if atlas_layout:
            item = atlas_layout.itemById(key.lower())
            if isinstance(item, QgsLayoutItemLabel):
                item.setText(value)
                found = True
        logger.info(
            'Additional parameters "{key}" {found} in layout, value "{value}"'.
            format(key=key,
                   found="found" if found else "not found",
                   value=value))

    file_name = "{}_{}.{}".format(clean_string(layout_name), uuid4(),
                                  output_format.name.lower())
    export_path = Path(tempfile.gettempdir()).joinpath(file_name)

    Logger().info("Exporting the request in {} using {}".format(
        export_path, output_format.value))

    if output_format in (OutputFormat.Png, OutputFormat.Jpeg):
        exporter = QgsLayoutExporter(atlas_layout or report_layout)
        result = exporter.exportToImage(str(export_path), settings)
        error = result_message(result)
    elif output_format in (OutputFormat.Svg, ):
        exporter = QgsLayoutExporter(atlas_layout or report_layout)
        result = exporter.exportToSvg(str(export_path), settings)
        error = result_message(result)
    else:
        # Default to PDF
        result, error = QgsLayoutExporter.exportToPdf(atlas or report_layout,
                                                      str(export_path),
                                                      settings)
        # Let's override error message
        _ = error
        error = result_message(result)

    if result != QgsLayoutExporter.Success:
        raise Exception("Export not generated in QGIS exporter {} : {}".format(
            export_path, error))

    if not export_path.is_file():
        logger.warning(
            "No error from QGIS Exporter, but the file does not exist.\n"
            "Message from QGIS exporter : {}\n"
            "File path : {}\n".format(error, export_path))
        raise Exception(
            "Export OK from QGIS, but file not found on the file system : {}".
            format(export_path))

    return export_path
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsRasterLayer(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
            
        band = self.parameterAsInt(parameters, self.BAND, context)
        expression = self.parameterAsString(parameters, self.EXPRESSION, context).replace('\n', '')
        feedback.pushInfo('Expression = {}'.format(expression))
        if len(expression.strip()) == 0:
            raise QgsProcessingException('Expression values required!')
        
        output_raster = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
        
        # create raster dataset
        inputDs = gdal.Open(unicode(source.source()), GA_ReadOnly)
        inputBand = inputDs.GetRasterBand(band)
        dataType = inputBand.DataType;
        nodata = int(inputBand.GetNoDataValue()) if dataType < 6 else inputBand.GetNoDataValue()
        
        feedback.pushInfo('NoData Value = {0}'.format(nodata))
        feedback.pushInfo('DataType = {0}'.format(dataType))
        feedback.pushInfo('DataType = {0}'.format(self.RASTER_TYPES[gdal.GetDataTypeName(dataType)]))

        driver = gdal.GetDriverByName('GTiff')
        outputDs = driver.Create(output_raster, inputDs.RasterXSize, inputDs.RasterYSize, 1, dataType)
        outputDs.SetProjection(inputDs.GetProjection())
        outputDs.SetGeoTransform(inputDs.GetGeoTransform())
        outputBand = outputDs.GetRasterBand(1)
        outputBand.SetNoDataValue(nodata)

        # prepare feature for expression
        fields = QgsFields()
        fields.append(QgsField('value', QVariant.Double))
        fields.append(QgsField(source.name(), QVariant.Double))

        exp = QgsExpression(expression)
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        context.appendScope(scope)

        feature = QgsFeature(fields)

        # extrace by attributes
        data_type  = self.RASTER_TYPES[gdal.GetDataTypeName(dataType)]
        for y in range(inputBand.YSize):
            if feedback.isCanceled():
                break
            feedback.setProgress(int(y / float(inputBand.YSize) * 100))
            
            scanline = inputBand.ReadRaster(0, y, inputBand.XSize, 1, inputBand.XSize, 1, dataType)
            values = struct.unpack(data_type * inputBand.XSize, scanline)
            
            output = ''.encode()
            for value in values:
                raster_value = nodata
                if value != nodata:
                    feature.setAttribute(0, value)
                    feature.setAttribute(1, value)
                    scope.setFeature(feature)
                    if bool(exp.evaluate(context)):
                      raster_value = value
                    else:
                      raster_value = nodata
                output = output + struct.pack(data_type, raster_value)
                    
            # write line
            outputBand.WriteRaster(0, y, inputBand.XSize, 1, output, buf_xsize=inputBand.XSize, buf_ysize=1, buf_type=dataType)
            del output

        outputDs.FlushCache()
        outputDs = None
        inputDs = None

        return {self.OUTPUT: output_raster}
Example #36
0
def print_layout(project, layout_name, feature_filter: str = None, scales=None, scale=None, **kwargs):
    """Generate a PDF for an atlas or a report.

    :param project: The QGIS project.
    :type project: QgsProject

    :param layout_name: Name of the layout of the atlas or report.
    :type layout_name: basestring

    :param feature_filter: QGIS Expression to use to select the feature.
    It can return many features, a multiple pages PDF will be returned.
    This is required to print atlas, not report
    :type feature_filter: basestring

    :param scale: A scale to force in the atlas context. Default to None.
    :type scale: int

    :param scales: A list of predefined list of scales to force in the atlas context.
    Default to None.
    :type scales: list

    :return: Path to the PDF.
    :rtype: basestring
    """
    canvas = QgsMapCanvas()
    bridge = QgsLayerTreeMapCanvasBridge(
        project.layerTreeRoot(),
        canvas
    )
    bridge.setCanvasLayers()
    manager = project.layoutManager()
    master_layout = manager.layoutByName(layout_name)
    settings = QgsLayoutExporter.PdfExportSettings()

    atlas = None
    atlas_layout = None
    report_layout = None


    if not master_layout:
        raise AtlasPrintException('Layout `{}` not found'.format(layout_name))

    if master_layout.layoutType() == QgsMasterLayoutInterface.PrintLayout:
        for _print_layout in manager.printLayouts():
            if _print_layout.name() == layout_name:
                atlas_layout = _print_layout
                break

        atlas = atlas_layout.atlas()
        if not atlas.enabled():
            raise AtlasPrintException('The layout is not enabled for an atlas')

        layer = atlas.coverageLayer()

        if feature_filter is None:
            raise AtlasPrintException('EXP_FILTER is mandatory to print an atlas layout')

        feature_filter = optimize_expression(layer, feature_filter)

        expression = QgsExpression(feature_filter)
        if expression.hasParserError():
            raise AtlasPrintException('Expression is invalid, parser error: {}'.format(expression.parserErrorString()))

        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.globalScope())
        context.appendScope(QgsExpressionContextUtils.projectScope(project))
        context.appendScope(QgsExpressionContextUtils.layoutScope(atlas_layout))
        context.appendScope(QgsExpressionContextUtils.atlasScope(atlas))
        context.appendScope(QgsExpressionContextUtils.layerScope(layer))
        expression.prepare(context)
        if expression.hasEvalError():
            raise AtlasPrintException('Expression is invalid, eval error: {}'.format(expression.evalErrorString()))

        atlas.setFilterFeatures(True)
        atlas.setFilterExpression(feature_filter)

        if scale:
            atlas_layout.referenceMap().setAtlasScalingMode(QgsLayoutItemMap.Fixed)
            atlas_layout.referenceMap().setScale(scale)

        if scales:
            atlas_layout.referenceMap().setAtlasScalingMode(QgsLayoutItemMap.Predefined)
            if Qgis.QGIS_VERSION_INT >= 30900:
                settings.predefinedMapScales = scales
            else:
                atlas_layout.reportContext().setPredefinedScales(scales)

        if not scales and atlas_layout.referenceMap().atlasScalingMode() == QgsLayoutItemMap.Predefined:
            if Qgis.QGIS_VERSION_INT >= 30900:
                use_project = project.useProjectScales()
                map_scales = project.mapScales()
            else:
                map_scales = project_scales(project)
                use_project = len(map_scales) == 0

            if not use_project or len(map_scales) == 0:
                logger.info(
                    'Map scales not found in project, fetching predefined map scales in global config'
                )
                map_scales = global_scales()

            if Qgis.QGIS_VERSION_INT >= 30900:
                settings.predefinedMapScales = map_scales
            else:
                atlas_layout.reportContext().setPredefinedScales(map_scales)

    elif master_layout.layoutType() == QgsMasterLayoutInterface.Report:
        report_layout = master_layout

    else:
        raise AtlasPrintException('The layout is not supported by the plugin')

    for key, value in kwargs.items():
        found = False
        if atlas_layout:
            item = atlas_layout.itemById(key.lower())
            if isinstance(item, QgsLayoutItemLabel):
                item.setText(value)
                found = True
        logger.info(
            'Additional parameters: {} found in layout {}, value {}'.format(key, found, value))

    export_path = os.path.join(
        tempfile.gettempdir(),
        '{}_{}.pdf'.format(layout_name, uuid4())
    )
    result, error = QgsLayoutExporter.exportToPdf(atlas or report_layout, export_path, settings)

    if result != QgsLayoutExporter.Success and not os.path.isfile(export_path):
        raise Exception('export not generated {} ({})'.format(export_path, error))

    return export_path
Example #37
0
    def runGetFeatureTests(self, provider):
        assert len([f for f in provider.getFeatures()]) == 5
        self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
        self.assert_query(provider, '"name" IS NULL', [5])
        self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
        self.assert_query(provider, 'name = \'Apple\'', [2])
        self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'name = \'apple\'', [])
        self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4])
        self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4])
        self.assert_query(provider, 'name LIKE \'Apple\'', [2])
        self.assert_query(provider, 'name LIKE \'aPple\'', [])
        self.assert_query(provider, 'name ILIKE \'aPple\'', [2])
        self.assert_query(provider, 'name ILIKE \'%pp%\'', [2])
        self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4])
        self.assert_query(provider, '-cnt > 0', [5])
        self.assert_query(provider, 'cnt < 0', [5])
        self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt <= 100', [1, 5])
        self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
        self.assert_query(provider, 'cnt = 50 * 2', [1])
        self.assert_query(provider, 'cnt = 99 + 1', [1])
        self.assert_query(provider, 'cnt = 101 - 1', [1])
        self.assert_query(provider, 'cnt - 1 = 99', [1])
        self.assert_query(provider, '-cnt - 1 = -101', [1])
        self.assert_query(provider, '-(-cnt) = 100', [1])
        self.assert_query(provider, '-(cnt) = -(100)', [1])
        self.assert_query(provider, 'cnt + 1 = 101', [1])
        self.assert_query(provider, 'cnt = 1100 % 1000', [1])
        self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
        self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
        self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '\'x\' || "name" IS NULL', [5])
        self.assert_query(provider, 'cnt = 10 ^ 2', [1])
        self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1])
        self.assert_query(provider, '"name"="name2"', [2, 4])  # mix of matched and non-matched case sensitive names
        self.assert_query(provider, 'true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false', [])

        # Three value logic
        self.assert_query(provider, 'false and false', [])
        self.assert_query(provider, 'false and true', [])
        self.assert_query(provider, 'false and NULL', [])
        self.assert_query(provider, 'true and false', [])
        self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true and NULL', [])
        self.assert_query(provider, 'NULL and false', [])
        self.assert_query(provider, 'NULL and true', [])
        self.assert_query(provider, 'NULL and NULL', [])
        self.assert_query(provider, 'false or false', [])
        self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false or NULL', [])
        self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or false', [])
        self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or NULL', [])
        self.assert_query(provider, 'not true', [])
        self.assert_query(provider, 'not false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'not null', [])

        # not
        self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4])
        self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3])
        self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5])

        # type conversion - QGIS expressions do not mind that we are comparing a string
        # against numeric literals
        self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5])

        # geometry
        self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])

        # combination of an uncompilable expression and limit
        feature = next(self.vl.getFeatures('pk=4'))
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('parent', feature)
        context.appendScope(scope)

        request = QgsFeatureRequest()
        request.setExpressionContext(context)
        request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
        request.setLimit(1)

        values = [f['pk'] for f in self.vl.getFeatures(request)]
        self.assertEqual(values, [4])
    def processAlgorithm(self, parameters, context, feedback):

        t_troncon = self.parameterAsSource(parameters, self.SEGMENTS_TABLE,
                                           context)
        g_troncon = self.parameterAsSource(parameters, self.GEOM_SEGMENTS,
                                           context)
        t_obs = self.parameterAsSource(parameters, self.OBSERVATION_TABLE,
                                       context)
        g_obs = self.parameterAsVectorLayer(parameters, self.GEOM_OBSERVATION,
                                            context)

        # Get troncon ids and file ids
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project()))
        exp_context.appendScope(t_troncon.createExpressionContextScope())

        exp_str = '"id_geom_troncon" IS NOT NULL'
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression %s has eval error: %s').format(
                    exp.expression(), exp.evalErrorString()))

        request = QgsFeatureRequest(exp, exp_context)
        request.setSubsetOfAttributes(
            ['id', 'aab', 'aad', 'aaf', 'abq', 'id_file', 'id_geom_troncon'],
            t_troncon.fields())
        has_geo_troncon = False
        troncons = {}
        file_ids = []
        for tro in t_troncon.getFeatures(request):
            troncons[tro['id']] = tro
            file_ids.append(tro['id_file'])
            has_geo_troncon = True

            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.OBSERVATIONS_CREATED: None}

        if not has_geo_troncon:
            raise QgsProcessingException(tr('* ERROR: No troncon geometries'))

        # Get observation ids
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project()))
        exp_context.appendScope(t_obs.createExpressionContextScope())

        exp_str = ('"id_troncon" IN ({}) AND '
                   '"id_file" IN ({})').format(
                       ','.join([str(i) for i in troncons.keys()]),
                       ','.join([str(i) for i in file_ids]))
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression {} has eval error: {}').format(
                    exp.expression(), exp.evalErrorString()))

        obs_ids = []
        request = QgsFeatureRequest(exp, exp_context)
        for obs in t_obs.getFeatures(request):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.OBSERVATIONS_CREATED: None}

            troncon = troncons[obs['id_troncon']]

            # verifying ITV file
            if troncon['id_file'] != obs['id_file']:
                continue

            obs_ids.append(obs['id'])

        if not obs_ids:
            raise QgsProcessingException(
                tr('* ERROR: No observations to geolocalize found'))

        # Check observations already geolocalised on troncon
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project()))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(g_obs))

        exp_str = '"id" IN ({})'.format(','.join([str(i) for i in obs_ids]))
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression {} has eval error: {}').format(
                    exp.expression(), exp.evalErrorString()))

        request = QgsFeatureRequest(exp, exp_context)
        geo_observations = []
        for obs in g_obs.getFeatures(request):
            geo_observations.append(obs['id'])

            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.OBSERVATIONS_CREATED: None}

        # build observation geometry based on table
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project()))
        exp_context.appendScope(t_obs.createExpressionContextScope())

        exp_str = ('"id_troncon" IN ({}) AND '
                   '"id_file" IN ({})').format(
                       ','.join([str(i) for i in troncons.keys()]),
                       ','.join([str(i) for i in file_ids]))
        if geo_observations:
            exp_str += ' AND id NOT IN ({})'.format(','.join(
                [str(i) for i in geo_observations]))
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression {} has eval error: {}').format(
                    exp.expression(), exp.evalErrorString()))

        request = QgsFeatureRequest(exp, exp_context)
        features = []
        fields = provider_fields(g_obs.fields())
        for obs in t_obs.getFeatures(request):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.OBSERVATIONS_CREATED: None}

            troncon = troncons[obs['id_troncon']]

            # verifying ITV file
            if troncon['id_file'] != obs['id_file']:
                continue

            geo_req = QgsFeatureRequest()
            geo_req.setFilterFid(troncon['id_geom_troncon'])
            for g_tro in g_troncon.getFeatures(geo_req):
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.OBSERVATIONS_CREATED: None}

                geom = g_tro.geometry()
                pt = None
                if troncon['aab'] == troncon['aad']:
                    pt = geom.interpolate(geom.length() * obs['i'] /
                                          troncon['abq'])
                else:
                    pt = geom.interpolate(geom.length() *
                                          (1 - obs['i'] / troncon['abq']))
                fet = QgsFeature(fields)
                fet.setGeometry(pt)
                fet.setAttribute('id', obs['id'])
                features.append(fet)

        # Ajout des objets observations
        if features:
            g_obs.startEditing()
            (res, outFeats) = g_obs.dataProvider().addFeatures(features)
            if not res or not outFeats:
                raise QgsProcessingException(
                    tr('* ERREUR: lors de l\'enregistrement '
                       'des regards {}').format(', '.join(
                           g_obs.dataProvider().errors())))
            if not g_obs.commitChanges():
                raise QgsProcessingException(
                    tr('* ERROR: Commit {}.').format(g_obs.commitErrors()))

        # Returns empty dict if no outputs
        return {self.OBSERVATIONS_CREATED: len(features)}
Example #39
0
    def runGetFeatureTests(self, provider):
        assert len([f for f in provider.getFeatures()]) == 5
        self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
        self.assert_query(provider, '"name" IS NULL', [5])
        self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
        self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
        self.assert_query(provider, 'name = \'Apple\'', [2])
        self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'name = \'apple\'', [])
        self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4])
        self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4])
        self.assert_query(provider, 'name LIKE \'Apple\'', [2])
        self.assert_query(provider, 'name LIKE \'aPple\'', [])
        self.assert_query(provider, 'name ILIKE \'aPple\'', [2])
        self.assert_query(provider, 'name ILIKE \'%pp%\'', [2])
        self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4])
        self.assert_query(provider, '-cnt > 0', [5])
        self.assert_query(provider, 'cnt < 0', [5])
        self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4])
        self.assert_query(provider, 'cnt <= 100', [1, 5])
        self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
        self.assert_query(provider, 'cnt = 50 * 2', [1])
        self.assert_query(provider, 'cnt = 150 / 1.5', [1])
        self.assert_query(provider, 'cnt = 1000 / 10', [1])
        self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int
        self.assert_query(provider, 'pk = 9 // 4', [2]) # int division
        self.assert_query(provider, 'cnt = 99 + 1', [1])
        self.assert_query(provider, 'cnt = 101 - 1', [1])
        self.assert_query(provider, 'cnt - 1 = 99', [1])
        self.assert_query(provider, '-cnt - 1 = -101', [1])
        self.assert_query(provider, '-(-cnt) = 100', [1])
        self.assert_query(provider, '-(cnt) = -(100)', [1])
        self.assert_query(provider, 'cnt + 1 = 101', [1])
        self.assert_query(provider, 'cnt = 1100 % 1000', [1])
        self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
        self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
        self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
        self.assert_query(provider, '\'x\' || "name" IS NULL', [5])
        self.assert_query(provider, 'cnt = 10 ^ 2', [1])
        self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1])
        self.assert_query(provider, '"name"="name2"', [2, 4])  # mix of matched and non-matched case sensitive names
        self.assert_query(provider, 'true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false', [])

        # Three value logic
        self.assert_query(provider, 'false and false', [])
        self.assert_query(provider, 'false and true', [])
        self.assert_query(provider, 'false and NULL', [])
        self.assert_query(provider, 'true and false', [])
        self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true and NULL', [])
        self.assert_query(provider, 'NULL and false', [])
        self.assert_query(provider, 'NULL and true', [])
        self.assert_query(provider, 'NULL and NULL', [])
        self.assert_query(provider, 'false or false', [])
        self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'false or NULL', [])
        self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or false', [])
        self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'NULL or NULL', [])
        self.assert_query(provider, 'not true', [])
        self.assert_query(provider, 'not false', [1, 2, 3, 4, 5])
        self.assert_query(provider, 'not null', [])

        # not
        self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
        self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4])
        self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3])
        self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5])

        # type conversion - QGIS expressions do not mind that we are comparing a string
        # against numeric literals
        self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5])

        #function
        self.assert_query(provider, 'sqrt(pk) >= 2', [4, 5])
        self.assert_query(provider, 'radians(cnt) < 2', [1, 5])
        self.assert_query(provider, 'degrees(pk) <= 200', [1, 2, 3])
        self.assert_query(provider, 'abs(cnt) <= 200', [1, 2, 5])
        self.assert_query(provider, 'cos(pk) < 0', [2, 3, 4])
        self.assert_query(provider, 'sin(pk) < 0', [4, 5])
        self.assert_query(provider, 'tan(pk) < 0', [2, 3, 5])
        self.assert_query(provider, 'acos(-1) < pk', [4, 5])
        self.assert_query(provider, 'asin(1) < pk', [2, 3, 4, 5])
        self.assert_query(provider, 'atan(3.14) < pk', [2, 3, 4, 5])
        self.assert_query(provider, 'atan2(3.14, pk) < 1', [3, 4, 5])
        self.assert_query(provider, 'exp(pk) < 10', [1, 2])
        self.assert_query(provider, 'ln(pk) <= 1', [1, 2])
        self.assert_query(provider, 'log(3, pk) <= 1', [1, 2, 3])
        self.assert_query(provider, 'log10(pk) < 0.5', [1, 2, 3])
        self.assert_query(provider, 'round(3.14) <= pk', [3, 4, 5])
        self.assert_query(provider, 'round(0.314,1) * 10 = pk', [3])
        self.assert_query(provider, 'floor(3.14) <= pk', [3, 4, 5])
        self.assert_query(provider, 'ceil(3.14) <= pk', [4, 5])
        self.assert_query(provider, 'pk < pi()', [1, 2, 3])

        self.assert_query(provider, 'round(cnt / 66.67) <= 2', [1, 5])
        self.assert_query(provider, 'floor(cnt / 66.67) <= 2', [1, 2, 5])
        self.assert_query(provider, 'ceil(cnt / 66.67) <= 2', [1, 5])
        self.assert_query(provider, 'pk < pi() / 2', [1])
        self.assert_query(provider, 'pk = char(51)', [3])
        self.assert_query(provider, 'pk = coalesce(NULL,3,4)', [3])
        self.assert_query(provider, 'lower(name) = \'apple\'', [2])
        self.assert_query(provider, 'upper(name) = \'APPLE\'', [2])
        self.assert_query(provider, 'name = trim(\'   Apple   \')', [2])

        # geometry
        # azimuth and touches tests are deactivated because they do not pass for WFS provider
        #self.assert_query(provider, 'azimuth($geometry,geom_from_wkt( \'Point (-70 70)\')) < pi()', [1, 5])
        self.assert_query(provider, 'x($geometry) < -70', [1, 5])
        self.assert_query(provider, 'y($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'xmin($geometry) < -70', [1, 5])
        self.assert_query(provider, 'ymin($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'xmax($geometry) < -70', [1, 5])
        self.assert_query(provider, 'ymax($geometry) > 70', [2, 4, 5])
        self.assert_query(provider, 'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [4, 5])
        self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])
        #self.assert_query(provider, 'touches($geometry,geom_from_wkt( \'Polygon ((-70.332 66.33, -65.32 66.33, -65.32 78.3, -70.332 78.3, -70.332 66.33))\'))', [1, 4])
        self.assert_query(provider, 'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)', [1, 2])
        self.assert_query(provider, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [4, 5])
        self.assert_query(provider, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2])

        # combination of an uncompilable expression and limit
        feature = next(self.vl.getFeatures('pk=4'))
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('parent', feature)
        context.appendScope(scope)

        request = QgsFeatureRequest()
        request.setExpressionContext(context)
        request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
        request.setLimit(1)

        values = [f['pk'] for f in self.vl.getFeatures(request)]
        self.assertEqual(values, [4])
# coding: utf-8
from qgis.core import QgsExpressionContextUtils, QgsExpressionContext
from qgis.gui import QgsVariableEditorWidget
from qgis.utils import iface

canvas = iface.mapCanvas()
variable_editor_widget = QgsVariableEditorWidget()
expression_context = QgsExpressionContext()
expression_context.appendScope(QgsExpressionContextUtils.globalScope())
expression_context.appendScope(QgsExpressionContextUtils.projectScope())
expression_context.appendScope(
    QgsExpressionContextUtils.mapSettingsScope(canvas.mapSettings()))
variable_editor_widget.setContext(expression_context)

variable_editor_widget.reloadContext()
variable_editor_widget.setEditableScopeIndex(0)
print(variable_editor_widget.context())
print(variable_editor_widget.editableScope())
print(variable_editor_widget.settingGroup())
print(variable_editor_widget.variablesInActiveScope())

variable_editor_widget.show()
Example #41
0
    def evaluate(self, params: Dict[str, str], response: QgsServerResponse,
                 project: QgsProject) -> None:
        """ Evaluate expressions against layer or features
        In parameters:
            LAYER=wms-layer-name
            EXPRESSION=An expression to evaluate
            or
            EXPRESSIONS=["first expression", "second expression"]
            or
            EXPRESSIONS={"key1": "first expression", "key2": "second expression"}
            // optionals
            FEATURE={"type": "Feature", "geometry": {}, "properties": {}}
            or
            FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}]
            FORM_SCOPE=boolean to add formScope based on provided features
        """

        layername = params.get('LAYER', '')
        if not layername:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = findVectorLayer(layername, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'Evaluate': {} provided".format(
                    layername), 400)

        # get expressions
        expressions = params.get('EXPRESSIONS', '')
        if not expressions:
            expression = params.get('EXPRESSION', '')
            if not expression:
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid 'Evaluate' REQUEST: EXPRESSION or EXPRESSIONS parameter is mandatory",
                    400)
            expressions = '["{}"]'.format(expression)

        # try to load expressions list or dict
        exp_json = None
        try:
            exp_json = json.loads(expressions)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads expressions '{}' exception:\n{}".format(
                    expressions, traceback.format_exc()), "lizmap",
                Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: EXPRESSIONS '{}' are not well formed"
                .format(expressions), 400)

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # parse expressions
        exp_map = {}
        exp_parser_errors = []
        exp_items = []
        if isinstance(exp_json, list):
            exp_items = enumerate(exp_json)
        elif isinstance(exp_json, dict):
            exp_items = exp_json.items()
        for k, e in exp_items:
            exp = QgsExpression(e)
            exp.setGeomCalculator(da)
            exp.setDistanceUnits(project.distanceUnits())
            exp.setAreaUnits(project.areaUnits())

            if exp.hasParserError():
                exp_parser_errors.append('Error "{}": {}'.format(
                    e, exp.parserErrorString()))
                continue

            if not exp.isValid():
                exp_parser_errors.append('Expression not valid "{}"'.format(e))
                continue

            exp.prepare(exp_context)
            exp_map[k] = exp

        # expression parser errors found
        if exp_parser_errors:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid EXPRESSIONS for 'Evaluate':\n{}".format(
                    '\n'.join(exp_parser_errors)), 400)

        # get features
        features = params.get('FEATURES', '')
        if not features:
            feature = params.get('FEATURE', '')
            if feature:
                features = '[' + feature + ']'

        # create the body
        body = {
            'status': 'success',
            'results': [],
            'errors': [],
            'features': 0
        }

        # without features just evaluate expression with layer context
        if not features:
            result = {}
            error = {}
            for k, exp in exp_map.items():
                value = exp.evaluate(exp_context)
                if exp.hasEvalError():
                    result[k] = None
                    error[k] = exp.evalErrorString()
                else:
                    result[k] = json.loads(QgsJsonUtils.encodeValue(value))
            body['results'].append(result)
            body['errors'].append(error)
            write_json_response(body, response)
            return

        # Check features
        geojson = []
        try:
            geojson = json.loads(features)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads features '{}' exception:\n{}".format(
                    features, traceback.format_exc()), "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed"
                .format(features), 400)

        if not geojson or not isinstance(geojson, list) or len(geojson) == 0:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed"
                .format(features), 400)

        if ('type' not in geojson[0]) or geojson[0]['type'] != 'Feature':
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not Feature."
                .format(features), 400)

        # try to load features
        # read fields
        feature_fields = QgsJsonUtils.stringToFields(
            '{ "type": "FeatureCollection","features":' + features + '}',
            QTextCodec.codecForName("UTF-8"))
        # read features
        feature_list = QgsJsonUtils.stringToFeatureList(
            '{ "type": "FeatureCollection","features":' + features + '}',
            feature_fields, QTextCodec.codecForName("UTF-8"))

        # features not well formed
        if not feature_list:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FEATURES for 'Evaluate': not GeoJSON features array provided\n{}"
                .format(features), 400)

        # Extend layer fields with this provided in GeoJSON Features
        feat_fields = QgsFields(layer.fields())
        feat_fields.extend(feature_fields)

        # form scope
        addFormScope = params.get('FORM_SCOPE',
                                  '').lower() in ['true', '1', 't']

        # loop through provided features to evaluate expressions
        for f in feature_list:
            # clone the features with all attributes
            # those defined in layer + fields from GeoJSON Features
            feat = QgsFeature(feat_fields)
            feat.setGeometry(f.geometry())
            for field in f.fields():
                fname = field.name()
                if feat_fields.indexOf(fname) != -1:
                    feat.setAttribute(fname, f[fname])

            # Add form scope to expression context
            if addFormScope:
                exp_context.appendScope(
                    QgsExpressionContextUtils.formScope(feat))

            exp_context.setFeature(feat)
            exp_context.setFields(feat.fields())

            # Evaluate expressions with the new feature
            result = {}
            error = {}
            for k, exp in exp_map.items():
                if addFormScope:  # need to prepare the expression because the context as been updated with a new scope
                    exp.prepare(exp_context)
                value = exp.evaluate(exp_context)
                if exp.hasEvalError():
                    result[k] = None
                    error[k] = exp.evalErrorString()
                else:
                    result[k] = json.loads(QgsJsonUtils.encodeValue(value))
                    error[k] = exp.expression()
            body['results'].append(result)
            body['errors'].append(error)

        write_json_response(body, response)
        return
def exportJSONLayer(layer, eachPopup, precision, tmpFileName, exp_crs,
                    layerFileName, safeLayerName, minify, canvas,
                    restrictToExtent, iface, extent):
    cleanedLayer = writeTmpLayer(layer, eachPopup, restrictToExtent, iface,
                                 extent)
    if is25d(layer, canvas, restrictToExtent, extent):
        provider = cleanedLayer.dataProvider()
        provider.addAttributes([
            QgsField("height", QVariant.Double),
            QgsField("wallColor", QVariant.String),
            QgsField("roofColor", QVariant.String)
        ])
        cleanedLayer.updateFields()
        fields = cleanedLayer.pendingFields()
        renderer = layer.rendererV2()
        renderContext = QgsRenderContext.fromMapSettings(canvas.mapSettings())
        feats = layer.getFeatures()
        context = QgsExpressionContext()
        context.appendScope(QgsExpressionContextUtils.layerScope(layer))
        expression = QgsExpression('eval(@qgis_25d_height)')
        heightField = fields.indexFromName("height")
        wallField = fields.indexFromName("wallColor")
        roofField = fields.indexFromName("roofColor")
        renderer.startRender(renderContext, fields)
        cleanedLayer.startEditing()
        for feat in feats:
            context.setFeature(feat)
            height = expression.evaluate(context)
            if isinstance(renderer, QgsCategorizedSymbolRendererV2):
                classAttribute = renderer.classAttribute()
                attrValue = feat.attribute(classAttribute)
                catIndex = renderer.categoryIndexForValue(attrValue)
                categories = renderer.categories()
                symbol = categories[catIndex].symbol()
            elif isinstance(renderer, QgsGraduatedSymbolRendererV2):
                classAttribute = renderer.classAttribute()
                attrValue = feat.attribute(classAttribute)
                ranges = renderer.ranges()
                for range in ranges:
                    if (attrValue >= range.lowerValue()
                            and attrValue <= range.upperValue()):
                        symbol = range.symbol().clone()
            else:
                symbol = renderer.symbolForFeature2(feat, renderContext)
            wallColor = symbol.symbolLayer(1).subSymbol().color().name()
            roofColor = symbol.symbolLayer(2).subSymbol().color().name()
            cleanedLayer.changeAttributeValue(feat.id() + 1, heightField,
                                              height)
            cleanedLayer.changeAttributeValue(feat.id() + 1, wallField,
                                              wallColor)
            cleanedLayer.changeAttributeValue(feat.id() + 1, roofField,
                                              roofColor)
        cleanedLayer.commitChanges()
        renderer.stopRender(renderContext)

    writer = QgsVectorFileWriter
    options = []
    if precision != "maintain":
        options.append("COORDINATE_PRECISION=" + unicode(precision))
    writer.writeAsVectorFormat(cleanedLayer,
                               tmpFileName,
                               'utf-8',
                               exp_crs,
                               'GeoJson',
                               0,
                               layerOptions=options)

    with open(layerFileName, "w") as f2:
        f2.write("var json_" + unicode(safeLayerName) + "=")
        with open(tmpFileName, "r") as tmpFile:
            for line in tmpFile:
                if minify:
                    line = line.strip("\n\t ")
                    line = removeSpaces(line)
                f2.write(line)
        os.remove(tmpFileName)

    fields = layer.pendingFields()
    for field in fields:
        exportImages(layer, field.name(), layerFileName)
 def createExpressionContext(self) -> QgsExpressionContext:  # pylint: disable=missing-docstring, no-self-use
     context = QgsExpressionContext()
     scope = QgsExpressionContextScope()
     context.appendScope(scope)
     context.appendScope(vl1.createExpressionContextScope())
     return context
Example #44
0
    def processAlgorithm(self, progress):
        layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_LAYER))
        fieldName = self.getParameterValue(self.FIELD_NAME)
        fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)]
        width = self.getParameterValue(self.FIELD_LENGTH)
        precision = self.getParameterValue(self.FIELD_PRECISION)
        newField = self.getParameterValue(self.NEW_FIELD)
        formula = self.getParameterValue(self.FORMULA)

        output = self.getOutputFromName(self.OUTPUT_LAYER)

        fields = layer.fields()
        if newField:
            fields.append(QgsField(fieldName, fieldType, '', width, precision))

        writer = output.getVectorWriter(fields, layer.wkbType(),
                                        layer.crs())

        exp = QgsExpression(formula)

        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs().srsid())
        da.setEllipsoidalMode(
            iface.mapCanvas().mapSettings().hasCrsTransformEnabled())
        da.setEllipsoid(QgsProject.instance().readEntry(
            'Measure', '/Ellipsoid', GEO_NONE)[0])
        exp.setGeomCalculator(da)
        exp.setDistanceUnits(QgsProject.instance().distanceUnits())
        exp.setAreaUnits(QgsProject.instance().areaUnits())

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        if not exp.prepare(exp_context):
            raise GeoAlgorithmExecutionException(
                self.tr('Evaluation error: %s' % exp.evalErrorString()))

        outFeature = QgsFeature()
        outFeature.initAttributes(len(fields))
        outFeature.setFields(fields)

        error = ''
        calculationSuccess = True

        features = vector.features(layer)
        total = 100.0 / len(features)

        rownum = 1
        for current, f in enumerate(features):
            rownum = current + 1
            exp_context.setFeature(f)
            exp_context.lastScope().setVariable("row_number", rownum)
            value = exp.evaluate(exp_context)
            if exp.hasEvalError():
                calculationSuccess = False
                error = exp.evalErrorString()
                break
            else:
                outFeature.setGeometry(f.geometry())
                for fld in f.fields():
                    outFeature[fld.name()] = f[fld.name()]
                outFeature[fieldName] = value
                writer.addFeature(outFeature)

            progress.setPercentage(int(current * total))
        del writer

        if not calculationSuccess:
            raise GeoAlgorithmExecutionException(
                self.tr('An error occurred while evaluating the calculation '
                        'string:\n%s' % error))
Example #45
0
    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 createExpressionContext(self) -> QgsExpressionContext:  # pylint: disable=missing-docstring, no-self-use
     context = QgsExpressionContext()
     scope = QgsExpressionContextScope()
     scope.setVariable('some_var', 10)
     context.appendScope(scope)
     return context
Example #47
0
def handleAlgorithmResults(alg, context, feedback=None, showResults=True, parameters={}):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(QCoreApplication.translate('Postprocessing', 'Loading resulting layers'))
    i = 0

    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion())))
        try:
            layer = QgsProcessingUtils.mapLayerFromString(l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                set_layer_name(layer, details)

                '''If running a model, the execution will arrive here when an algorithm that is part of
                that model is executed. We check if its output is a final otuput of the model, and
                adapt the output name accordingly'''
                outputName = details.outputName
                expcontext = QgsExpressionContext()
                scope = QgsExpressionContextScope()
                expcontext.appendScope(scope)
                for out in alg.outputDefinitions():
                    if out.name() not in parameters:
                        continue
                    outValue = parameters[out.name()]
                    if hasattr(outValue, "sink"):
                        outValue = outValue.sink.valueAsString(expcontext)[0]
                    else:
                        outValue = str(outValue)
                    if outValue == l:
                        outputName = out.name()
                        break
                style = None
                if outputName:
                    style = RenderingStyles.getStyle(alg.id(), outputName)
                if style is None:
                    if layer.type() == QgsMapLayer.RasterLayer:
                        style = ProcessingConfig.getSetting(ProcessingConfig.RASTER_STYLE)
                    else:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(QCoreApplication.translate('Postprocessing', "Error loading result layer:") + "\n" + traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate('Postprocessing', "The following layers were not correctly generated.")
        msg += "<ul>" + "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>"
        msg += QCoreApplication.translate('Postprocessing', "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm.")
        feedback.reportError(msg)

    return len(wrongLayers) == 0
Example #48
0
def handleAlgorithmResults(alg,
                           context,
                           feedback=None,
                           showResults=True,
                           parameters={}):
    wrongLayers = []
    if feedback is None:
        feedback = QgsProcessingFeedback()
    feedback.setProgressText(
        QCoreApplication.translate('Postprocessing',
                                   'Loading resulting layers'))
    i = 0

    for l, details in context.layersToLoadOnCompletion().items():
        if feedback.isCanceled():
            return False

        if len(context.layersToLoadOnCompletion()) > 2:
            # only show progress feedback if we're loading a bunch of layers
            feedback.setProgress(
                100 * i / float(len(context.layersToLoadOnCompletion())))
        try:
            layer = QgsProcessingUtils.mapLayerFromString(
                l, context, typeHint=details.layerTypeHint)
            if layer is not None:
                details.setOutputLayerName(layer)
                '''If running a model, the execution will arrive here when an algorithm that is part of
                that model is executed. We check if its output is a final otuput of the model, and
                adapt the output name accordingly'''
                outputName = details.outputName
                expcontext = QgsExpressionContext()
                scope = QgsExpressionContextScope()
                expcontext.appendScope(scope)
                for out in alg.outputDefinitions():
                    if out.name() not in parameters:
                        continue
                    outValue = parameters[out.name()]
                    if hasattr(outValue, "sink"):
                        outValue = outValue.sink.valueAsString(expcontext)[0]
                    else:
                        outValue = str(outValue)
                    if outValue == l:
                        outputName = out.name()
                        break
                style = None
                if outputName:
                    style = RenderingStyles.getStyle(alg.id(), outputName)
                if style is None:
                    if layer.type() == QgsMapLayerType.RasterLayer:
                        style = ProcessingConfig.getSetting(
                            ProcessingConfig.RASTER_STYLE)
                    elif layer.type() == QgsMapLayerType.VectorLayer:
                        if layer.geometryType() == QgsWkbTypes.PointGeometry:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_POINT_STYLE)
                        elif layer.geometryType() == QgsWkbTypes.LineGeometry:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_LINE_STYLE)
                        else:
                            style = ProcessingConfig.getSetting(
                                ProcessingConfig.VECTOR_POLYGON_STYLE)
                if style:
                    layer.loadNamedStyle(style)

                # Load layer to layer tree root or to a specific group
                mapLayer = context.temporaryLayerStore().takeMapLayer(layer)
                group_name = ProcessingConfig.getSetting(
                    ProcessingConfig.RESULTS_GROUP_NAME)
                if group_name:
                    group = details.project.layerTreeRoot().findGroup(
                        group_name)
                    if not group:
                        group = details.project.layerTreeRoot().insertGroup(
                            0, group_name)

                    details.project.addMapLayer(mapLayer, False)
                    group.addLayer(mapLayer)
                else:
                    details.project.addMapLayer(mapLayer)

                if details.postProcessor():
                    details.postProcessor().postProcessLayer(
                        layer, context, feedback)

            else:
                wrongLayers.append(str(l))
        except Exception:
            QgsMessageLog.logMessage(
                QCoreApplication.translate(
                    'Postprocessing', "Error loading result layer:") + "\n" +
                traceback.format_exc(), 'Processing', Qgis.Critical)
            wrongLayers.append(str(l))
        i += 1

    feedback.setProgress(100)

    if wrongLayers:
        msg = QCoreApplication.translate(
            'Postprocessing',
            "The following layers were not correctly generated.")
        msg += "\n" + "\n".join(["• {}".format(lay)
                                 for lay in wrongLayers]) + '\n'
        msg += QCoreApplication.translate(
            'Postprocessing',
            "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm."
        )
        feedback.reportError(msg)

    return len(wrongLayers) == 0
    def processAlgorithm(self, parameters, context, feedback):
        path = self.parameterAsFile(parameters, self.INPUT, context)
        t_file = self.parameterAsVectorLayer(
            parameters,
            self.FILE_TABLE,
            context
        )
        t_troncon = self.parameterAsVectorLayer(
            parameters,
            self.SEGMENT_TABLE,
            context
        )
        t_obs = self.parameterAsVectorLayer(
            parameters,
            self.OBSERVATIONS_TABLE,
            context
        )
        t_regard = self.parameterAsVectorLayer(
            parameters,
            self.MANHOLES_TABLE,
            context
        )

        paths = path.split(';')
        if len(paths) != 1:
            raise QgsProcessingException(
                tr('* ERREUR: 1 fichier a la fois {}.').format(path)
            )

        if not os.path.exists(path):
            raise QgsProcessingException(
                tr('* ERREUR: {} n\'existe pas.').format(path)
            )

        # add date fields to itv file table
        # relation_aggregate(
        #     'itv_tronco_id_file_itv_file20_id',
        #     'max',
        #     "abf"
        # )
        if 'date_debut' not in t_file.dataProvider().fields().names():
            with edit(t_file):
                res = t_file.dataProvider().addAttributes([
                    QgsField("date_debut", QVariant.String),
                    QgsField("date_fin", QVariant.String)
                ])
                if res:
                    t_file.updateFields()

        feat_file = None
        with open(path, 'rb') as f:
            basename = os.path.basename(path)
            md = hashlib.md5()
            md.update(f.read())
            hashcontent = md.hexdigest()

            feat_file = QgsVectorLayerUtils.createFeature(t_file)
            feat_file.setAttribute('basename', basename)
            feat_file.setAttribute('hashcontent', hashcontent)

        if not feat_file:
            raise QgsProcessingException(
                tr(
                    '* ERREUR: le fichier {} n\'a pas été lu '
                    'correctement.'
                ).format(path)
            )

        exp_context = QgsExpressionContext()
        exp_context.appendScope(
            QgsExpressionContextUtils.globalScope()
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project())
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.layerScope(t_file)
        )

        exp_str = QgsExpression.createFieldEqualityExpression(
            'basename', feat_file['basename']
        ) + ' AND ' + QgsExpression.createFieldEqualityExpression(
            'hashcontent', feat_file['hashcontent']
        )
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression {} has eval error: {}').format(
                    exp.expression(), exp.evalErrorString()
                )
            )

        request = QgsFeatureRequest(exp, exp_context)
        request.setLimit(1)

        for _t in t_file.getFeatures(request):
            raise QgsProcessingException(
                tr('* ERREUR: le fichier {} a deja ete lu').format(
                    path
                )
            )

        exp_str = QgsExpression.createFieldEqualityExpression(
            'hashcontent', feat_file['hashcontent']
        )
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr('* ERROR: Expression {} has eval error: {}').format(
                    (exp.expression(), exp.evalErrorString())
                )
            )

        request = QgsFeatureRequest(exp, exp_context)
        request.setLimit(1)

        for _t in t_file.getFeatures(request):
            raise QgsProcessingException(
                tr(
                    '* ERREUR: le fichier {} semble deja avoir ete lu'
                ).format(path)
            )

        exp_context = QgsExpressionContext()
        exp_context.appendScope(
            QgsExpressionContextUtils.globalScope()
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project())
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.layerScope(t_troncon)
        )

        exp_str = 'maximum("id")'
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr(
                    '* ERROR: Expression {} has eval error: {}'
                ).format(exp.expression(), exp.evalErrorString())
            )

        last_t_id = exp.evaluate(exp_context)
        if not last_t_id:
            last_t_id = 0

        exp_context = QgsExpressionContext()
        exp_context.appendScope(
            QgsExpressionContextUtils.globalScope()
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(context.project())
        )
        exp_context.appendScope(
            QgsExpressionContextUtils.layerScope(t_regard)
        )

        exp_str = 'maximum("id")'
        exp = QgsExpression(exp_str)

        exp.prepare(exp_context)
        if exp.hasEvalError():
            raise QgsProcessingException(
                tr(
                    '* ERROR: Expression {} has eval error: {}'
                ).format(exp.expression(), exp.evalErrorString())
            )

        last_r_id = exp.evaluate(exp_context)
        if not last_r_id:
            last_r_id = 0

        # lecture des entetes
        ENCODING = 'ISO-8859-1'
        LANG = 'fr'
        DELIMITER = ','
        DECIMAL = '.'
        QUOTECHAR = '"'
        VERSION = ''

        with open(path, 'rb') as f:
            for line in f:
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.SUCCESS: 0}
                try:
                    line = line.decode()
                except UnicodeDecodeError:
                    raise QgsProcessingException(
                        'Error while reading {}'.format(path)
                    )
                # remove break line
                line = line.replace('\n', '').replace('\r', '')
                if line.startswith('#'):
                    if line.startswith('#A'):
                        if line.startswith('#A1'):
                            ENCODING = line[4:]
                            if ENCODING.find(':') != -1:
                                ENCODING = ENCODING[:ENCODING.find(':')]
                        elif line.startswith('#A2'):
                            LANG = line[4:]
                        elif line.startswith('#A3'):
                            DELIMITER = line[4:]
                        elif line.startswith('#A4'):
                            DECIMAL = line[4:]
                        elif line.startswith('#A5'):
                            QUOTECHAR = line[4:]
                    else:
                        break

        # Dialect CSV pour la lecture des tableaux de valeurs du
        # fichier d'ITV
        class itvDialect(csv.Dialect):
            strict = True
            skipinitialspace = True
            quoting = csv.QUOTE_MINIMAL
            delimiter = DELIMITER
            quotechar = QUOTECHAR
            lineterminator = '\r\n'

        # Liste des troncons, observations et regards
        troncons = []
        regards = []
        observations = []

        # Lectures des donnees
        with open(path, 'rb') as f:
            # Identifiant de départ
            t_id = last_t_id
            r_id = last_r_id
            # Entête
            header = []
            # Nom du tableau
            array = ''
            # Observations de troncons ?
            obs_for_troncon = False
            # initialisation du Dialect CSV pour ITV
            dia = itvDialect()
            # Lecture ligne à ligne du fichier
            for line in f:
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.SUCCESS: 0}
                # Decoding line en utilisant l'encoding du fichier
                line = line.decode(ENCODING)
                # remove break line
                line = line.replace('\n', '').replace('\r', '')
                # Ligne commençant par un # est une ligne d'entête
                if line.startswith('#'):
                    # Entête de troncon ou regard
                    if line.startswith('#B'):
                        l_b = io.StringIO(line[5:])
                        for l_r in csv.reader(l_b, dia):
                            header = l_r
                            break
                        array = line[1:4]
                        continue
                    # Entête d'observation
                    elif line.startswith('#C'):
                        if header[0].startswith('A'):
                            obs_for_troncon = True
                        else:
                            obs_for_troncon = False
                        l_b = io.StringIO(line[3:])
                        for l_r in csv.reader(l_b, dia):
                            header = l_r
                            break
                        array = line[1:2]
                        continue
                    # Fin d'observation
                    elif line.startswith('#Z'):
                        header = []
                        array = ''
                        obs_for_troncon = False
                        continue
                # La ligne contient des donnees
                else:
                    if not header:  # an error in the file structure
                        continue
                    l_b = io.StringIO(line)
                    for l_r in csv.reader(l_b, dia):
                        data = l_r
                        row = list(
                            zip(
                                [h.lower() for h in header],
                                [t for t in data]
                            )
                        )
                        # observation
                        if array == 'C':
                            if obs_for_troncon:
                                observations.append(
                                    row + [('id_troncon', t_id)]
                                )
                        # Premiere ligne de description d'un troncon ou regard
                        elif array == 'B01':
                            if header[0].startswith('A'):
                                t_id += 1
                                troncons.append([('id', t_id)] + row)
                            elif header[0].startswith('C'):
                                r_id += 1
                                regards.append([('id', r_id)] + row)
                        # Ligne complémentaire de description
                        else:
                            if header[0].startswith('A'):
                                troncons[-1] += row
                            elif header[0].startswith('C'):
                                regards[-1] += row

        # Recuperation des references de noeuds et dates
        itv_dates = []
        regard_node_refs = []
        regard_ref_id = {}
        max_r_id = last_r_id
        for reg in regards:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            d_rg = dict(reg)
            if d_rg['caa'] and d_rg['caa'] not in regard_node_refs:
                regard_node_refs.append(d_rg['caa'])
                regard_ref_id[d_rg['caa']] = d_rg['id']
            if d_rg['id'] and d_rg['id'] > max_r_id:
                max_r_id = d_rg['id']
            if 'cbf' in d_rg and d_rg['cbf'] not in itv_dates:
                itv_dates.append(d_rg['cbf'])

        node_refs = []
        for tro in troncons:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            d_tr = dict(tro)
            # The nodes ref are stored in AAB, AAD, AAF and AAT
            if 'aab' in d_tr and \
                    d_tr['aab'] and \
                    d_tr['aab'] not in regard_node_refs and \
                    d_tr['aab'] not in node_refs:
                node_refs.append(d_tr['aab'])
            if 'aad' in d_tr and \
                    d_tr['aad'] and \
                    d_tr['aad'] not in regard_node_refs and \
                    d_tr['aad'] not in node_refs:
                node_refs.append(d_tr['aad'])
            if 'aaf' in d_tr and \
                    d_tr['aaf'] and \
                    d_tr['aaf'] not in regard_node_refs and \
                    d_tr['aaf'] not in node_refs:
                node_refs.append(d_tr['aaf'])
            if 'aat' in d_tr and \
                    d_tr['aat'] and \
                    d_tr['aat'] not in regard_node_refs and \
                    d_tr['aat'] not in node_refs:
                node_refs.append(d_tr['aat'])
            if 'abf' in d_tr and d_tr['abf'] not in itv_dates:
                itv_dates.append(d_tr['abf'])

        # Ajout des regards manquant
        for n_ref in node_refs:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            max_r_id += 1
            regards.append([('id', max_r_id), ('caa', n_ref)])
            regard_ref_id[n_ref] = max_r_id

        # Ajout des identifiants de regards aux tronçons
        regard_refs = regard_ref_id.keys()
        for tro in troncons:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            d_tr = dict(tro)
            # If AAD is not defined then it is equal to AAB
            if 'aad' not in d:
                d['aad'] = d['aab']
            if d_tr['aad'] and \
                    d_tr['aad'] in regard_refs:
                tro += [('id_regard1', regard_ref_id[d_tr['aad']])]
            if d_tr['aaf'] and \
                    d_tr['aaf'] in regard_refs:
                tro += [('id_regard2', regard_ref_id[d_tr['aaf']])]
            if 'aat' in d_tr.keys() and \
                    d_tr['aat'] and \
                    d_tr['aat'] in regard_refs:
                tro += [('id_regard3', regard_ref_id[d_tr['aat']])]

        # Verification des champs
        fields = provider_fields(t_troncon.fields())
        for tro in troncons:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            for key, val in tro:
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.SUCCESS: 0}

                if fields.indexOf(key) == -1:
                    raise QgsProcessingException(
                        tr(
                            '* ERREUR dans le fichier : '
                            'le champs de tronçon "{}" est inconnue'
                        ).format(key)
                    )
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        try:
                            float(val.replace(DECIMAL, '.'))
                        except BaseException:
                            raise QgsProcessingException(
                                tr(
                                    '* ERREUR dans le fichier : '
                                    'le champs de tronçon "{}" est '
                                    'numérique mais pas la valeur "{}"'
                                ).format(key, val)
                            )

        fields = provider_fields(t_obs.fields())
        for obs in observations:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            for key, val in obs:
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.SUCCESS: 0}

                if fields.indexOf(key) == -1:
                    raise QgsProcessingException(
                        tr(
                            '* ERREUR dans le fichier : '
                            'le champs d\'observation "{}" est '
                            'inconnue'
                        ).format(key)
                    )
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        try:
                            float(val.replace(DECIMAL, '.'))
                        except BaseException:
                            raise QgsProcessingException(
                                tr(
                                    '* ERREUR dans le fichier : '
                                    'le champs d\'observation "{}" est '
                                    'numérique mais pas la valeur "{}"'
                                ).format(key, val)
                            )

        fields = provider_fields(t_regard.fields())
        for reg in regards:
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                return {self.SUCCESS: 0}

            for key, val in reg:
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    return {self.SUCCESS: 0}

                if fields.indexOf(key) == -1:
                    raise QgsProcessingException(
                        tr(
                            '* ERREUR dans le fichier : '
                            'le champs de regard "{}" est inconnue'
                        ).format(key)
                    )
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        try:
                            float(val.replace(DECIMAL, '.'))
                        except BaseException:
                            raise QgsProcessingException(
                                tr(
                                    '* ERREUR dans le fichier : '
                                    'le champs de regard "{}" est '
                                    'numérique mais pas la valeur "{}"'
                                ).format(key, val)
                            )

        # Finalisation objet fichier
        feat_file.setAttribute('encoding', ENCODING)
        feat_file.setAttribute('lang', LANG)
        if VERSION:
            feat_file.setAttribute('version', VERSION)
        if itv_dates:
            feat_file.setAttribute('date_debut', min(itv_dates))
            feat_file.setAttribute('date_fin', max(itv_dates))

        # Stop the algorithm if cancel button has been clicked
        if feedback.isCanceled():
            return {self.SUCCESS: 0}

        # Ajout de l'objet fichier
        t_file.startEditing()
        (res, outFeats) = t_file.dataProvider().addFeatures([feat_file])
        if not res or not outFeats:
            raise QgsProcessingException(
                tr(
                    '* ERREUR: lors de l\'enregistrement du fichier {}'
                ).format(', '.join(t_file.dataProvider().errors()))
            )
        if not t_file.commitChanges():
            raise QgsProcessingException(
                tr('* ERROR: Commit {}.').format(t_file.commitErrors())
            )

        # Mise a jour de l'identifiant de l'objet fichier
        feat_file.setAttribute('id', outFeats[0]['id'])

        # Creation des objets troncons
        features = []
        fields = provider_fields(t_troncon.fields())
        for tro in troncons:
            feat_t = QgsVectorLayerUtils.createFeature(t_troncon)
            feat_t.setAttribute('id_file', feat_file['id'])
            for key, val in tro:
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        feat_t.setAttribute(
                            key, float(val.replace(DECIMAL, '.'))
                        )
                else:
                    feat_t.setAttribute(key, val)
            features.append(feat_t)

        # Ajout des objets troncons
        if features:
            t_troncon.startEditing()
            (res, outFeats) = t_troncon.dataProvider().addFeatures(features)
            if not res or not outFeats:
                raise QgsProcessingException(
                    tr(
                        '* ERREUR: lors de l\'enregistrement '
                        'des troncon {}'
                    ).format(
                        ', '.join(t_troncon.dataProvider().errors())
                    )
                )
            if not t_troncon.commitChanges():
                raise QgsProcessingException(
                    tr('* ERROR: Commit {}.').format(
                        t_troncon.commitErrors()
                    )
                )

        # Creation des objets observations
        features = []
        fields = provider_fields(t_obs.fields())
        for obs in observations:
            feat_o = QgsVectorLayerUtils.createFeature(t_obs)
            feat_o.setAttribute('id_file', feat_file['id'])
            for key, val in obs:
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        feat_o.setAttribute(
                            key, float(val.replace(DECIMAL, '.'))
                        )
                else:
                    feat_o.setAttribute(key, val)
            features.append(feat_o)

        # Ajout des objets observations
        if features:
            t_obs.startEditing()
            (res, outFeats) = t_obs.dataProvider().addFeatures(features)
            if not res or not outFeats:
                raise QgsProcessingException(
                    tr(
                        '* ERREUR: lors de l\'enregistrement '
                        'des observations {}'
                    ).format(
                        ', '.join(t_obs.dataProvider().errors())
                    )
                )
            if not t_obs.commitChanges():
                raise QgsProcessingException(
                    tr('* ERROR: Commit {}.').format(
                        t_obs.commitErrors()
                    )
                )

        # Creation des objets regards
        features = []
        fields = provider_fields(t_regard.fields())
        for reg in regards:
            feat_r = QgsVectorLayerUtils.createFeature(t_regard)
            feat_r.setAttribute('id_file', feat_file['id'])
            for key, val in reg:
                field = fields.field(key)
                if isinstance(val, str) and field.isNumeric():
                    if val:
                        feat_r.setAttribute(
                            key, float(val.replace(DECIMAL, '.'))
                        )
                else:
                    feat_r.setAttribute(key, val)
            features.append(feat_r)

        # Ajout des objets regards
        if features:
            t_regard.startEditing()
            (res, outFeats) = t_regard.dataProvider().addFeatures(
                features
            )
            if not res or not outFeats:
                raise QgsProcessingException(
                    tr(
                        '* ERREUR: lors de l\'enregistrement '
                        'des regards {}'
                    ).format(
                        ', '.join(t_regard.dataProvider().errors())
                    )
                )
            if not t_regard.commitChanges():
                raise QgsProcessingException(
                    tr('* ERROR: Commit %s.').format(
                        t_regard.commitErrors()
                    )
                )

        # Returns empty dict if no outputs
        return {self.SUCCESS: 1}
Example #50
0
    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 )
Example #51
0
def layer_value(feature, layer, defaultconfig):
    if not canvas:
        roam.utils.warning("No canvas set for using layer_values default function")
        return None
    layers = []
    # layer name can also be a list of layers to search
    layername = defaultconfig['layer']
    if isinstance(layername, basestring):
        layers.append(layername)
    else:
        layers = layername

    expression = defaultconfig['expression']
    field = defaultconfig['field']

    for searchlayer in layers:
        try:
            searchlayer = QgsMapLayerRegistry.instance().mapLayersByName(searchlayer)[0]
        except IndexError:
            RoamEvents.raisemessage("Missing layer",
                                    "Unable to find layer used in widget expression {}".format(searchlayer),
                                    level=1)
            roam.utils.warning("Unable to find expression layer {}".format(searchlayer))
            return
        if feature.geometry():
            rect = feature.geometry().boundingBox()
            if layer.geometryType() == QGis.Point:
                point = feature.geometry().asPoint()
                rect = QgsRectangle(point.x(), point.y(), point.x() + 10, point.y() + 10)

            rect.scale(20)

            rect = canvas.mapRenderer().mapToLayerCoordinates(layer, rect)
            rq = QgsFeatureRequest().setFilterRect(rect)\
                                    .setFlags(QgsFeatureRequest.ExactIntersect)
            features = searchlayer.getFeatures(rq)
        else:
            features = searchlayer.getFeatures()

        expression = expression.replace("$roamgeometry", "@roamgeometry")

        exp = QgsExpression(expression)
        exp.prepare(searchlayer.pendingFields())
        if exp.hasParserError():
            error = exp.parserErrorString()
            roam.utils.warning(error)

        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        context.appendScope(scope)
        scope.setVariable("roamgeometry", feature.geometry())

        for f in features:
            context.setFeature(f)
            value = exp.evaluate(context)
            if exp.hasEvalError():
                error = exp.evalErrorString()
                roam.utils.warning(error)
            if value:
                return f[field]

    raise DefaultError('No features found')
Example #52
0
class VectorLayer:
    def __init__(self, settings, layer, materialManager, modelManager):
        """layer: Layer object"""
        self.settings = settings
        self.renderContext = QgsRenderContext.fromMapSettings(
            settings.mapSettings)

        self.mapLayer = layer.mapLayer
        self.name = self.mapLayer.name() if self.mapLayer else "no title"
        self.properties = layer.properties

        self.expressionContext = QgsExpressionContext()
        self.expressionContext.appendScope(
            QgsExpressionContextUtils.layerScope(self.mapLayer))

        self.objectType = ObjectType.typeByName(
            self.properties.get("comboBox_ObjectType"),
            self.mapLayer.geometryType())

        self.materialManager = materialManager
        self.modelManager = modelManager
        self.colorNames = []  # for random color

        self.transform = QgsCoordinateTransform(self.mapLayer.crs(),
                                                settings.crs,
                                                QgsProject.instance())
        self.geomType = self.mapLayer.geometryType()

        # attributes
        self.writeAttrs = self.properties.get("checkBox_ExportAttrs", False)
        self.labelAttrIndex = self.properties.get("comboBox_Label", None)
        self.fieldIndices = []
        self.fieldNames = []

        if self.writeAttrs:
            for index, field in enumerate(self.mapLayer.fields()):
                if field.editorWidgetSetup().type() != "Hidden":
                    self.fieldIndices.append(index)
                    self.fieldNames.append(field.displayName())

        # expressions
        self._exprs = {}
        self.exprAlt = QgsExpression(
            self.properties.get("fieldExpressionWidget_altitude") or "0")
        self.exprLabel = QgsExpression(
            self.properties.get("labelHeightWidget", {}).get("editText")
            or "0")

    def features(self, request=None):
        mapTo3d = self.settings.mapTo3d()
        be = self.settings.baseExtent()
        beGeom = be.geometry()
        rotation = be.rotation()
        fields = self.mapLayer.fields()

        # initialize symbol rendering, and then get features (geometry, attributes, color, etc.)
        self.renderer = self.mapLayer.renderer().clone()
        self.renderer.startRender(self.renderContext, self.mapLayer.fields())

        for f in self.mapLayer.getFeatures(request or QgsFeatureRequest()):
            geometry = f.geometry()
            if geometry is None:
                logMessage("null geometry skipped")
                continue

            # coordinate transformation - layer crs to project crs
            geom = QgsGeometry(geometry)
            if geom.transform(self.transform) != 0:
                logMessage("Failed to transform geometry")
                continue

            # check if geometry intersects with the base extent (rotated rect)
            if rotation and not beGeom.intersects(geom):
                continue

            # set feature to expression context
            self.expressionContext.setFeature(f)

            # evaluate expression
            altitude = float(
                self.exprAlt.evaluate(self.expressionContext) or 0)
            swVals = self.styleWidgetValues(f)

            attrs = labelHeight = None
            if self.writeAttrs:
                attrs = [
                    fields[i].displayString(f.attribute(i))
                    for i in self.fieldIndices
                ]

                if self.hasLabel():
                    labelHeight = float(
                        self.exprLabel.evaluate(self.expressionContext)
                        or 0) * mapTo3d.multiplierZ

            # create a feature object
            yield Feature(self, geom, altitude, swVals, attrs, labelHeight)

        self.renderer.stopRender(self.renderContext)

    def evaluateExpression(self, expr_str, f):
        if expr_str not in self._exprs:
            self._exprs[expr_str] = QgsExpression(expr_str)

        self.expressionContext.setFeature(f)
        return self._exprs[expr_str].evaluate(self.expressionContext)

    def readFillColor(self, vals, f):
        return self._readColor(vals, f)

    def readBorderColor(self, vals, f):
        return self._readColor(vals, f, isBorder=True)

    # read color from COLOR or OPTIONAL_COLOR widget
    def _readColor(self, widgetValues, f, isBorder=False):
        mode = widgetValues["comboData"]
        if mode == OptionalColorWidgetFunc.NONE:
            return None

        if mode == ColorWidgetFunc.EXPRESSION:
            val = self.evaluateExpression(widgetValues["editText"], f)
            try:
                if isinstance(val, str):
                    a = val.split(",")
                    if len(a) >= 3:
                        a = [max(0, min(int(c), 255)) for c in a[:3]]
                        return "0x{:02x}{:02x}{:02x}".format(a[0], a[1], a[2])
                    return val.replace("#", "0x")

                raise
            except:
                logMessage("Wrong color value: {}".format(val))
                return "0"

        if mode == ColorWidgetFunc.RANDOM or f is None:
            self.colorNames = self.colorNames or QColor.colorNames()
            color = random.choice(self.colorNames)
            self.colorNames.remove(color)
            return QColor(color).name().replace("#", "0x")

        # feature color
        symbols = self.renderer.symbolsForFeature(f, self.renderContext)
        if not symbols:
            logMessage(
                "Symbol for feature not found. Please use a simple renderer for {0}."
                .format(self.name))
            return "0"

        symbol = symbols[0]
        if isBorder:
            sl = symbol.symbolLayer(0)
            if sl:
                return sl.strokeColor().name().replace("#", "0x")

        return symbol.color().name().replace("#", "0x")

    def readOpacity(self, widgetValues, f):
        vals = widgetValues

        if vals["comboData"] == OpacityWidgetFunc.EXPRESSION:
            try:
                val = self.evaluateExpression(widgetValues["editText"], f)
                return min(max(0, val), 100) / 100
            except:
                logMessage("Wrong opacity value: {}".format(val))
                return 1

        symbols = self.renderer.symbolsForFeature(f, self.renderContext)
        if not symbols:
            logMessage(
                "Symbol for feature not found. Please use a simple renderer for {0}."
                .format(self.name))
            return 1

        symbol = symbols[0]
        return self.mapLayer.opacity() * symbol.opacity()

    @classmethod
    def toFloat(cls, val):
        try:
            return float(val)
        except Exception as e:
            logMessage('{0} (value: {1})'.format(e.message, str(val)))
            return 0

    # functions to read values from height widget (z coordinate)
    def useZ(self):
        return self.properties.get("radioButton_zValue", False)

    def useM(self):
        return self.properties.get("radioButton_mValue", False)

    def isHeightRelativeToDEM(self):
        return self.properties.get("comboBox_altitudeMode") is not None

    def hasLabel(self):
        return bool(self.labelAttrIndex is not None)

    # read values from style widgets
    def styleWidgetValues(self, f):
        vals = []
        for i in range(16):  # big number for style count
            widgetValues = self.properties.get("styleWidget" + str(i))
            if not widgetValues:
                break

            widgetType = widgetValues["type"]
            comboData = widgetValues.get("comboData")
            if widgetType == StyleWidget.COLOR:
                vals.append(self.readFillColor(widgetValues, f))

            elif widgetType == StyleWidget.OPACITY:
                vals.append(self.readOpacity(widgetValues, f))

            elif widgetType in (StyleWidget.EXPRESSION,
                                StyleWidget.LABEL_HEIGHT):
                expr = widgetValues["editText"]
                val = self.evaluateExpression(expr, f)
                if val:
                    vals.append(val)
                else:
                    if val is None:
                        logMessage(
                            "Failed to evaluate expression: {} ({})".format(
                                expr, self.name))
                    else:  # if val.isNull():
                        logMessage("NULL was treated as zero. ({})".format(
                            self.name))
                    vals.append(0)

            elif widgetType == StyleWidget.OPTIONAL_COLOR:
                vals.append(self.readBorderColor(widgetValues, f))

            elif widgetType == StyleWidget.CHECKBOX:
                vals.append(widgetValues["checkBox"])

            elif widgetType == StyleWidget.COMBOBOX:
                vals.append(widgetValues["comboData"])

            elif widgetType == StyleWidget.FILEPATH:
                expr = widgetValues["editText"]
                val = self.evaluateExpression(expr, f)
                if val is None:
                    logMessage("Failed to evaluate expression: " + expr)
                vals.append(val or "")

            elif widgetType == StyleWidget.COLOR_TEXTURE:
                if comboData == ColorTextureWidgetFunc.MAP_CANVAS:
                    vals.append(comboData)
                elif comboData == ColorTextureWidgetFunc.LAYER:
                    vals.append(widgetValues.get("layerIds", []))
                else:
                    vals.append(self.readFillColor(widgetValues, f))

            else:
                logMessage("Widget type {} not found.".format(widgetType))
                vals.append(None)

        return vals
Example #53
0
class VectorPropertyReader:

    def __init__(self, objectTypeManager, renderContext, layer, properties):
        assert(properties is not None)
        self.renderContext = renderContext
        self.expressionContext = QgsExpressionContext()
        self.expressionContext.appendScope(QgsExpressionContextUtils.layerScope(layer))
        self.layer = layer
        properties = properties or {}
        self.properties = properties

        if properties:
            self.objType = objectTypeManager.objectType(layer.geometryType(), properties["comboBox_ObjectType"])
            self.visible = properties.get("visible", True)
        else:
            self.visible = False

        self._exprs = {}
        self.exprAlt = QgsExpression(properties.get("fieldExpressionWidget_altitude") or "0")
        self.exprLabel = QgsExpression(properties.get("labelHeightWidget", {}).get("editText") or "0")

    def evaluateExpression(self, expr_str, f):
        if expr_str not in self._exprs:
            self._exprs[expr_str] = QgsExpression(expr_str)

        self.expressionContext.setFeature(f)
        return self._exprs[expr_str].evaluate(self.expressionContext)

    def readFillColor(self, vals, f):
        return self._readColor(vals, f)

    def readBorderColor(self, vals, f):
        return self._readColor(vals, f, isBorder=True)

    # read color from COLOR or OPTIONAL_COLOR widget
    def _readColor(self, widgetValues, f, isBorder=False):
        global colorNames

        mode = widgetValues["comboData"]
        if mode == OptionalColorWidgetFunc.NONE:
            return None

        if mode == ColorWidgetFunc.EXPRESSION:
            val = self.evaluateExpression(widgetValues["editText"], f)
            try:
                if isinstance(val, str):
                    a = val.split(",")
                    if len(a) >= 3:
                        a = [max(0, min(int(c), 255)) for c in a[:3]]
                        return "0x{:02x}{:02x}{:02x}".format(a[0], a[1], a[2])
                    return val.replace("#", "0x")

                raise
            except:
                logMessage("Wrong color value: {}".format(val))
                return "0"

        if mode == ColorWidgetFunc.RANDOM or f is None:
            if len(colorNames) == 0:
                colorNames = QColor.colorNames()
            colorName = random.choice(colorNames)
            colorNames.remove(colorName)
            return QColor(colorName).name().replace("#", "0x")

        # feature color
        symbol = self.layer.renderer().symbolForFeature(f, self.renderContext)
        if symbol is None:
            logMessage('Symbol for feature not found. Please use a simple renderer for {0}.'.format(self.layer.name()))
            return "0"

        else:
            sl = symbol.symbolLayer(0)
            if sl:
                if isBorder:
                    return sl.strokeColor().name().replace("#", "0x")

                if symbol.hasDataDefinedProperties():
                    expr = sl.dataDefinedProperty("color")
                    if expr:
                        # data defined color
                        rgb = expr.evaluate(f, f.fields())

                        # "rrr,ggg,bbb" (dec) to "0xRRGGBB" (hex)
                        r, g, b = [max(0, min(int(c), 255)) for c in rgb.split(",")[:3]]
                        return "0x{:02x}{:02x}{:02x}".format(r, g, b)

        return symbol.color().name().replace("#", "0x")

    def readOpacity(self, widgetValues, f):
        vals = widgetValues

        if vals["comboData"] == OpacityWidgetFunc.EXPRESSION:
            try:
                val = self.evaluateExpression(widgetValues["editText"], f)
                return min(max(0, val), 100) / 100
            except:
                logMessage("Wrong opacity value: {}".format(val))
                return 1

        symbol = self.layer.renderer().symbolForFeature(f, self.renderContext)
        if symbol is None:
            logMessage('Symbol for feature not found. Please use a simple renderer for {0}.'.format(self.layer.name()))
            return 1
        # TODO [data defined property]
        return self.layer.opacity() * symbol.opacity()

    @classmethod
    def toFloat(cls, val):
        try:
            return float(val)
        except Exception as e:
            logMessage('{0} (value: {1})'.format(e.message, str(val)))
            return 0

    # functions to read values from height widget (z coordinate)
    def useZ(self):
        return self.properties.get("radioButton_zValue", False)

    def useM(self):
        return self.properties.get("radioButton_mValue", False)

    def isHeightRelativeToDEM(self):
        return self.properties.get("comboBox_altitudeMode") is not None

    def altitude(self):
        return float(self.exprAlt.evaluate(self.expressionContext) or 0)

    def labelHeight(self):
        return float(self.exprLabel.evaluate(self.expressionContext) or 0)

    def setContextFeature(self, f):
        self.expressionContext.setFeature(f)

    # read values from style widgets
    def values(self, f):
        assert(f is not None)
        vals = []
        for i in range(16):   # big number for style count
            p = "styleWidget" + str(i)
            if p not in self.properties:
                break

            widgetValues = self.properties[p]
            if len(widgetValues) == 0:
                break

            widgetType = widgetValues["type"]
            comboData = widgetValues.get("comboData")
            if widgetType == StyleWidget.COLOR:
                vals.append(self.readFillColor(widgetValues, f))

            elif widgetType == StyleWidget.OPTIONAL_COLOR:
                vals.append(self.readBorderColor(widgetValues, f))

            elif widgetType == StyleWidget.COLOR_TEXTURE:
                if comboData == ColorTextureWidgetFunc.MAP_CANVAS:
                    vals.append(comboData)
                elif comboData == ColorTextureWidgetFunc.LAYER:
                    vals.append(widgetValues.get("layerIds", []))
                else:
                    vals.append(self.readFillColor(widgetValues, f))

            elif widgetType == StyleWidget.OPACITY:
                vals.append(self.readOpacity(widgetValues, f))

            elif widgetType == StyleWidget.CHECKBOX:
                vals.append(widgetValues["checkBox"])

            else:
                expr = widgetValues["editText"]
                val = self.evaluateExpression(expr, f)
                if val is None:
                    logMessage("Failed to evaluate expression: " + expr)
                    if widgetType == StyleWidget.FILEPATH:
                        val = ""
                    else:
                        val = 0
                vals.append(val)
        return vals
Example #54
0
    def getFeatureWithFormScope(self, params: Dict[str, str],
                                response: QgsServerResponse,
                                project: QgsProject) -> None:
        """ Get filtered features with a form scope
        In parameters:
            LAYER=wms-layer-name
            FILTER=An expression to filter layer
            FORM_FEATURE={"type": "Feature", "geometry": {}, "properties": {}}
            // optionals
            FIELDS=list of requested field separated by comma
            WITH_GEOMETRY=False
        """
        layername = params.get('LAYER', '')
        if not layername:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = findVectorLayer(layername, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'VirtualField': {} provided".
                format(layername), 400)

        # get filter
        exp_filter = params.get('FILTER', '')
        if not exp_filter:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FILTER parameter is mandatory",
                400)

        # get form feature
        form_feature = params.get('FORM_FEATURE', '')
        if not form_feature:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE parameter is mandatory",
                400)

        # Check features
        geojson = {}
        try:
            geojson = json.loads(form_feature)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads form feature '{}' exception:\n{}".format(
                    form_feature, traceback.format_exc()), "lizmap",
                Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed"
                .format(form_feature), 400)

        if not geojson or not isinstance(geojson, dict):
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed"
                .format(form_feature), 400)

        if ('type' not in geojson) or geojson['type'] != 'Feature':
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed: type not defined or not Feature."
                .format(form_feature), 400)

        # try to load form feature
        # read fields
        form_feature_fields = QgsJsonUtils.stringToFields(
            form_feature, QTextCodec.codecForName("UTF-8"))
        # read features
        form_feature_list = QgsJsonUtils.stringToFeatureList(
            form_feature, form_feature_fields,
            QTextCodec.codecForName("UTF-8"))

        # features not well formed
        if not form_feature_list:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n{}"
                .format(form_feature), 400)

        if len(form_feature_list) != 1:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n{}"
                .format(form_feature), 400)

        # Get the form feature
        form_feat = form_feature_list[0]

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(
            QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))
        exp_context.appendScope(QgsExpressionContextUtils.formScope(form_feat))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # Get filter expression
        exp_f = QgsExpression(exp_filter)
        exp_f.setGeomCalculator(da)
        exp_f.setDistanceUnits(project.distanceUnits())
        exp_f.setAreaUnits(project.areaUnits())

        if exp_f.hasParserError():
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FILTER for 'GetFeatureWithFormScope': Error \"{}\": {}"
                .format(exp_filter, exp_f.parserErrorString()), 400)

        if not exp_f.isValid():
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FILTER for 'GetFeatureWithFormScope': Expression not valid \"{}\""
                .format(exp_filter), 400)

        exp_f.prepare(exp_context)

        req = QgsFeatureRequest(exp_f, exp_context)

        # With geometry
        withGeom = params.get('WITH_GEOMETRY',
                              '').lower() in ['true', '1', 't']
        if not withGeom:
            req.setFlags(QgsFeatureRequest.NoGeometry)

        # Fields
        pkAttributes = layer.primaryKeyAttributes()
        attributeList = [i for i in pkAttributes]
        fields = layer.fields()
        r_fields = [
            f.strip() for f in params.get('FIELDS', '').split(',') if f
        ]
        for f in r_fields:
            attributeList.append(fields.indexOf(f))

        # response
        response.setStatusCode(200)
        response.setHeader("Content-Type", "application/json")
        response.write('{ "type": "FeatureCollection","features":[')
        response.flush()

        jsonExporter = QgsJsonExporter(layer)
        if attributeList:
            jsonExporter.setAttributes(attributeList)

        separator = ''
        for feat in layer.getFeatures(req):
            fid = layername + '.' + getServerFid(feat, pkAttributes)
            response.write(separator +
                           jsonExporter.exportFeature(feat, {}, fid))
            response.flush()
            separator = ',\n'
        response.write(']}')
        return
Example #55
0
    def processAlgorithm(self, progress):
        layer = self.getParameterValue(self.INPUT_LAYER)
        mapping = self.getParameterValue(self.FIELDS_MAPPING)
        output = self.getOutputFromName(self.OUTPUT_LAYER)

        layer = dataobjects.getObjectFromUri(layer)
        provider = layer.dataProvider()
        fields = []
        expressions = []

        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs().srsid())
        da.setEllipsoidalMode(
            iface.mapCanvas().mapSettings().hasCrsTransformEnabled())
        da.setEllipsoid(QgsProject.instance().readEntry(
            'Measure', '/Ellipsoid', GEO_NONE)[0])

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        for field_def in mapping:
            fields.append(
                QgsField(name=field_def['name'],
                         type=field_def['type'],
                         len=field_def['length'],
                         prec=field_def['precision']))

            expression = QgsExpression(field_def['expression'])
            expression.setGeomCalculator(da)
            expression.setDistanceUnits(QgsProject.instance().distanceUnits())
            expression.setAreaUnits(QgsProject.instance().areaUnits())

            if expression.hasParserError():
                raise GeoAlgorithmExecutionException(
                    self.tr(u'Parser error in expression "{}": {}').format(
                        unicode(field_def['expression']),
                        unicode(expression.parserErrorString())))
            expression.prepare(exp_context)
            if expression.hasEvalError():
                raise GeoAlgorithmExecutionException(
                    self.tr(u'Evaluation error in expression "{}": {}').format(
                        unicode(field_def['expression']),
                        unicode(expression.evalErrorString())))
            expressions.append(expression)

        writer = output.getVectorWriter(fields, provider.geometryType(),
                                        layer.crs())

        # Create output vector layer with new attributes
        error = ''
        calculationSuccess = True
        inFeat = QgsFeature()
        outFeat = QgsFeature()
        features = vector.features(layer)
        total = 100.0 / len(features)
        for current, inFeat in enumerate(features):
            rownum = current + 1

            geometry = inFeat.geometry()
            if geometry is not None:
                outFeat.setGeometry(geometry)

            attrs = []
            for i in xrange(0, len(mapping)):
                field_def = mapping[i]
                expression = expressions[i]
                exp_context.setFeature(inFeat)
                exp_context.lastScope().setVariable("row_number", rownum)
                value = expression.evaluate(exp_context)
                if expression.hasEvalError():
                    calculationSuccess = False
                    error = expression.evalErrorString()
                    break

                attrs.append(value)
            outFeat.setAttributes(attrs)

            writer.addFeature(outFeat)

            progress.setPercentage(int(current * total))

        del writer

        if not calculationSuccess:
            raise GeoAlgorithmExecutionException(
                self.tr('An error occurred while evaluating the calculation'
                        ' string:\n') + error)
Example #56
0
    def processAlgorithm(self, progress):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_LAYER))
        fieldName = self.getParameterValue(self.FIELD_NAME)
        fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)]
        width = self.getParameterValue(self.FIELD_LENGTH)
        precision = self.getParameterValue(self.FIELD_PRECISION)
        newField = self.getParameterValue(self.NEW_FIELD)
        formula = self.getParameterValue(self.FORMULA)

        output = self.getOutputFromName(self.OUTPUT_LAYER)

        if output.value == '':
            ext = output.getDefaultFileExtension(self)
            output.value = system.getTempFilenameInTempFolder(output.name +
                                                              '.' + ext)

        fields = layer.fields()
        if newField:
            fields.append(QgsField(fieldName, fieldType, '', width, precision))

        writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs())

        exp = QgsExpression(formula)

        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs().srsid())
        da.setEllipsoidalMode(
            iface.mapCanvas().mapSettings().hasCrsTransformEnabled())
        da.setEllipsoid(QgsProject.instance().readEntry(
            'Measure', '/Ellipsoid', GEO_NONE)[0])
        exp.setGeomCalculator(da)
        exp.setDistanceUnits(QgsProject.instance().distanceUnits())
        exp.setAreaUnits(QgsProject.instance().areaUnits())

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        if not exp.prepare(exp_context):
            raise GeoAlgorithmExecutionException(
                self.tr('Evaluation error: %s' % exp.evalErrorString()))

        # add layer to registry to fix https://issues.qgis.org/issues/17300
        # it is necessary only for aggregate expressions that verify that layer
        # is registered
        removeRegistryAfterEvaluation = False
        if not QgsMapLayerRegistry.instance().mapLayer(layer.id()):
            removeRegistryAfterEvaluation = True
            QgsMapLayerRegistry.instance().addMapLayer(layer,
                                                       addToLegend=False)

        outFeature = QgsFeature()
        outFeature.initAttributes(len(fields))
        outFeature.setFields(fields)

        error = ''
        calculationSuccess = True

        features = vector.features(layer)
        total = 100.0 / len(features) if len(features) > 0 else 1

        rownum = 1
        for current, f in enumerate(features):
            rownum = current + 1
            exp_context.setFeature(f)
            exp_context.lastScope().setVariable("row_number", rownum)
            value = exp.evaluate(exp_context)
            if exp.hasEvalError():
                calculationSuccess = False
                error = exp.evalErrorString()
                break
            else:
                outFeature.setGeometry(f.geometry())
                for fld in f.fields():
                    outFeature[fld.name()] = f[fld.name()]
                outFeature[fieldName] = value
                writer.addFeature(outFeature)

            progress.setPercentage(int(current * total))
        del writer

        # remove from registry if added for expression requirement
        # see above comment about fix #17300
        if removeRegistryAfterEvaluation:
            QgsMapLayerRegistry.instance().removeMapLayer(layer)

        if not calculationSuccess:
            raise GeoAlgorithmExecutionException(
                self.tr('An error occurred while evaluating the calculation '
                        'string:\n%s' % error))
Example #57
0
def calculate_node(
        node, node_attr_name, node_attr_id, layer, discarded_feats):
    operator = node.get('operator', DEFAULT_OPERATOR)
    # for backwards compatibility, we treat the old
    # 'Use a custom field (no recalculation) as the new one with no parentheses
    if operator in (OPERATORS_DICT['CUSTOM'],
                    'Use a custom field (no recalculation)'):
        customFormula = node.get('customFormula', '')
        expression = QgsExpression(customFormula)
        valid, err_msg = QgsExpression.checkExpression(customFormula, None)
        if not valid:
            raise InvalidFormula(
                'Invalid formula "%s": %s' % (customFormula, err_msg))
        if customFormula == '':
            # use the custom field values instead of recalculating them
            request = QgsFeatureRequest().setFlags(
                QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                    [node['field']], layer.fields())
            for feat in layer.getFeatures(request):
                if feat[node['field']] == NULL:
                    discard_feat = True
                    discarded_feat = DiscardedFeature(
                        feat.id(), 'Missing value')
                    discarded_feats.add(discarded_feat)
            return discarded_feats
        else:
            # attempt to retrieve a formula from the description and to
            # calculate the field values based on that formula
            context = QgsExpressionContext()
            context.appendScope(QgsExpressionContextUtils.layerScope(layer))
            expression.prepare(context)
            request = QgsFeatureRequest().setFlags(
                QgsFeatureRequest.NoGeometry)
            with edit(layer):
                for feat in layer.getFeatures(request):
                    context.setFeature(feat)
                    value = expression.evaluate(context)
                    if expression.hasEvalError():
                        raise ValueError(expression.evalErrorString())
                    if value == NULL:
                        discard_feat = True
                        discarded_feat = DiscardedFeature(
                            feat.id(), 'Missing value')
                        discarded_feats.add(discarded_feat)
                    layer.changeAttributeValue(feat.id(), node_attr_id, value)
            return discarded_feats
    # the existance of children should already be checked
    children = node['children']
    with edit(layer):
        request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
        for feat in layer.getFeatures(request):
            # If a feature contains any NULL value, discard_feat will
            # be set to True and the corresponding node value will be
            # set to NULL
            discard_feat = False
            feat_id = feat.id()

            # init node_value to the correct value depending on the
            # node's operator
            if operator in SUM_BASED_OPERATORS:
                node_value = 0
            elif operator in MUL_BASED_OPERATORS:
                node_value = 1
            else:
                raise InvalidOperator('Invalid operator: %s' % operator)
            for child in children:
                if 'field' not in child:
                    raise InvalidChild()
                    # for instance, if the RI can't be calculated, then
                    # also the IRI can't be calculated
                    # But it shouldn't happen, because all the children
                    # should be previously linked to corresponding fields
                if feat[child['field']] == NULL:
                    discard_feat = True
                    discarded_feat = DiscardedFeature(feat_id, 'Missing value')
                    discarded_feats.add(discarded_feat)
                    break  # proceed to the next feature
                # multiply a variable by -1 if it isInverted
                try:
                    inversion_factor = -1 if child['isInverted'] else 1
                except KeyError:  # child is not inverted
                    inversion_factor = 1
                if operator in IGNORING_WEIGHT_OPERATORS:
                    # although these operators ignore weights, they
                    # take into account the inversion
                    child_weighted = \
                        feat[child['field']] * inversion_factor
                else:  # also multiply by the weight
                    child_weighted = (
                        child['weight'] *
                        feat[child['field']] * inversion_factor)

                if operator in SUM_BASED_OPERATORS:
                    node_value += child_weighted
                elif operator in MUL_BASED_OPERATORS:
                    node_value *= child_weighted
                else:
                    error_message = 'Invalid operator: %s' % operator
                    raise RuntimeError(error_message)
            if discard_feat:
                node_value = NULL
            elif operator == OPERATORS_DICT['AVG']:
                # it is equivalent to do a weighted sum with equal weights, or
                # to do the simple sum (ignoring weights) and dividing by the
                # number of children (we use the latter solution)
                node_value /= len(children)  # for sure, len(children)!=0
            elif operator == OPERATORS_DICT['GEOM_MEAN']:
                # the geometric mean
                # (see http://en.wikipedia.org/wiki/Geometric_mean)
                # is the product of the N combined items, elevated by 1/N
                try:
                    # NOTE: in python2 this check was the default. In python3
                    # it would produce a complex number without raising any
                    # error
                    if (node_value < 0
                            and not (1. / len(children)).is_integer()):
                        raise ValueError('negative number cannot be raised'
                                         ' to a fractional power')
                    node_value **= 1. / len(children)
                except ValueError:
                    node_value = NULL
                    discarded_feat = DiscardedFeature(feat_id, 'Invalid value')
                    discarded_feats.add(discarded_feat)
            layer.changeAttributeValue(
                feat_id, node_attr_id, node_value)
    return discarded_feats
Example #58
0
    def processAlgorithm(self, progress):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_LAYER))
        fieldName = self.getParameterValue(self.FIELD_NAME)
        fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)]
        width = self.getParameterValue(self.FIELD_LENGTH)
        precision = self.getParameterValue(self.FIELD_PRECISION)
        newField = self.getParameterValue(self.NEW_FIELD)
        formula = self.getParameterValue(self.FORMULA)

        output = self.getOutputFromName(self.OUTPUT_LAYER)

        fields = layer.fields()
        if newField:
            fields.append(QgsField(fieldName, fieldType, '', width, precision))

        writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs())

        exp = QgsExpression(formula)

        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs().srsid())
        da.setEllipsoidalMode(
            iface.mapCanvas().mapSettings().hasCrsTransformEnabled())
        da.setEllipsoid(QgsProject.instance().readEntry(
            'Measure', '/Ellipsoid', GEO_NONE)[0])
        exp.setGeomCalculator(da)
        exp.setDistanceUnits(QgsProject.instance().distanceUnits())
        exp.setAreaUnits(QgsProject.instance().areaUnits())

        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope())
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        if not exp.prepare(exp_context):
            raise GeoAlgorithmExecutionException(
                self.tr('Evaluation error: %s' % exp.evalErrorString()))

        outFeature = QgsFeature()
        outFeature.initAttributes(len(fields))
        outFeature.setFields(fields)

        error = ''
        calculationSuccess = True

        features = vector.features(layer)
        total = 100.0 / len(features)

        rownum = 1
        for current, f in enumerate(features):
            rownum = current + 1
            exp_context.setFeature(f)
            exp_context.lastScope().setVariable("row_number", rownum)
            value = exp.evaluate(exp_context)
            if exp.hasEvalError():
                calculationSuccess = False
                error = exp.evalErrorString()
                break
            else:
                outFeature.setGeometry(f.geometry())
                for fld in f.fields():
                    outFeature[fld.name()] = f[fld.name()]
                outFeature[fieldName] = value
                writer.addFeature(outFeature)

            progress.setPercentage(int(current * total))
        del writer

        if not calculationSuccess:
            raise GeoAlgorithmExecutionException(
                self.tr('An error occurred while evaluating the calculation '
                        'string:\n%s' % error))
    def testExpression(self):
        """ test aggregate calculation using an expression """

        # numeric
        layer = QgsVectorLayer("Point?field=fldint:integer", "layer", "memory")
        pr = layer.dataProvider()

        int_values = [4, 2, 3, 2, 5, None, 8]

        features = []
        for v in int_values:
            f = QgsFeature()
            f.setFields(layer.fields())
            f.setAttributes([v])
            features.append(f)
        assert pr.addFeatures(features)

        #int
        agg = QgsAggregateCalculator(layer)
        val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 2')
        self.assertTrue(ok)
        self.assertEqual(val, 48)

        # double
        val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 1.5')
        self.assertTrue(ok)
        self.assertEqual(val, 36)

        # datetime
        val, ok = agg.calculate(QgsAggregateCalculator.Max, "to_date('2012-05-04') + to_interval( fldint || ' day' )")
        self.assertTrue(ok)
        self.assertEqual(val, QDateTime(QDate(2012, 5, 12), QTime(0, 0, 0)))

        # date
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "to_date(to_date('2012-05-04') + to_interval( fldint || ' day' ))")
        self.assertTrue(ok)
        self.assertEqual(val, QDateTime(QDate(2012, 5, 6), QTime(0, 0, 0)))

        # string
        val, ok = agg.calculate(QgsAggregateCalculator.Max, "fldint || ' oranges'")
        self.assertTrue(ok)
        self.assertEqual(val, '8 oranges')

        # geometry
        val, ok = agg.calculate(QgsAggregateCalculator.GeometryCollect, "make_point( coalesce(fldint,0), 2 )")
        self.assertTrue(ok)
        self.assertTrue(val.exportToWkt(), 'MultiPoint((4 2, 2 2, 3 2, 2 2,5 2, 0 2,8 2))')

        # try a bad expression
        val, ok = agg.calculate(QgsAggregateCalculator.Max, "not_a_field || ' oranges'")
        self.assertFalse(ok)
        val, ok = agg.calculate(QgsAggregateCalculator.Max, "5+")
        self.assertFalse(ok)

        # test expression context

        # check default context first
        # should have layer variables:
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@layer_name")
        self.assertTrue(ok)
        self.assertEqual(val, 'layer')
        # but not custom variables:
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var")
        self.assertTrue(ok)
        self.assertEqual(val, NULL)

        # test with manual expression context
        scope = QgsExpressionContextScope()
        scope.setVariable('my_var', 5)
        context = QgsExpressionContext()
        context.appendScope(scope)
        val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var", context)
        self.assertTrue(ok)
        self.assertEqual(val, 5)
Example #60
0
def exportLayers(iface, layers, folder, precision, optimize, popupField, json,
                 restrictToExtent, extent, feedback):
    canvas = iface.mapCanvas()
    epsg4326 = QgsCoordinateReferenceSystem("EPSG:4326")
    layersFolder = os.path.join(folder, "layers")
    QDir().mkpath(layersFolder)
    for count, (layer, encode2json,
                popup) in enumerate(zip(layers, json, popupField)):
        if (layer.type() == layer.VectorLayer
                and (layer.providerType() != "WFS" or encode2json)):
            feedback.showFeedback("Exporting %s to JSON..." % layer.name())
            cleanLayer = writeTmpLayer(layer, popup, restrictToExtent, iface,
                                       extent)
            fields = layer.pendingFields()
            for field in fields:
                exportImages(layer, field.name(), layersFolder + "/tmp.tmp")
            if is25d(layer, canvas, restrictToExtent, extent):
                provider = cleanLayer.dataProvider()
                provider.addAttributes([
                    QgsField("height", QVariant.Double),
                    QgsField("wallColor", QVariant.String),
                    QgsField("roofColor", QVariant.String)
                ])
                cleanLayer.updateFields()
                fields = cleanLayer.pendingFields()
                renderer = layer.rendererV2()
                renderContext = QgsRenderContext.fromMapSettings(
                    canvas.mapSettings())
                feats = layer.getFeatures()
                context = QgsExpressionContext()
                context.appendScope(
                    QgsExpressionContextUtils.layerScope(layer))
                expression = QgsExpression('eval(@qgis_25d_height)')
                heightField = fields.indexFromName("height")
                wallField = fields.indexFromName("wallColor")
                roofField = fields.indexFromName("roofColor")
                renderer.startRender(renderContext, fields)
                cleanLayer.startEditing()
                for feat in feats:
                    context.setFeature(feat)
                    height = expression.evaluate(context)
                    if isinstance(renderer, QgsCategorizedSymbolRendererV2):
                        classAttribute = renderer.classAttribute()
                        attrValue = feat.attribute(classAttribute)
                        catIndex = renderer.categoryIndexForValue(attrValue)
                        categories = renderer.categories()
                        symbol = categories[catIndex].symbol()
                    elif isinstance(renderer, QgsGraduatedSymbolRendererV2):
                        classAttribute = renderer.classAttribute()
                        attrValue = feat.attribute(classAttribute)
                        ranges = renderer.ranges()
                        for range in ranges:
                            if (attrValue >= range.lowerValue()
                                    and attrValue <= range.upperValue()):
                                symbol = range.symbol().clone()
                    else:
                        symbol = renderer.symbolForFeature2(
                            feat, renderContext)
                    sl1 = symbol.symbolLayer(1)
                    sl2 = symbol.symbolLayer(2)
                    wallColor = sl1.subSymbol().color().name()
                    roofColor = sl2.subSymbol().color().name()
                    provider.changeAttributeValues({
                        feat.id() + 1: {
                            heightField: height,
                            wallField: wallColor,
                            roofField: roofColor
                        }
                    })
                cleanLayer.commitChanges()
                renderer.stopRender(renderContext)

            sln = safeName(cleanLayer.name()) + unicode(count)
            tmpPath = os.path.join(layersFolder, sln + ".json")
            path = os.path.join(layersFolder, sln + ".js")
            options = []
            if precision != "maintain":
                options.append("COORDINATE_PRECISION=" + unicode(precision))
            QgsVectorFileWriter.writeAsVectorFormat(cleanLayer,
                                                    tmpPath,
                                                    "utf-8",
                                                    epsg4326,
                                                    'GeoJson',
                                                    0,
                                                    layerOptions=options)
            with open(path, "w") as f:
                f.write("var %s = " % ("geojson_" + sln))
                with open(tmpPath, "r") as f2:
                    for line in f2:
                        if optimize:
                            line = line.strip("\n\t ")
                            line = removeSpaces(line)
                        f.write(line)
            os.remove(tmpPath)

        elif (layer.type() == layer.RasterLayer
              and layer.providerType() != "wms"):

            feedback.showFeedback("Exporting %s to PNG..." % layer.name())
            name_ts = (safeName(layer.name()) + unicode(count) +
                       unicode(int(time.time())))

            # We need to create a new file to export style
            piped_file = os.path.join(tempfile.gettempdir(),
                                      name_ts + '_piped.tif')

            piped_extent = layer.extent()
            piped_width = layer.height()
            piped_height = layer.width()
            piped_crs = layer.crs()
            piped_renderer = layer.renderer()
            piped_provider = layer.dataProvider()

            pipe = QgsRasterPipe()
            pipe.set(piped_provider.clone())
            pipe.set(piped_renderer.clone())

            file_writer = QgsRasterFileWriter(piped_file)

            file_writer.writeRaster(pipe, piped_width, piped_height,
                                    piped_extent, piped_crs)

            # Extent of the layer in EPSG:3857
            crsSrc = layer.crs()
            crsDest = QgsCoordinateReferenceSystem(3857)
            xform = QgsCoordinateTransform(crsSrc, crsDest)
            extentRep = xform.transform(layer.extent())

            extentRepNew = ','.join([
                unicode(extentRep.xMinimum()),
                unicode(extentRep.xMaximum()),
                unicode(extentRep.yMinimum()),
                unicode(extentRep.yMaximum())
            ])

            # Reproject in 3857
            piped_3857 = os.path.join(tempfile.gettempdir(),
                                      name_ts + '_piped_3857.tif')
            # Export layer as PNG
            out_raster = os.path.join(
                layersFolder,
                safeName(layer.name()) + unicode(count) + ".png")

            qgis_version = QGis.QGIS_VERSION

            if int(qgis_version.split('.')[1]) < 15:
                processing.runalg("gdalogr:warpreproject", piped_file,
                                  layer.crs().authid(), "EPSG:3857", "", 0, 1,
                                  0, -1, 75, 6, 1, False, 0, False, "",
                                  piped_3857)
                processing.runalg("gdalogr:translate", piped_3857, 100, True,
                                  "", 0, "", extentRepNew, False, 0, 0, 75, 6,
                                  1, False, 0, False, "", out_raster)
            else:
                try:
                    warpArgs = {
                        "INPUT": piped_file,
                        "SOURCE_SRS": layer.crs().authid(),
                        "DEST_SRS": "EPSG:3857",
                        "NO_DATA": "",
                        "TR": 0,
                        "METHOD": 2,
                        "RAST_EXT": extentRepNew,
                        "EXT_CRS": "EPSG:3857",
                        "RTYPE": 0,
                        "COMPRESS": 4,
                        "JPEGCOMPRESSION": 75,
                        "ZLEVEL": 6,
                        "PREDICTOR": 1,
                        "TILED": False,
                        "BIGTIFF": 0,
                        "TFW": False,
                        "EXTRA": "",
                        "OUTPUT": piped_3857
                    }
                    procRtn = processing.runalg("gdalogr:warpreproject",
                                                warpArgs)
                    # force exception on algorithm fail
                    for val in procRtn:
                        pass
                except:
                    try:
                        warpArgs = {
                            "INPUT": piped_file,
                            "SOURCE_SRS": layer.crs().authid(),
                            "DEST_SRS": "EPSG:3857",
                            "NO_DATA": "",
                            "TR": 0,
                            "METHOD": 2,
                            "RAST_EXT": extentRepNew,
                            "RTYPE": 0,
                            "COMPRESS": 4,
                            "JPEGCOMPRESSION": 75,
                            "ZLEVEL": 6,
                            "PREDICTOR": 1,
                            "TILED": False,
                            "BIGTIFF": 0,
                            "TFW": False,
                            "EXTRA": "",
                            "OUTPUT": piped_3857
                        }
                        procRtn = processing.runalg("gdalogr:warpreproject",
                                                    warpArgs)
                        # force exception on algorithm fail
                        for val in procRtn:
                            pass
                    except:
                        try:
                            warpArgs = {
                                "INPUT": piped_file,
                                "SOURCE_SRS": layer.crs().authid(),
                                "DEST_SRS": "EPSG:3857",
                                "NO_DATA": "",
                                "TR": 0,
                                "METHOD": 2,
                                "RTYPE": 0,
                                "COMPRESS": 4,
                                "JPEGCOMPRESSION": 75,
                                "ZLEVEL": 6,
                                "PREDICTOR": 1,
                                "TILED": False,
                                "BIGTIFF": 0,
                                "TFW": False,
                                "EXTRA": "",
                                "OUTPUT": piped_3857
                            }
                            procRtn = processing.runalg(
                                "gdalogr:warpreproject", warpArgs)
                            # force exception on algorithm fail
                            for val in procRtn:
                                pass
                        except:
                            shutil.copyfile(piped_file, piped_3857)

                try:
                    processing.runalg("gdalogr:translate", piped_3857, 100,
                                      True, "", 0, "", extentRepNew, False, 5,
                                      4, 75, 6, 1, False, 0, False, "",
                                      out_raster)
                except:
                    shutil.copyfile(piped_3857, out_raster)
    feedback.completeStep()