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
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