Example #1
0
def run_single_post_processor(layer, post_processor):
    """Run single post processor.

    If the layer has the output field, it will pass the post
    processor calculation.

    :param layer: The vector layer to use for post processing.
    :type layer: QgsVectorLayer

    :param post_processor: A post processor definition.
    :type post_processor: dict

    :returns: Tuple with True if success, else False with an error message.
    :rtype: (bool, str)
    """
    if not layer.editBuffer():

        # Turn on the editing mode.
        if not layer.startEditing():
            msg = tr('The impact layer could not start the editing mode.')
            return False, msg

    # Calculate based on formula
    # Iterate all possible output and create the correct field.
    for output_key, output_value in list(post_processor['output'].items()):

        # Get output attribute name
        key = output_value['value']['key']
        output_field_name = output_value['value']['field_name']
        layer.keywords['inasafe_fields'][key] = output_field_name

        # If there is already the output field, don't proceed
        if layer.fields().lookupField(output_field_name) > -1:
            msg = tr('The field name %s already exists.' % output_field_name)
            layer.rollBack()
            return False, msg

        # Add output attribute name to the layer
        field = create_field_from_definition(output_value['value'])
        result = layer.addAttribute(field)
        if not result:
            msg = tr('Error while creating the field %s.' % output_field_name)
            layer.rollBack()
            return False, msg

        # Get the index of output attribute
        output_field_index = layer.fields().lookupField(output_field_name)

        if layer.fields().lookupField(output_field_name) == -1:
            msg = tr('The field name %s has not been created.' %
                     output_field_name)
            layer.rollBack()
            return False, msg

        # Get the input field's indexes for input
        input_indexes = {}

        input_properties = {}

        # Default parameters
        default_parameters = {}

        msg = None

        # Iterate over every inputs.
        for key, values in list(post_processor['input'].items()):
            values = values if isinstance(values, list) else [values]
            for value in values:
                is_constant_input = (value['type'] == constant_input_type)
                is_field_input = (value['type'] == field_input_type
                                  or value['type'] == dynamic_field_input_type)
                is_geometry_input = (
                    value['type'] == geometry_property_input_type)
                is_keyword_input = (value['type'] == keyword_input_type)
                is_needs_input = (value['type'] == needs_profile_input_type)
                is_layer_property_input = (
                    value['type'] == layer_property_input_type)
                if value['type'] == keyword_value_expected:
                    break
                if is_constant_input:
                    default_parameters[key] = value['value']
                    break
                elif is_field_input:
                    if value['type'] == dynamic_field_input_type:
                        key_template = value['value']['key']
                        field_param = value['field_param']
                        field_key = key_template % field_param
                    else:
                        field_key = value['value']['key']

                    inasafe_fields = layer.keywords['inasafe_fields']
                    name_field = inasafe_fields.get(field_key)

                    if not name_field:
                        msg = tr('%s has not been found in inasafe fields.' %
                                 value['value']['key'])
                        continue

                    index = layer.fields().lookupField(name_field)

                    if index == -1:
                        fields = layer.fields().toList()
                        msg = tr('The field name %s has not been found in %s' %
                                 (name_field, [f.name() for f in fields]))
                        continue

                    input_indexes[key] = index
                    break

                # For geometry, create new field that contain the value
                elif is_geometry_input:
                    input_properties[key] = geometry_property_input_type['key']
                    break

                # for keyword
                elif is_keyword_input:
                    # See http://stackoverflow.com/questions/14692690/
                    # access-python-nested-dictionary-items-via-a-list-of-keys
                    value = reduce(lambda d, k: d[k], value['value'],
                                   layer.keywords)

                    default_parameters[key] = value
                    break

                # for needs profile
                elif is_needs_input:
                    need_parameter = minimum_needs_parameter(
                        parameter_name=value['value'])
                    value = need_parameter.value

                    default_parameters[key] = value
                    break

                # for layer property
                elif is_layer_property_input:
                    if value['value'] == layer_crs_input_value:
                        default_parameters[key] = layer.crs()

                    if value['value'] == size_calculator_input_value:
                        exposure = layer.keywords.get('exposure')
                        if not exposure:
                            keywords = layer.keywords.get('exposure_keywords')
                            exposure = keywords.get('exposure')

                        default_parameters[key] = SizeCalculator(
                            layer.crs(), layer.geometryType(), exposure)
                    break

            else:
                # executed when we can't find all the inputs
                layer.rollBack()
                return False, msg

        # Create iterator for feature
        request = QgsFeatureRequest().setSubsetOfAttributes(
            list(input_indexes.values()))
        iterator = layer.getFeatures(request)

        inputs = input_indexes.copy()
        inputs.update(input_properties)

        # Iterate all feature
        for feature in iterator:
            attributes = feature.attributes()

            # Create dictionary to store the input
            parameters = {}
            parameters.update(default_parameters)

            # Fill up the input from fields
            for key, value in list(inputs.items()):
                if value == geometry_property_input_type['key']:
                    parameters[key] = feature.geometry()
                else:
                    parameters[key] = attributes[value]
            # Fill up the input from geometry property

            # Evaluate the function
            python_function = output_value.get('function')
            if python_function:
                # Launch the python function
                post_processor_result = python_function(**parameters)
            else:
                # Evaluate the function
                formula = output_value['formula']
                post_processor_result = evaluate_formula(formula, parameters)

            # The affected postprocessor returns a boolean.
            if isinstance(post_processor_result, bool):
                post_processor_result = tr(str(post_processor_result))

            layer.changeAttributeValue(feature.id(), output_field_index,
                                       post_processor_result)

    layer.commitChanges()
    return True, None
Example #2
0
def recompute_counts(layer, callback=None):
    """Recompute counts according to the size field and the new size.

    This function will also take care of updating the size field. The size
    post processor won't run after this function again.

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

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The layer with updated counts.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = recompute_counts_steps['output_layer_name']
    processing_step = recompute_counts_steps['step_name']

    fields = layer.keywords['inasafe_fields']

    if size_field['key'] not in fields:
        # noinspection PyTypeChecker
        msg = '%s not found in %s' % (size_field['key'],
                                      layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    indexes = []
    absolute_field_keys = [f['key'] for f in count_fields]
    for field, field_name in fields.iteritems():
        if field in absolute_field_keys and field != size_field['key']:
            indexes.append(layer.fieldNameIndex(field_name))
            LOGGER.info(
                'We detected the count {field_name}, we will recompute the '
                'count according to the new size.'.format(
                    field_name=field_name))

    if not len(indexes):
        msg = 'Absolute field not found in the layer %s' % (
            layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    size_field_name = fields[size_field['key']]
    size_field_index = layer.fieldNameIndex(size_field_name)

    layer.startEditing()

    exposure_key = layer.keywords['exposure_keywords']['exposure']
    size_calculator = SizeCalculator(layer.crs(), layer.geometryType(),
                                     exposure_key)

    for feature in layer.getFeatures():
        old_size = feature[size_field_name]
        new_size = size(size_calculator=size_calculator,
                        geometry=feature.geometry())

        layer.changeAttributeValue(feature.id(), size_field_index, new_size)

        # Cross multiplication for each field
        for index in indexes:
            old_count = feature[index]
            try:
                new_value = new_size * old_count / old_size
            except TypeError:
                new_value = ''
            layer.changeAttributeValue(feature.id(), index, new_value)

    layer.commitChanges()

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer