Ejemplo n.º 1
0
def expression_eval(expression_text,
                    project_id=None,
                    qgs_layer_id=None,
                    form_data=None,
                    formatter=0):
    """Evaluates a QgsExpression and returns the result

    :param expression_text: The QgsExpression text
    :type expression_text: str
    :param project_id: ID of the qdjango project, defaults to None
    :type project_id: int, optional
    :param qgs_layer_id: ID of the QGIS Layer, defaults to None
    :type qgslayer_id: str, optional
    :param form_data: A dictionary that maps to a GeoJSON representation of the feature currently edited in the form
    :type form_data: dict, optional
    :param formatter: Indicate if form_data values contains formatter values or original features value.
    :type formatter: int, optional
    """

    expression = QgsExpression(expression_text)
    expression_context = QgsExpressionContext()

    layer = None

    for func_name in expression.referencedFunctions():
        if func_name in FORBIDDEN_FUNCTIONS:
            raise ExpressionForbiddenError(
                _('Function "{}" is not allowed for security reasons!').format(
                    func_name))

    for var_name in expression.referencedVariables():
        if var_name in FORBIDDEN_VARIABLES:
            raise ExpressionForbiddenError(
                _('Variable "{}" is not allowed for security reasons!').format(
                    var_name))

    if project_id is not None:

        try:
            project = Project.objects.get(pk=project_id)

            if qgs_layer_id is not None:
                try:
                    layer = project.layer_set.get(qgs_layer_id=qgs_layer_id)
                except Layer.DoesNotExist:
                    raise ExpressionLayerError(
                        _('QGIS layer with id "{}" could not be found!').
                        format(qgs_layer_id))

                expression_contex = QgsExpressionContextUtils.globalProjectLayerScopes(
                    layer.qgis_layer)

            else:
                expression_contex = QgsExpressionContextUtils.globalScope()
                expression_context.appendScope(
                    QgsExpressionContextUtils.projectScope(
                        project.qgis_project))

        except Project.DoesNotExist:
            raise ExpressionProjectError(
                _('QDjango project with id "{}" could not be found!').format(
                    project_id))

    else:
        expression_contex = QgsExpressionContextUtils.globalScope()

    if form_data is not None:

        if layer is None:
            raise ExpressionLayerError(
                _('A valid QGIS layer is required to process form data!'))

        try:
            # Case by formatter
            # formatter == 1 : get featureid from layer, usually must be used with formatter form_data
            # formatter == 0 : default behavior
            if formatter == 0:
                fields = layer.qgis_layer.fields()
                form_feature = QgsJsonUtils.stringToFeatureList(
                    json.dumps(form_data), fields, None)[0]

                # Set attributes manually because QgsJsonUtils does not respect order
                for k, v in form_data['properties'].items():
                    form_feature.setAttribute(k, v)
            else:
                qgis_feature_request = QgsFeatureRequest()
                exp = expression_from_server_fids(
                    [form_data['id']], layer.qgis_layer.dataProvider())
                qgis_feature_request.combineFilterExpression(exp)
                form_feature = get_qgis_features(layer.qgis_layer,
                                                 qgis_feature_request)[0]

            expression_context.appendScope(
                QgsExpressionContextUtils.formScope(form_feature))
            expression_context.setFeature(form_feature)
        except:
            raise ExpressionFormDataError()

    valid, errors = expression.checkExpression(expression_text,
                                               expression_context)

    if not valid:
        raise ExpressionParseError(errors)

    result = expression.evaluate(expression_context)

    if expression.hasEvalError():
        raise ExpressionEvalError(expression.evalErrorString())

    return result
Ejemplo n.º 2
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