Example #1
0
def sum_fields(layer, output_field_key, input_fields):
    """Sum the value of input_fields and put it as output_field.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param output_field_key: The output field definition key.
    :type output_field_key: basestring

    :param input_fields: List of input fields' name.
    :type input_fields: list
    """
    field_definition = definition(output_field_key)
    output_field_name = field_definition['field_name']
    # If the fields only has one element
    if len(input_fields) == 1:
        # Name is different, copy it
        if input_fields[0] != output_field_name:
            to_rename = {input_fields[0]: output_field_name}
            # We copy only, it will be deleted later.
            # We can't rename the field, we need to copy it as the same
            # field might be used many times in the FMT tool.
            copy_fields(layer, to_rename)
        else:
            # Name is same, do nothing
            return
    else:
        # Creating expression
        # Put field name in a double quote. See #4248
        input_fields = ['"%s"' % f for f in input_fields]
        string_expression = ' + '.join(input_fields)
        sum_expression = QgsExpression(string_expression)
        context = QgsExpressionContext()
        context.setFields(layer.fields())
        sum_expression.prepare(context)

        # Get the output field index
        output_idx = layer.fields().lookupField(output_field_name)
        # Output index is not found
        layer.startEditing()
        if output_idx == -1:
            output_field = create_field_from_definition(field_definition)
            layer.addAttribute(output_field)
            output_idx = layer.fields().lookupField(output_field_name)

        # Iterate to all features
        for feature in layer.getFeatures():
            context.setFeature(feature)
            result = sum_expression.evaluate(context)
            feature[output_idx] = result
            layer.updateFeature(feature)

        layer.commitChanges()
Example #2
0
def sum_fields(layer, output_field_key, input_fields):
    """Sum the value of input_fields and put it as output_field.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param output_field_key: The output field definition key.
    :type output_field_key: basestring

    :param input_fields: List of input fields' name.
    :type input_fields: list
    """
    field_definition = definition(output_field_key)
    output_field_name = field_definition['field_name']
    # If the fields only has one element
    if len(input_fields) == 1:
        # Name is different, copy it
        if input_fields[0] != output_field_name:
            copy_fields(layer, {
                input_fields[0]: output_field_name})
        # Name is same, do nothing
        else:
            return
    else:
        # Creating expression
        # Put field name in a double quote. See #4248
        input_fields = ['"%s"' % f for f in input_fields]
        string_expression = ' + '.join(input_fields)
        sum_expression = QgsExpression(string_expression)
        context = QgsExpressionContext()
        context.setFields(layer.pendingFields())
        sum_expression.prepare(context)

        # Get the output field index
        output_idx = layer.fieldNameIndex(output_field_name)
        # Output index is not found
        if output_idx == -1:
            output_field = create_field_from_definition(field_definition)
            layer.startEditing()
            layer.addAttribute(output_field)
            layer.commitChanges()
            output_idx = layer.fieldNameIndex(output_field_name)

        layer.startEditing()
        # Iterate to all features
        for feature in layer.getFeatures():
            context.setFeature(feature)
            result = sum_expression.evaluate(context)
            feature[output_idx] = result
            layer.updateFeature(feature)

        layer.commitChanges()
Example #3
0
    def add_flooded_field(self, shapefile_path):
        """Create the layer from the local shp adding the flooded field.

        .. versionadded:: 3.3

        Use this method to add a calculated field to a shapefile. The shapefile
        should have a field called 'count' containing the number of flood
        reports for the field. The field values will be set to 0 if the count
        field is < 1, otherwise it will be set to 1.

        :param shapefile_path: Path to the shapefile that will have the flooded
            field added.
        :type shapefile_path: basestring

        :return: A vector layer with the flooded field added.
        :rtype: QgsVectorLayer
        """
        layer = QgsVectorLayer(
            shapefile_path, self.tr('Jakarta Floods'), 'ogr')
        # Add a calculated field indicating if a poly is flooded or not
        # from qgis.PyQt.QtCore import QVariant
        layer.startEditing()
        # Add field with integer from 0 to 4 which represents the flood
        # class. Its the same as 'state' field except that is being treated
        # as a string.
        # This is used for cartography
        flood_class_field = QgsField('floodclass', QVariant.Int)
        layer.addAttribute(flood_class_field)
        layer.commitChanges()
        layer.startEditing()
        flood_class_idx = layer.fields().lookupField('floodclass')
        flood_class_expression = QgsExpression('to_int(state)')
        context = QgsExpressionContext()
        context.setFields(layer.fields())
        flood_class_expression.prepare(context)

        # Add field with boolean flag to say if the area is flooded
        # This is used by the impact function
        flooded_field = QgsField('flooded', QVariant.Int)
        layer.dataProvider().addAttributes([flooded_field])
        layer.commitChanges()
        layer.startEditing()
        flooded_idx = layer.fields().lookupField('flooded')
        flood_flag_expression = QgsExpression('state > 0')
        flood_flag_expression.prepare(context)
        for feature in layer.getFeatures():
            context.setFeature(feature)
            feature[flood_class_idx] = flood_class_expression.evaluate(context)
            feature[flooded_idx] = flood_flag_expression.evaluate(context)
            layer.updateFeature(feature)
        layer.commitChanges()
        return layer
    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 #5
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))
Example #6
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 virtualFields(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
        """
        layer_name = params.get('LAYER', '')
        if not layer_name:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = find_vector_layer(layer_name, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'VirtualFields': {} provided".format(layer_name),
                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
        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
        with_geom = params.get('WITH_GEOMETRY', '').lower() in ['true', '1', 't']
        if not with_geom:
            req.setFlags(QgsFeatureRequest.NoGeometry)

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

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

        json_exporter = QgsJsonExporter(layer)
        if attribute_list:
            json_exporter.setAttributes(attribute_list)

        separator = ''
        for feat in layer.getFeatures(req):
            fid = layer_name + '.' + get_server_fid(feat, pk_attributes)

            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 + json_exporter.exportFeature(feat, extra, fid))
            response.flush()
            separator = ',\n'
        response.write(']}')
        return
    def replace_expression_text(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 = find_vector_layer(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
        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
        add_form_scope = 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():
                field_name = field.name()
                if feat_fields.indexOf(field_name) != -1:
                    feat.setAttribute(field_name, f[field_name])

            # Add form scope to expression context
            if add_form_scope:
                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
    def evaluate(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 = find_vector_layer(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
        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
        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
        add_form_scope = 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 add_form_scope:
                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 add_form_scope:
                    # need to prepare the expression because the context has 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
Example #10
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 data( self ):
        if self._dataLoaded:
            return self._x, self._y, self._z
        self._dataLoaded=True
        self._x = None
        self._y = None
        self._z = None
        self._gridShape=None
        self._gridTested=False
        self._dataLoaded=True

        source=self._source
        zField=self._zField
        if source is None or zField is None or zField == '':
            return self._x, self._y, self._z

        discardTolerance=self._discardTolerance
        feedback=self._feedback

        total = source.featureCount()
        percent = 100.0 / total if total > 0 else 0

        count = 0
        x = list()
        y = list()
        z = list()
        try:
            if source.fields().lookupField(zField) >= 0:
                zField='"'+zField.replace('"','""')+'"'
            expression=QgsExpression(zField)
            if expression.hasParserError():
                raise ContourError(tr("Cannot parse")+" "+zField)
            fields=source.fields()
            context=QgsExpressionContext()
            context.setFields(fields)
            if not expression.prepare(context):
                raise ContourError(tr("Cannot evaluate value")+ " "+zField)
            request = QgsFeatureRequest()
            request.setSubsetOfAttributes( expression.referencedColumns(),fields)
            if self._sourceFids is not None:
                request.setFilterFids(self._sourceFids)
            for current,feat in enumerate(source.getFeatures( request )):
                try:
                    if feedback.isCanceled():
                        raise ContourError('Cancelled by user')
                    feedback.setProgress(int(current * percent))
                    context.setFeature(feat)
                    zval=expression.evaluate(context)
                    try:
                        zval=float(zval)
                    except ValueError:
                        raise ContourError(tr("Z value {0} is not number")
                                                   .format(zval))
                    if zval is not None:
                        fgeom = feat.geometry()
                        if QgsWkbTypes.flatType(fgeom.wkbType()) != QgsWkbTypes.Point:
                            raise ContourError(tr("Invalid geometry type for contouring - must be point geometry"))
                        geom=fgeom.asPoint()
                        x.append(geom.x())
                        y.append(geom.y())
                        z.append(zval)
                except Exception as ex:
                    raise
                count = count + 1

            npt=len(x)
            if npt > 0:
                x=np.array(x)
                y=np.array(y)
                z=np.array(z)
                if discardTolerance > 0:
                    index=ContourUtils.discardDuplicatePoints(
                        x,y,discardTolerance,self.crs().isGeographic())
                    npt1=len(index)
                    if npt1 < npt:
                        x=x[index]
                        y=y[index]
                        z=z[index]
                        feedback.pushInfo(tr("{0} near duplicate points discarded - tolerance {1}")
                                          .format(npt-npt1,discardTolerance))
        except ContourError as ce:
            feedback.reportError(ce.message())
            feedback.setProgress(0)
            return self._x,self._y,self._z
        finally:
            feedback.setProgress(0)

        if len(x) < 3:
            feedback.reportError(tr("Too few points to contour"))
            return self._x, self._y, self._z
        self._x=x
        self._y=y
        self._z=z
        return self._x, self._y, self._z
Example #12
0
    def testRemappingSink(self):
        """
        Test remapping features
        """
        fields = QgsFields()
        fields.append(QgsField('fldtxt', QVariant.String))
        fields.append(QgsField('fldint', QVariant.Int))
        fields.append(QgsField('fldtxt2', QVariant.String))

        store = QgsFeatureStore(fields,
                                QgsCoordinateReferenceSystem('EPSG:3857'))

        mapping_def = QgsRemappingSinkDefinition()
        mapping_def.setDestinationWkbType(QgsWkbTypes.Point)
        mapping_def.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        mapping_def.setDestinationCrs(
            QgsCoordinateReferenceSystem('EPSG:3857'))
        mapping_def.setDestinationFields(fields)
        mapping_def.addMappedField('fldtxt2', QgsProperty.fromField('fld1'))
        mapping_def.addMappedField(
            'fldint', QgsProperty.fromExpression('@myval * fldint'))

        proxy = QgsRemappingProxyFeatureSink(mapping_def, store)
        self.assertEqual(proxy.destinationSink(), store)

        self.assertEqual(len(store), 0)

        incoming_fields = QgsFields()
        incoming_fields.append(QgsField('fld1', QVariant.String))
        incoming_fields.append(QgsField('fldint', QVariant.Int))

        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        scope.setVariable('myval', 2)
        context.appendScope(scope)
        context.setFields(incoming_fields)
        proxy.setExpressionContext(context)
        proxy.setTransformContext(QgsProject.instance().transformContext())

        f = QgsFeature()
        f.setFields(incoming_fields)
        f.setAttributes(["test", 123])
        f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(1, 2)))
        self.assertTrue(proxy.addFeature(f))
        self.assertEqual(len(store), 1)
        self.assertEqual(store.features()[0].geometry().asWkt(1),
                         'Point (111319.5 222684.2)')
        self.assertEqual(store.features()[0].attributes(), [None, 246, 'test'])

        f2 = QgsFeature()
        f2.setAttributes(["test2", 457])
        f2.setGeometry(QgsGeometry.fromWkt('LineString( 1 1, 2 2)'))
        f3 = QgsFeature()
        f3.setAttributes(["test3", 888])
        f3.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(3, 4)))
        self.assertTrue(proxy.addFeatures([f2, f3]))
        self.assertEqual(len(store), 4)
        self.assertEqual(store.features()[1].attributes(),
                         [None, 914, 'test2'])
        self.assertEqual(store.features()[2].attributes(),
                         [None, 914, 'test2'])
        self.assertEqual(store.features()[3].attributes(),
                         [None, 1776, 'test3'])
        self.assertEqual(store.features()[1].geometry().asWkt(1),
                         'Point (111319.5 111325.1)')
        self.assertEqual(store.features()[2].geometry().asWkt(1),
                         'Point (222639 222684.2)')
        self.assertEqual(store.features()[3].geometry().asWkt(1),
                         'Point (333958.5 445640.1)')
Example #13
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, str):
        layers.append(layername)
    else:
        layers = layername

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

    for searchlayer in layers:
        try:
            searchlayer = QgsProject.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() == QgsWkbTypes.PointGeometry:
                point = feature.geometry().asPoint()
                rect = QgsRectangle(point.x(), point.y(),
                                    point.x() + 10,
                                    point.y() + 10)

            rect.scale(20)

            rect = canvas.mapSettings().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)

        context = QgsExpressionContext()
        context.setFields(searchlayer.fields())
        scope = QgsExpressionContextScope()
        context.appendScope(scope)
        scope.setVariable("roamgeometry", feature.geometry())
        exp.prepare(context)

        if exp.hasParserError():
            error = exp.parserErrorString()
            roam.utils.warning(error)

        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 #14
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 #15
0
    def data( self ):
        if self._dataLoaded:
            return self._x, self._y, self._z
        self._dataLoaded=True
        self._x = None
        self._y = None
        self._z = None
        self._gridShape=None
        self._gridTested=False
        self._dataLoaded=True

        source=self._source
        zField=self._zField
        if source is None or zField is None or zField == '':
            return self._x, self._y, self._z

        discardTolerance=self._discardTolerance
        feedback=self._feedback

        total = source.featureCount()
        percent = 100.0 / total if total > 0 else 0

        count = 0
        x = list()
        y = list()
        z = list()
        try:
            if source.fields().lookupField(zField) >= 0:
                zField='"'+zField.replace('"','""')+'"'
            expression=QgsExpression(zField)
            if expression.hasParserError():
                raise ContourError(tr("Cannot parse")+" "+zField)
            fields=source.fields()
            context=QgsExpressionContext()
            context.setFields(fields)
            if not expression.prepare(context):
                raise ContourError(tr("Cannot evaluate value")+ " "+zField)
            request = QgsFeatureRequest()
            request.setSubsetOfAttributes( expression.referencedColumns(),fields)
            if self._sourceFids is not None:
                request.setFilterFids(self._sourceFids)
            for current,feat in enumerate(source.getFeatures( request )):
                try:
                    if feedback.isCanceled():
                        raise ContourError('Cancelled by user')
                    feedback.setProgress(int(current * percent))
                    context.setFeature(feat)
                    zval=expression.evaluate(context)
                    if zval is None or (isinstance(zval,QVariant) and zval.isNull()):
                        continue
                    try:
                        zval=float(zval)
                    except ValueError:
                        raise ContourError(tr("Z value {0} is not number")
                                                   .format(zval))
                    if zval is not None:
                        fgeom = feat.geometry()
                        if QgsWkbTypes.flatType(fgeom.wkbType()) != QgsWkbTypes.Point:
                            raise ContourError(tr("Invalid geometry type for contouring - must be point geometry"))
                        geom=fgeom.asPoint()
                        x.append(geom.x())
                        y.append(geom.y())
                        z.append(zval)
                except Exception as ex:
                    raise
                count = count + 1

            npt=len(x)
            if npt > 0:
                x=np.array(x)
                y=np.array(y)
                z=np.array(z)
                if discardTolerance > 0:
                    index=ContourUtils.discardDuplicatePoints(
                        x,y,discardTolerance,self.crs().isGeographic())
                    npt1=len(index)
                    if npt1 < npt:
                        x=x[index]
                        y=y[index]
                        z=z[index]
                        feedback.pushInfo(tr("{0} near duplicate points discarded - tolerance {1}")
                                          .format(npt-npt1,discardTolerance))
        except ContourError as ce:
            feedback.reportError(ce.message())
            feedback.setProgress(0)
            return self._x,self._y,self._z
        finally:
            feedback.setProgress(0)

        if len(x) < 3:
            feedback.reportError(tr("Too few points to contour"))
            return self._x, self._y, self._z
        self._x=x
        self._y=y
        self._z=z
        return self._x, self._y, self._z