Пример #1
0
    def post_process_value_relation_fields(self, e_layer: QgsVectorLayer,
                                           o_field: QgsField):
        project = QgsProject.instance()
        o_ews = o_field.editorWidgetSetup()
        o_widget_config = o_ews.config()
        o_referenced_layer_id = o_widget_config["Layer"]

        if o_referenced_layer_id not in self.__layer_data_by_id:
            e_referenced_layer = QgsValueRelationFieldFormatter.resolveLayer(
                o_widget_config, project)

            if e_referenced_layer:
                o_referenced_layer_id = e_referenced_layer.customProperty(
                    "remoteLayerId")

        # yet another check whether value relation resolver succeeded
        if o_referenced_layer_id not in self.__layer_data_by_id:
            self.warning.emit(
                self.tr("Bad attribute form configuration"),
                self.
                tr('Field "{}" in layer "{}" has no configured layer in the value relation widget.'
                   ).format(o_field.name(), e_layer.name()),
            )
            return

        e_referenced_layer_id = None
        for e_layer in project.mapLayers().values():
            o_layer_data = self.__layer_data_by_id[o_referenced_layer_id]

            if e_layer.customProperty(
                    "remoteSource") == o_layer_data["source"]:
                #  First try strict matching: the offline layer should have a "remoteSource" property
                e_referenced_layer_id = e_layer.id()
                break
            elif e_layer.name() == o_layer_data["name"]:
                #  If that did not work, go with loose matching
                e_referenced_layer_id = e_layer.id()
                break

        if not e_referenced_layer_id:
            self.warning.emit(
                self.tr("Bad attribute form configuration"),
                self.
                tr('Field "{}" in layer "{}" has no configured layer in the value relation widget.'
                   ).format(o_field.name(), e_layer.name()),
            )
            return

        e_widget_config = o_widget_config
        e_widget_config["Layer"] = e_referenced_layer_id
        e_layer.setEditorWidgetSetup(
            e_layer.fields().indexOf(o_field.name()),
            QgsEditorWidgetSetup(o_ews.type(), e_widget_config),
        )
Пример #2
0
 def addFieldKeepType(original, stat):
     """
     Adds a field to the output, keeping the same data type as the original
     """
     field = QgsField(original)
     field.setName(field.name() + '_' + stat)
     fields_to_join.append(field)
Пример #3
0
 def addFieldKeepType(original, stat):
     """
     Adds a field to the output, keeping the same data type as the original
     """
     field = QgsField(original)
     field.setName(field.name() + '_' + stat)
     fields_to_join.append(field)
Пример #4
0
 def addField(original, stat, type):
     """
     Adds a field to the output, with a specified type
     """
     field = QgsField(original)
     field.setName(field.name() + '_' + stat)
     field.setType(type)
     if type == QVariant.Double:
         field.setLength(20)
         field.setPrecision(6)
     fields_to_join.append(field)
Пример #5
0
 def addField(original, stat, type):
     """
     Adds a field to the output, with a specified type
     """
     field = QgsField(original)
     field.setName(field.name() + '_' + stat)
     field.setType(type)
     if type == QVariant.Double:
         field.setLength(20)
         field.setPrecision(6)
     fields_to_join.append(field)
Пример #6
0
 def __init__(
     self,
     layer_wrapper,
     field: QgsField,
     values: Set,
     pk_name: Optional[str] = None,
 ) -> None:
     from .layer_wrapper import LayerWrapper
     self.layer_wrapper: LayerWrapper = layer_wrapper
     self.name = field.name()
     self._alias = field.alias()
     self.type = field.type()
     self.unique_values = values
     self.pk_name = pk_name
Пример #7
0
def _add_id_column(layer):
    """Add an ID column if it's not present in the attribute table.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer
    """
    layer_purpose = layer.keywords['layer_purpose']
    mapping = {
        layer_purpose_exposure['key']: exposure_id_field,
        layer_purpose_hazard['key']: hazard_id_field,
        layer_purpose_aggregation['key']: aggregation_id_field
    }

    has_id_column = False
    for layer_type, field in mapping.iteritems():
        if layer_purpose == layer_type:
            safe_id = field
            if layer.keywords.get(field['key']):
                has_id_column = True
            break

    if not has_id_column:
        LOGGER.info(
            'We add an ID column in {purpose}'.format(purpose=layer_purpose))

        layer.startEditing()

        id_field = QgsField()
        id_field.setName(safe_id['field_name'])
        if isinstance(safe_id['type'], list):
            # Use the first element in the list of type
            id_field.setType(safe_id['type'][0])
        else:
            id_field.setType(safe_id['type'][0])
        id_field.setPrecision(safe_id['precision'])
        id_field.setLength(safe_id['length'])

        layer.addAttribute(id_field)

        new_index = layer.fieldNameIndex(id_field.name())

        for feature in layer.getFeatures():
            layer.changeAttributeValue(
                feature.id(), new_index, feature.id())

        layer.commitChanges()

        layer.keywords['inasafe_fields'][safe_id['key']] = (
            safe_id['field_name'])
Пример #8
0
def _add_id_column(layer):
    """Add an ID column if it's not present in the attribute table.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer
    """
    layer_purpose = layer.keywords['layer_purpose']
    mapping = {
        layer_purpose_exposure['key']: exposure_id_field,
        layer_purpose_hazard['key']: hazard_id_field,
        layer_purpose_aggregation['key']: aggregation_id_field
    }

    has_id_column = False
    for layer_type, field in mapping.iteritems():
        if layer_purpose == layer_type:
            safe_id = field
            if layer.keywords['inasafe_fields'].get(field['key']):
                has_id_column = True
            break

    if not has_id_column:
        LOGGER.info(
            'We add an ID column in {purpose}'.format(purpose=layer_purpose))

        layer.startEditing()

        id_field = QgsField()
        id_field.setName(safe_id['field_name'])
        if isinstance(safe_id['type'], list):
            # Use the first element in the list of type
            id_field.setType(safe_id['type'][0])
        else:
            id_field.setType(safe_id['type'][0])
        id_field.setPrecision(safe_id['precision'])
        id_field.setLength(safe_id['length'])

        layer.addAttribute(id_field)

        new_index = layer.fieldNameIndex(id_field.name())

        for feature in layer.getFeatures():
            layer.changeAttributeValue(
                feature.id(), new_index, feature.id())

        layer.commitChanges()

        layer.keywords['inasafe_fields'][safe_id['key']] = (
            safe_id['field_name'])
Пример #9
0
    def setValue(self, value):
        if type(value) != dict:
            return
        destinationFields = QgsFields()
        expressions = {}
        for field_def in value:
            f = QgsField(
                field_def.get('name'), field_def.get('type', QVariant.Invalid),
                field_def.get(
                    QVariant.typeToName(field_def.get('type',
                                                      QVariant.Invalid))),
                field_def.get('length', 0), field_def.get('precision', 0))
            try:
                expressions[f.name()] = field_def['expressions']
            except AttributeError:
                pass
            destinationFields.append(f)

        if len(destinationFields):
            self.fieldsView.setDestinationFields(destinationFields,
                                                 expressions)
    def _qgis_field_to_unfolded_field(self, field: QgsField) -> Field:
        """
        Analyze information about the field
        :param field: QGIS field
        :return: Unfolded field
        """
        field_name = field.name()
        field_type = field.type()
        format_ = ''
        if field_type in [QVariant.Int, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong]:
            type_, analyzer_type = 'integer', 'INT'
        elif field_type == QVariant.Double:
            type_, analyzer_type = 'real', 'FLOAT'
        elif field_type == QVariant.String:
            if field_name == self.GEOM_FIELD:
                type_, analyzer_type = 'geojson', 'PAIR_GEOMETRY_FROM_STRING'
            else:
                type_, analyzer_type = 'string', 'STRING'
        elif field_type == QVariant.Bool:
            type_, analyzer_type = ('boolean', 'BOOLEAN')
        # TODO: check date time formats
        elif field_type == QVariant.Date:
            type_, analyzer_type = ('date', 'DATE')
            format_ = 'YYYY/M/D'
        elif field_type == QVariant.DateTime:
            type_, analyzer_type = ('timestamp', 'DATETIME')
            format_ = 'YYYY/M/D H:m:s'
        elif field_type == QVariant.Time:
            type_, analyzer_type = ('timestamp', 'INT')
            format_ = 'H:m:s'
        # elif field_type == QVariant.ByteArray:
        #     type, analyzer_type = ('integer', 'INT')
        else:
            raise QgsPluginNotImplementedException(tr('Field type "{}" not implemented yet', field_type))

        return Field(field_name, type_, format_, analyzer_type)
Пример #11
0
def reclassify(layer, exposure_key=None, callback=None):
    """Reclassify a continuous vector layer.

    This function will modify the input.

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['title']
    processing_step = reclassify_vector_steps['step_name']

    # This layer should have this keyword, or it's a mistake from the dev.
    inasafe_fields = layer.keywords['inasafe_fields']
    continuous_column = inasafe_fields[hazard_value_field['key']]

    if exposure_key:
        classification_key = active_classification(layer.keywords,
                                                   exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')

    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s' %
            layer.keywords['layer_purpose'])

    continuous_index = layer.fieldNameIndex(continuous_column)

    classified_field = QgsField()
    classified_field.setType(hazard_class_field['type'])
    classified_field.setName(hazard_class_field['field_name'])
    classified_field.setLength(hazard_class_field['length'])
    classified_field.setPrecision(hazard_class_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fieldNameIndex(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[continuous_index]
        classified_value = _classified_value(source_value, thresholds)
        if not classified_value:
            layer.deleteFeature(feature.id())
        else:
            layer.changeAttributeValue(feature.id(), classified_field_index,
                                       classified_value)

    layer.commitChanges()
    layer.updateFields()

    # We transfer keywords to the output.
    inasafe_fields[hazard_class_field['key']] = (
        hazard_class_field['field_name'])

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    layer.keywords['value_map'] = value_map
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Пример #12
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
        field_type = self.TYPES[self.parameterAsEnum(parameters, self.FIELD_TYPE, context)]
        width = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
        precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
        code = self.parameterAsString(parameters, self.FORMULA, context)
        globalExpression = self.parameterAsString(parameters, self.GLOBAL, context)

        fields = source.fields()
        field = QgsField(field_name, field_type, '', width, precision)
        fields.append(field)
        new_ns = {}

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, source.wkbType(), source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        # Run global code
        if globalExpression.strip() != '':
            try:
                bytecode = compile(globalExpression, '<string>', 'exec')
                exec(bytecode, new_ns)
            except:
                raise QgsProcessingException(
                    self.tr("FieldPyculator code execute error.Global code block can't be executed!\n{0}\n{1}").format(
                        str(sys.exc_info()[0].__name__), str(sys.exc_info()[1])))

        # Replace all fields tags
        fields = source.fields()
        num = 0
        for field in fields:
            field_name = str(field.name())
            replval = '__attr[' + str(num) + ']'
            code = code.replace('<' + field_name + '>', replval)
            num += 1

        # Replace all special vars
        code = code.replace('$id', '__id')
        code = code.replace('$geom', '__geom')
        need_id = code.find('__id') != -1
        need_geom = code.find('__geom') != -1
        need_attrs = code.find('__attr') != -1

        # Compile
        try:
            bytecode = compile(code, '<string>', 'exec')
        except:
            raise QgsProcessingException(
                self.tr("FieldPyculator code execute error. Field code block can't be executed!\n{0}\n{1}").format(
                    str(sys.exc_info()[0].__name__), str(sys.exc_info()[1])))

        # Run
        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0

        for current, feat in enumerate(features):
            if feedback.isCanceled():
                break

            feedback.setProgress(int(current * total))
            attrs = feat.attributes()
            feat_id = feat.id()

            # Add needed vars
            if need_id:
                new_ns['__id'] = feat_id

            if need_geom:
                geom = feat.geometry()
                new_ns['__geom'] = geom

            if need_attrs:
                pyattrs = [a for a in attrs]
                new_ns['__attr'] = pyattrs

            # Clear old result
            if self.RESULT_VAR_NAME in new_ns:
                del new_ns[self.RESULT_VAR_NAME]

            # Exec
            exec(bytecode, new_ns)

            # Check result
            if self.RESULT_VAR_NAME not in new_ns:
                raise QgsProcessingException(
                    self.tr("FieldPyculator code execute error\n"
                            "Field code block does not return '{0}' variable! "
                            "Please declare this variable in your code!").format(self.RESULT_VAR_NAME))

            # Write feature
            attrs.append(new_ns[self.RESULT_VAR_NAME])
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

        return {self.OUTPUT: dest_id}
Пример #13
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        field_name = self.parameterAsString(parameters, self.FIELD_NAME,
                                            context)
        field_type = self.TYPES[self.parameterAsEnum(parameters,
                                                     self.FIELD_TYPE, context)]
        width = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
        precision = self.parameterAsInt(parameters, self.FIELD_PRECISION,
                                        context)
        code = self.parameterAsString(parameters, self.FORMULA, context)
        globalExpression = self.parameterAsString(parameters, self.GLOBAL,
                                                  context)

        fields = source.fields()
        field = QgsField(field_name, field_type, '', width, precision)
        fields.append(field)
        new_ns = {}

        (sink, dest_id) = self.parameterAsSink(parameters,
                                               self.OUTPUT, context, fields,
                                               source.wkbType(),
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        # Run global code
        if globalExpression.strip() != '':
            try:
                bytecode = compile(globalExpression, '<string>', 'exec')
                exec(bytecode, new_ns)
            except:
                raise QgsProcessingException(
                    self.
                    tr("FieldPyculator code execute error.Global code block can't be executed!\n{0}\n{1}"
                       ).format(str(sys.exc_info()[0].__name__),
                                str(sys.exc_info()[1])))

        # Replace all fields tags
        fields = source.fields()
        num = 0
        for field in fields:
            field_name = str(field.name())
            replval = '__attr[' + str(num) + ']'
            code = code.replace('<' + field_name + '>', replval)
            num += 1

        # Replace all special vars
        code = code.replace('$id', '__id')
        code = code.replace('$geom', '__geom')
        need_id = code.find('__id') != -1
        need_geom = code.find('__geom') != -1
        need_attrs = code.find('__attr') != -1

        # Compile
        try:
            bytecode = compile(code, '<string>', 'exec')
        except:
            raise QgsProcessingException(
                self.
                tr("FieldPyculator code execute error. Field code block can't be executed!\n{0}\n{1}"
                   ).format(str(sys.exc_info()[0].__name__),
                            str(sys.exc_info()[1])))

        # Run
        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0

        for current, feat in enumerate(features):
            if feedback.isCanceled():
                break

            feedback.setProgress(int(current * total))
            attrs = feat.attributes()
            feat_id = feat.id()

            # Add needed vars
            if need_id:
                new_ns['__id'] = feat_id

            if need_geom:
                geom = feat.geometry()
                new_ns['__geom'] = geom

            if need_attrs:
                pyattrs = [a for a in attrs]
                new_ns['__attr'] = pyattrs

            # Clear old result
            if self.RESULT_VAR_NAME in new_ns:
                del new_ns[self.RESULT_VAR_NAME]

            # Exec
            exec(bytecode, new_ns)

            # Check result
            if self.RESULT_VAR_NAME not in new_ns:
                raise QgsProcessingException(
                    self.tr(
                        "FieldPyculator code execute error\n"
                        "Field code block does not return '{0}' variable! "
                        "Please declare this variable in your code!").format(
                            self.RESULT_VAR_NAME))

            # Write feature
            attrs.append(new_ns[self.RESULT_VAR_NAME])
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

        return {self.OUTPUT: dest_id}
Пример #14
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )

        # If source was not found, throw an exception to indicate that the
        # algorithm encountered a fatal error. The exception text can be any
        # string, but in this case we use the pre-built invalidSourceError
        # method to return a standard helper text for when a source cannot be
        # evaluated
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        fields_to_transform = self.parameterAsFields(
            parameters, self.FIELDS_TO_TRANSFORM, context)
        source_fields = source.fields()
        if not fields_to_transform:
            # no fields selected, use all numeric ones
            fields_to_transform = [source_fields.at(i).name()
                                   for i in range(len(source_fields))
                                   if source_fields.at(i).isNumeric()]
        self.transformed_fields = QgsFields()

        transformation_name = self.name()

        fields_to_transform_idxs = []
        for f in fields_to_transform:
            idx = source.fields().lookupField(f)
            if idx >= 0:
                fields_to_transform_idxs.append(idx)
                field_to_transform = source.fields().at(idx)
                if field_to_transform.isNumeric():
                    transformed_field = QgsField(field_to_transform)
                    transformed_field.setName(
                        "%s_%s" % (field_to_transform.name(),
                                   transformation_name))
                    transformed_field.setType(QVariant.Double)
                    transformed_field.setLength(20)
                    transformed_field.setPrecision(6)
                    self.transformed_fields.append(transformed_field)

        out_fields = QgsProcessingUtils.combineFields(
            source_fields, self.transformed_fields)

        (sink, self.dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            out_fields,
            source.wkbType(),
            source.sourceCrs()
        )

        # Send some information to the user
        feedback.pushInfo('CRS is {}'.format(source.sourceCrs().authid()))

        # If sink was not created, throw an exception to indicate that the
        # algorithm encountered a fatal error. The exception text can be any
        # string, but in this case we use the pre-built invalidSinkError method
        # to return a standard helper text for when a sink cannot be evaluated
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        total = 100.0 / len(fields_to_transform)
        transformed_values = {}

        for current, fieldname_to_transform in enumerate(fields_to_transform):
            original_values = vector.values(
                source, fieldname_to_transform)[fieldname_to_transform]
            transformed_values[fieldname_to_transform] = self.transform_values(
                original_values, parameters, context)
            feedback.setProgress(int(current * total))

        feedback.setProgress(0)

        # Compute the number of steps to display within the progress bar and
        # get features from source
        total = 100.0 / source.featureCount() if source.featureCount() else 0

        for current, source_feature in enumerate(source.getFeatures()):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                break

            sink_feature = QgsFeature(out_fields)

            # copy original fields
            for field in source.fields():
                sink_feature[field.name()] = source_feature[field.name()]

            for original_fieldname, transformed_field in zip(
                    fields_to_transform, self.transformed_fields):
                sink_feature[transformed_field.name()] = \
                    transformed_values[original_fieldname][current]
            sink_feature.setGeometry(source_feature.geometry())

            # Add a feature in the sink
            sink.addFeature(sink_feature, QgsFeatureSink.FastInsert)

            # Update the progress bar
            feedback.setProgress(int(current * total))

        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some
        # algorithms may return multiple feature sinks, calculated numeric
        # statistics, etc. These should all be included in the returned
        # dictionary, with keys matching the feature corresponding parameter
        # or output names.
        return {self.OUTPUT: self.dest_id}
        def _test(autoTransaction):
            """Test buffer methods within and without transactions

            - create a feature
            - save
            - retrieve the feature
            - change geom and attrs
            - test changes are seen in the buffer
            """

            def _check_feature(wkt):

                f = next(layer_a.getFeatures())
                self.assertEqual(f.geometry().asWkt().upper(), wkt)
                f = list(buffer.addedFeatures().values())[0]
                self.assertEqual(f.geometry().asWkt().upper(), wkt)

            ml = QgsVectorLayer('Point?crs=epsg:4326&field=int:integer', 'test', 'memory')
            self.assertTrue(ml.isValid())

            d = QTemporaryDir()
            options = QgsVectorFileWriter.SaveVectorOptions()
            options.driverName = 'GPKG'
            options.layerName = 'layer_a'
            err, _ = QgsVectorFileWriter.writeAsVectorFormatV2(ml, os.path.join(d.path(), 'transaction_test.gpkg'), QgsCoordinateTransformContext(), options)

            self.assertEqual(err, QgsVectorFileWriter.NoError)
            self.assertTrue(os.path.isfile(os.path.join(d.path(), 'transaction_test.gpkg')))

            options.layerName = 'layer_b'
            options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
            err, _ = QgsVectorFileWriter.writeAsVectorFormatV2(ml, os.path.join(d.path(), 'transaction_test.gpkg'), QgsCoordinateTransformContext(), options)

            layer_a = QgsVectorLayer(os.path.join(d.path(), 'transaction_test.gpkg|layername=layer_a'))

            self.assertTrue(layer_a.isValid())

            project = QgsProject()
            project.setAutoTransaction(autoTransaction)
            project.addMapLayers([layer_a])

            ###########################################
            # Tests with a new feature

            self.assertTrue(layer_a.startEditing())
            buffer = layer_a.editBuffer()

            f = QgsFeature(layer_a.fields())
            f.setAttribute('int', 123)
            f.setGeometry(QgsGeometry.fromWkt('point(7 45)'))
            self.assertTrue(layer_a.addFeatures([f]))

            _check_feature('POINT (7 45)')

            # Need to fetch the feature because its ID is NULL (-9223372036854775808)
            f = next(layer_a.getFeatures())

            self.assertEqual(len(buffer.addedFeatures()), 1)
            layer_a.undoStack().undo()
            self.assertEqual(len(buffer.addedFeatures()), 0)
            layer_a.undoStack().redo()
            self.assertEqual(len(buffer.addedFeatures()), 1)
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 123)

            # Now change attribute
            self.assertEqual(buffer.changedAttributeValues(), {})
            layer_a.changeAttributeValue(f.id(), 1, 321)

            self.assertEqual(len(buffer.addedFeatures()), 1)
            # This is surprising: because it was a new feature it has been changed directly
            self.assertEqual(buffer.changedAttributeValues(), {})
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 321)

            layer_a.undoStack().undo()
            self.assertEqual(buffer.changedAttributeValues(), {})
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 123)
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attribute('int'), 123)

            # Change geometry
            f = next(layer_a.getFeatures())
            self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)')))

            _check_feature('POINT (9 43)')
            self.assertEqual(buffer.changedGeometries(), {})

            layer_a.undoStack().undo()

            _check_feature('POINT (7 45)')
            self.assertEqual(buffer.changedGeometries(), {})

            self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)')))
            _check_feature('POINT (9 43)')

            self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(10 44)')))
            _check_feature('POINT (10 44)')

            # This is anothr surprise: geometry edits get collapsed into a single
            # one because they have the same hardcoded id
            layer_a.undoStack().undo()
            _check_feature('POINT (7 45)')

            self.assertTrue(layer_a.commitChanges())

            ###########################################
            # Tests with the existing feature

            # Get the feature
            f = next(layer_a.getFeatures())
            self.assertTrue(f.isValid())
            self.assertEqual(f.attribute('int'), 123)
            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)')

            self.assertTrue(layer_a.startEditing())
            layer_a.changeAttributeValue(f.id(), 1, 321)
            buffer = layer_a.editBuffer()
            self.assertEqual(buffer.changedAttributeValues(), {1: {1: 321}})
            layer_a.undoStack().undo()
            self.assertEqual(buffer.changedAttributeValues(), {})

            # Change geometry
            self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)')))
            f = next(layer_a.getFeatures())
            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (9 43)')
            self.assertEqual(buffer.changedGeometries()[1].asWkt().upper(), 'POINT (9 43)')
            layer_a.undoStack().undo()
            self.assertEqual(buffer.changedGeometries(), {})
            f = next(layer_a.getFeatures())

            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)')
            self.assertEqual(buffer.changedGeometries(), {})

            # Delete an existing feature
            self.assertTrue(layer_a.deleteFeature(f.id()))
            with self.assertRaises(StopIteration):
                next(layer_a.getFeatures())
            self.assertEqual(buffer.deletedFeatureIds(), [f.id()])

            layer_a.undoStack().undo()
            self.assertTrue(layer_a.getFeature(f.id()).isValid())
            self.assertEqual(buffer.deletedFeatureIds(), [])

            ###########################################
            # Test delete

            # Delete a new feature
            f = QgsFeature(layer_a.fields())
            f.setAttribute('int', 555)
            f.setGeometry(QgsGeometry.fromWkt('point(8 46)'))
            self.assertTrue(layer_a.addFeatures([f]))
            f = [f for f in layer_a.getFeatures() if f.attribute('int') == 555][0]
            self.assertTrue(f.id() in buffer.addedFeatures())
            self.assertTrue(layer_a.deleteFeature(f.id()))
            self.assertFalse(f.id() in buffer.addedFeatures())
            self.assertFalse(f.id() in buffer.deletedFeatureIds())

            layer_a.undoStack().undo()
            self.assertTrue(f.id() in buffer.addedFeatures())

            ###########################################
            # Add attribute

            field = QgsField('attr1', QVariant.String)
            self.assertTrue(layer_a.addAttribute(field))
            self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [field])

            layer_a.undoStack().undo()
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [])

            layer_a.undoStack().redo()
            self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [field])

            self.assertTrue(layer_a.commitChanges())

            ###########################################
            # Remove attribute

            self.assertTrue(layer_a.startEditing())
            buffer = layer_a.editBuffer()

            attr_idx = layer_a.fields().lookupField(field.name())
            self.assertNotEqual(attr_idx, -1)

            self.assertTrue(layer_a.deleteAttribute(attr_idx))
            self.assertEqual(buffer.deletedAttributeIds(), [2])
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)

            layer_a.undoStack().undo()
            self.assertEqual(buffer.deletedAttributeIds(), [])
            self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx)

            layer_a.undoStack().redo()
            self.assertEqual(buffer.deletedAttributeIds(), [2])
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)

            self.assertTrue(layer_a.rollBack())

            ###########################################
            # Rename attribute

            self.assertTrue(layer_a.startEditing())

            attr_idx = layer_a.fields().lookupField(field.name())
            self.assertNotEqual(attr_idx, -1)

            self.assertEqual(layer_a.fields().lookupField('new_name'), -1)
            self.assertTrue(layer_a.renameAttribute(attr_idx, 'new_name'))
            self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx)

            layer_a.undoStack().undo()
            self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx)
            self.assertEqual(layer_a.fields().lookupField('new_name'), -1)

            layer_a.undoStack().redo()
            self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx)
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)
Пример #16
0
    def saveAutoField(self):
        """ Do some validation and then call AutoFieldManager """

        # Check layers
        if not self.tblLayers.selectedItems():
            self.msg.show(
                QApplication.translate(
                    "AutoFieldsDockWidgetPy",
                    "[Warning] Please first select a layer."), 'warning')
            return

        # Check expression
        expression = u''
        if self.optXCoord.isChecked():
            expression = u'$x'
        elif self.optYCoord.isChecked():
            expression = u'$y'
        elif self.optLength.isChecked():
            expression = u'$length'
        elif self.optPerimeter.isChecked():
            expression = u'$perimeter'
        elif self.optArea.isChecked():
            expression = u'$area'
        elif self.optDate.isChecked():
            expression = u'now()'
        elif self.optCustomExpression.isChecked():
            if self.expressionDlg:
                expression = self.expressionDlg.expression
            if not self.expressionDlg or not expression:
                self.msg.show(
                    QApplication.translate(
                        "AutoFieldsDockWidgetPy",
                        "[Warning] Please first set a valid custom expression."
                    ), 'warning')
                return
        else:  # optSpatialValue
            pass

        # Check fields
        fieldName = ''
        if self.optNewField.isChecked():
            if self.txtFieldName.text():

                fieldName = self.txtFieldName.text().strip()
                newField = QgsField(
                    fieldName,
                    self.cboFieldType.itemData(
                        self.cboFieldType.currentIndex(), Qt.UserRole))

                length = self.txtFieldLength.value()
                precision = self.txtFieldPrecision.value()
                # Ensure length and precision are valid values when dealing with Real numbers
                if self.fieldTypesDict[
                        self.cboFieldType.currentIndex()] == 'Real':
                    if precision > length:
                        precision = length
                newField.setLength(length)
                newField.setPrecision(precision)

                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 layer.fieldNameIndex(fieldName) != -1:
                            self.msg.show(
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    "[Error] The field " ) + fieldName + \
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    " already exists in layer " ) + layer.name() + ". " + \
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    " If you want to create an AutoField on it, you need to choose it from 'Existing Field' list." ),
                                'warning' )
                        else:
                            res = layer.dataProvider().addAttributes(
                                [newField])
                            if res:
                                layer.updateFields()

                                # Check if fieldName is preserved by the provider after field creation.
                                if layer.fieldNameIndex(fieldName) == -1:
                                    self.msg.show(
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            "[Error] The field " ) + fieldName + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            " was probably created with another name by the layer (" ) + \
                                        layer.name() + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            ") provider. " ) + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            " If you want to create an AutoField on it, you need to choose it from 'Existing Field' list." ),
                                        'warning' )
                                else:

                                    self.doSaveAutoField(
                                        layer, fieldName, expression)

                            else:
                                self.msg.show( QApplication.translate( "AutoFieldsDockWidgetPy",
                                    "[Error] Couldn't create " ) + newField.name() + \
                                    QApplication.translate( "AutoFieldsDockWidgetPy",
                                        " field in " ) + layer.name() + \
                                    QApplication.translate( "AutoFieldsDockWidgetPy", " layer." ),
                                    'warning' )

                # Some fields might have been created, update the field list once
                self.updateFieldList()

            else:
                self.msg.show(
                    QApplication.translate(
                        "AutoFieldsDockWidgetPy",
                        "[Warning] Please first set a name for the new field."
                    ), 'warning')
                return
        else:
            fieldName = self.cboField.currentText()

            for item in self.tblLayers.selectedItems():
                if item.column() == 1:  # It's the layer name item
                    layer = QgsMapLayerRegistry.instance().mapLayer(
                        item.data(Qt.UserRole))
                    self.doSaveAutoField(layer, fieldName, expression)
Пример #17
0
    def saveAutoField( self ):
        """ Do some validation and then call AutoFieldManager """

        # Check layers
        if not self.tblLayers.selectedItems():
            self.msg.show( QApplication.translate( "AutoFieldsDockWidgetPy",
                "[Warning] Please first select a layer." ), 'warning' )
            return

        # Check expression
        expression = u''
        if self.optXCoord.isChecked():
            expression = u'$x'
        elif self.optYCoord.isChecked():
            expression = u'$y'
        elif self.optLength.isChecked():
            expression = u'$length'
        elif self.optPerimeter.isChecked():
            expression = u'$perimeter'
        elif self.optArea.isChecked():
            expression = u'$area'
        elif self.optDate.isChecked():
            expression = u'now()'
        elif self.optCustomExpression.isChecked():
            if self.expressionDlg:
                expression = self.expressionDlg.expression
            if not self.expressionDlg or not expression:
                self.msg.show( QApplication.translate( "AutoFieldsDockWidgetPy",
                    "[Warning] Please first set a valid custom expression." ),
                    'warning' )
                return
        else: # optSpatialValue
            pass

        # Check fields
        fieldName = ''
        if self.optNewField.isChecked():
            if self.txtFieldName.text():

                fieldName = self.txtFieldName.text().strip()
                newField = QgsField( fieldName,
                    self.cboFieldType.itemData( self.cboFieldType.currentIndex(), Qt.UserRole) )

                length = self.txtFieldLength.value()
                precision = self.txtFieldPrecision.value()
                # Ensure length and precision are valid values when dealing with Real numbers
                if self.fieldTypesDict[self.cboFieldType.currentIndex()] == 'Real':
                    if precision > length:
                        precision = length
                newField.setLength( length )
                newField.setPrecision( precision )

                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 layer.fieldNameIndex( fieldName ) != -1:
                            self.msg.show(
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    "[Error] The field " ) + fieldName + \
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    " already exists in layer " ) + layer.name() + ". " + \
                                QApplication.translate( "AutoFieldsDockWidgetPy",
                                    " If you want to create an AutoField on it, you need to choose it from 'Existing Field' list." ),
                                'warning' )
                        else:
                            res = layer.dataProvider().addAttributes( [ newField ] )
                            if res:
                                layer.updateFields()

                                # Check if fieldName is preserved by the provider after field creation.
                                if layer.fieldNameIndex( fieldName ) == -1:
                                    self.msg.show(
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            "[Error] The field " ) + fieldName + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            " was probably created with another name by the layer (" ) + \
                                        layer.name() + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            ") provider. " ) + \
                                        QApplication.translate( "AutoFieldsDockWidgetPy",
                                            " If you want to create an AutoField on it, you need to choose it from 'Existing Field' list." ),
                                        'warning' )
                                else:

                                    self.doSaveAutoField( layer, fieldName, expression )

                            else:
                                self.msg.show( QApplication.translate( "AutoFieldsDockWidgetPy",
                                    "[Error] Couldn't create " ) + newField.name() + \
                                    QApplication.translate( "AutoFieldsDockWidgetPy",
                                        " field in " ) + layer.name() + \
                                    QApplication.translate( "AutoFieldsDockWidgetPy", " layer." ),
                                    'warning' )

                # Some fields might have been created, update the field list once
                self.updateFieldList()

            else:
                self.msg.show( QApplication.translate( "AutoFieldsDockWidgetPy",
                    "[Warning] Please first set a name for the new field." ), 'warning' )
                return
        else:
            fieldName = self.cboField.currentText()

            for item in self.tblLayers.selectedItems():
                if item.column() == 1: # It's the layer name item
                    layer = QgsMapLayerRegistry.instance().mapLayer( item.data( Qt.UserRole ) )
                    self.doSaveAutoField( layer, fieldName, expression )
Пример #18
0
def reclassify(layer, exposure_key=None):
    """Reclassify a continuous vector layer.

    This function will modify the input.

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :return: The classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['title']

    # This layer should have this keyword, or it's a mistake from the dev.
    inasafe_fields = layer.keywords['inasafe_fields']
    continuous_column = inasafe_fields[hazard_value_field['key']]

    if exposure_key:
        classification_key = active_classification(
            layer.keywords, exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')

    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s'
            % layer.keywords['layer_purpose'])

    continuous_index = layer.fields().lookupField(continuous_column)

    classified_field = QgsField()
    classified_field.setType(hazard_class_field['type'])
    classified_field.setName(hazard_class_field['field_name'])
    classified_field.setLength(hazard_class_field['length'])
    classified_field.setPrecision(hazard_class_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fields(). \
        lookupField(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[continuous_index]
        classified_value = reclassify_value(source_value, thresholds)
        if (classified_value is None
                or (hasattr(classified_value, 'isNull')
                    and classified_value.isNull())):
            layer.deleteFeature(feature.id())
        else:
            layer.changeAttributeValue(
                feature.id(), classified_field_index, classified_value)

    layer.commitChanges()
    layer.updateFields()

    # We transfer keywords to the output.
    inasafe_fields[hazard_class_field['key']] = (
        hazard_class_field['field_name'])

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    layer.keywords['value_map'] = value_map
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Пример #19
0
def update_value_map(layer, exposure_key=None, callback=None):
    """Assign inasafe values according to definitions for a vector layer.

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

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_inasafe_values_steps['output_layer_name']
    processing_step = assign_inasafe_values_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    keywords = layer.keywords
    inasafe_fields = keywords['inasafe_fields']

    classification = None
    if keywords['layer_purpose'] == layer_purpose_hazard['key']:
        if not inasafe_fields.get(hazard_value_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = hazard_value_field
        new_field = hazard_class_field
        classification = active_classification(layer.keywords, exposure_key)

    elif keywords['layer_purpose'] == layer_purpose_exposure['key']:
        if not inasafe_fields.get(exposure_type_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = exposure_type_field
        new_field = exposure_class_field
    else:
        raise InvalidKeywordsForProcessingAlgorithm

    # It's a hazard layer
    if exposure_key:
        if not active_thresholds_value_maps(keywords, exposure_key):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = active_thresholds_value_maps(keywords, exposure_key)
    # It's exposure layer
    else:
        if not keywords.get('value_map'):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = keywords.get('value_map')

    unclassified_column = inasafe_fields[old_field['key']]
    unclassified_index = layer.fieldNameIndex(unclassified_column)

    reversed_value_map = {}
    for inasafe_class, values in value_map.iteritems():
        for val in values:
            reversed_value_map[val] = inasafe_class

    classified_field = QgsField()
    classified_field.setType(new_field['type'])
    classified_field.setName(new_field['field_name'])
    classified_field.setLength(new_field['length'])
    classified_field.setPrecision(new_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fieldNameIndex(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[unclassified_index]
        classified_value = reversed_value_map.get(source_value)

        if not classified_value:
            classified_value = ''

        layer.changeAttributeValue(
            feature.id(), classified_field_index, classified_value)

    layer.commitChanges()

    remove_fields(layer, [unclassified_column])

    # We transfer keywords to the output.
    # We add new class field
    inasafe_fields[new_field['key']] = new_field['field_name']

    # and we remove hazard value field
    inasafe_fields.pop(old_field['key'])

    layer.keywords = keywords
    layer.keywords['inasafe_fields'] = inasafe_fields
    if exposure_key:
        value_map_key = 'value_maps'
    else:
        value_map_key = 'value_map'
    if value_map_key in layer.keywords.keys():
        layer.keywords.pop(value_map_key)
    layer.keywords['title'] = output_layer_name
    if classification:
        layer.keywords['classification'] = classification

    check_layer(layer)
    return layer
Пример #20
0
        def _test(autoTransaction):
            """Test buffer methods within and without transactions

            - create a feature
            - save
            - retrieve the feature
            - change geom and attrs
            - test changes are seen in the buffer
            """
            def _check_feature(wkt):

                f = next(layer_a.getFeatures())
                self.assertEqual(f.geometry().asWkt().upper(), wkt)
                f = list(buffer.addedFeatures().values())[0]
                self.assertEqual(f.geometry().asWkt().upper(), wkt)

            ml = QgsVectorLayer(
                'Point?crs=epsg:4326&field=int:integer&field=int2:integer',
                'test', 'memory')
            self.assertTrue(ml.isValid())

            d = QTemporaryDir()
            options = QgsVectorFileWriter.SaveVectorOptions()
            options.driverName = 'GPKG'
            options.layerName = 'layer_a'
            err, msg, newFileName, newLayer = QgsVectorFileWriter.writeAsVectorFormatV3(
                ml, os.path.join(d.path(), 'transaction_test.gpkg'),
                QgsCoordinateTransformContext(), options)

            self.assertEqual(err, QgsVectorFileWriter.NoError)
            self.assertTrue(os.path.isfile(newFileName))

            layer_a = QgsVectorLayer(newFileName + '|layername=layer_a')

            self.assertTrue(layer_a.isValid())

            project = QgsProject()
            project.setAutoTransaction(autoTransaction)
            project.addMapLayers([layer_a])

            ###########################################
            # Tests with a new feature

            self.assertTrue(layer_a.startEditing())
            buffer = layer_a.editBuffer()

            f = QgsFeature(layer_a.fields())
            f.setAttribute('int', 123)
            f.setGeometry(QgsGeometry.fromWkt('point(7 45)'))
            self.assertTrue(layer_a.addFeatures([f]))

            _check_feature('POINT (7 45)')

            # Need to fetch the feature because its ID is NULL (-9223372036854775808)
            f = next(layer_a.getFeatures())

            self.assertEqual(len(buffer.addedFeatures()), 1)
            layer_a.undoStack().undo()
            self.assertEqual(len(buffer.addedFeatures()), 0)
            layer_a.undoStack().redo()
            self.assertEqual(len(buffer.addedFeatures()), 1)
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 123)

            # Now change attribute
            self.assertEqual(buffer.changedAttributeValues(), {})
            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.changeAttributeValue(f.id(), 1, 321)
            self.assertEqual(len(spy_attribute_changed), 1)
            self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321])

            self.assertEqual(len(buffer.addedFeatures()), 1)
            # This is surprising: because it was a new feature it has been changed directly
            self.assertEqual(buffer.changedAttributeValues(), {})
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 321)

            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.undoStack().undo()
            self.assertEqual(len(spy_attribute_changed), 1)
            self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 123])
            self.assertEqual(buffer.changedAttributeValues(), {})
            f = list(buffer.addedFeatures().values())[0]
            self.assertEqual(f.attribute('int'), 123)
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attribute('int'), 123)

            # Change multiple attributes
            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.changeAttributeValues(f.id(), {1: 321, 2: 456})
            self.assertEqual(len(spy_attribute_changed), 2)
            self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321])
            self.assertEqual(spy_attribute_changed[1], [f.id(), 2, 456])
            buffer = layer_a.editBuffer()
            # This is surprising: because it was a new feature it has been changed directly
            self.assertEqual(buffer.changedAttributeValues(), {})

            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.undoStack().undo()
            # This is because QgsVectorLayerUndoCommandChangeAttribute plural
            if not autoTransaction:
                layer_a.undoStack().undo()
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attribute('int'), 123)
            self.assertEqual(f.attribute('int2'), None)
            self.assertEqual(len(spy_attribute_changed), 2)
            self.assertEqual(
                spy_attribute_changed[1 if autoTransaction else 0],
                [f.id(), 2, None])
            self.assertEqual(
                spy_attribute_changed[0 if autoTransaction else 1],
                [f.id(), 1, 123])

            # Change geometry
            f = next(layer_a.getFeatures())
            spy_geometry_changed = QSignalSpy(layer_a.geometryChanged)
            self.assertTrue(
                layer_a.changeGeometry(f.id(),
                                       QgsGeometry.fromWkt('point(9 43)')))
            self.assertTrue(len(spy_geometry_changed), 1)
            self.assertEqual(spy_geometry_changed[0][0], f.id())
            self.assertEqual(spy_geometry_changed[0][1].asWkt(),
                             QgsGeometry.fromWkt('point(9 43)').asWkt())

            _check_feature('POINT (9 43)')
            self.assertEqual(buffer.changedGeometries(), {})

            layer_a.undoStack().undo()

            _check_feature('POINT (7 45)')
            self.assertEqual(buffer.changedGeometries(), {})

            self.assertTrue(
                layer_a.changeGeometry(f.id(),
                                       QgsGeometry.fromWkt('point(9 43)')))
            _check_feature('POINT (9 43)')

            self.assertTrue(
                layer_a.changeGeometry(f.id(),
                                       QgsGeometry.fromWkt('point(10 44)')))
            _check_feature('POINT (10 44)')

            # This is another surprise: geometry edits get collapsed into a single
            # one because they have the same hardcoded id
            layer_a.undoStack().undo()
            _check_feature('POINT (7 45)')

            self.assertTrue(layer_a.commitChanges())

            ###########################################
            # Tests with the existing feature

            # Get the feature
            f = next(layer_a.getFeatures())
            self.assertTrue(f.isValid())
            self.assertEqual(f.attribute('int'), 123)
            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)')

            # Change single attribute
            self.assertTrue(layer_a.startEditing())
            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.changeAttributeValue(f.id(), 1, 321)
            self.assertEqual(len(spy_attribute_changed), 1)
            self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321])
            buffer = layer_a.editBuffer()
            self.assertEqual(buffer.changedAttributeValues(), {1: {1: 321}})

            f = next(layer_a.getFeatures())
            self.assertEqual(f.attribute(1), 321)

            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.undoStack().undo()
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attribute(1), 123)
            self.assertEqual(len(spy_attribute_changed), 1)
            self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 123])
            self.assertEqual(buffer.changedAttributeValues(), {})

            # Change attributes
            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.changeAttributeValues(f.id(), {1: 111, 2: 654})
            self.assertEqual(len(spy_attribute_changed), 2)
            self.assertEqual(spy_attribute_changed[0], [1, 1, 111])
            self.assertEqual(spy_attribute_changed[1], [1, 2, 654])
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attributes(), [1, 111, 654])
            self.assertEqual(buffer.changedAttributeValues(),
                             {1: {
                                 1: 111,
                                 2: 654
                             }})

            spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged)
            layer_a.undoStack().undo()
            # This is because QgsVectorLayerUndoCommandChangeAttribute plural
            if not autoTransaction:
                layer_a.undoStack().undo()
            self.assertEqual(len(spy_attribute_changed), 2)
            self.assertEqual(
                spy_attribute_changed[0 if autoTransaction else 1],
                [1, 1, 123])
            self.assertEqual(
                spy_attribute_changed[1 if autoTransaction else 0],
                [1, 2, None])
            f = next(layer_a.getFeatures())
            self.assertEqual(f.attributes(), [1, 123, None])
            self.assertEqual(buffer.changedAttributeValues(), {})

            # Change geometry
            spy_geometry_changed = QSignalSpy(layer_a.geometryChanged)
            self.assertTrue(
                layer_a.changeGeometry(f.id(),
                                       QgsGeometry.fromWkt('point(9 43)')))
            self.assertEqual(spy_geometry_changed[0][0], 1)
            self.assertEqual(spy_geometry_changed[0][1].asWkt(),
                             QgsGeometry.fromWkt('point(9 43)').asWkt())

            f = next(layer_a.getFeatures())
            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (9 43)')
            self.assertEqual(buffer.changedGeometries()[1].asWkt().upper(),
                             'POINT (9 43)')

            spy_geometry_changed = QSignalSpy(layer_a.geometryChanged)
            layer_a.undoStack().undo()
            self.assertEqual(spy_geometry_changed[0][0], 1)
            self.assertEqual(spy_geometry_changed[0][1].asWkt(),
                             QgsGeometry.fromWkt('point(7 45)').asWkt())
            self.assertEqual(buffer.changedGeometries(), {})
            f = next(layer_a.getFeatures())

            self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)')
            self.assertEqual(buffer.changedGeometries(), {})

            # Delete an existing feature
            self.assertTrue(layer_a.deleteFeature(f.id()))
            with self.assertRaises(StopIteration):
                next(layer_a.getFeatures())
            self.assertEqual(buffer.deletedFeatureIds(), [f.id()])

            layer_a.undoStack().undo()
            self.assertTrue(layer_a.getFeature(f.id()).isValid())
            self.assertEqual(buffer.deletedFeatureIds(), [])

            ###########################################
            # Test delete

            # Delete a new feature
            f = QgsFeature(layer_a.fields())
            f.setAttribute('int', 555)
            f.setGeometry(QgsGeometry.fromWkt('point(8 46)'))
            self.assertTrue(layer_a.addFeatures([f]))
            f = [
                f for f in layer_a.getFeatures() if f.attribute('int') == 555
            ][0]
            self.assertTrue(f.id() in buffer.addedFeatures())
            self.assertTrue(layer_a.deleteFeature(f.id()))
            self.assertFalse(f.id() in buffer.addedFeatures())
            self.assertFalse(f.id() in buffer.deletedFeatureIds())

            layer_a.undoStack().undo()
            self.assertTrue(f.id() in buffer.addedFeatures())

            ###########################################
            # Add attribute

            field = QgsField('attr1', QVariant.String)
            self.assertTrue(layer_a.addAttribute(field))
            self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [field])

            layer_a.undoStack().undo()
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [])

            layer_a.undoStack().redo()
            self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1)
            self.assertEqual(buffer.addedAttributes(), [field])

            self.assertTrue(layer_a.commitChanges())

            ###########################################
            # Remove attribute

            self.assertTrue(layer_a.startEditing())
            buffer = layer_a.editBuffer()

            attr_idx = layer_a.fields().lookupField(field.name())
            self.assertNotEqual(attr_idx, -1)

            self.assertTrue(layer_a.deleteAttribute(attr_idx))
            self.assertEqual(buffer.deletedAttributeIds(), [attr_idx])
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)

            layer_a.undoStack().undo()
            self.assertEqual(buffer.deletedAttributeIds(), [])
            self.assertEqual(layer_a.fields().lookupField(field.name()),
                             attr_idx)

            # This is totally broken at least on OGR/GPKG: the rollback
            # does not restore the original fields
            if False:

                layer_a.undoStack().redo()
                self.assertEqual(buffer.deletedAttributeIds(), [attr_idx])
                self.assertEqual(layer_a.fields().lookupField(field.name()),
                                 -1)

                # Rollback!
                self.assertTrue(layer_a.rollBack())

                self.assertIn('attr1', layer_a.dataProvider().fields().names())
                self.assertIn('attr1', layer_a.fields().names())
                self.assertEqual(layer_a.fields().names(),
                                 layer_a.dataProvider().fields().names())

                attr_idx = layer_a.fields().lookupField(field.name())
                self.assertNotEqual(attr_idx, -1)

                self.assertTrue(layer_a.startEditing())
                attr_idx = layer_a.fields().lookupField(field.name())
                self.assertNotEqual(attr_idx, -1)

            ###########################################
            # Rename attribute

            attr_idx = layer_a.fields().lookupField(field.name())
            self.assertEqual(layer_a.fields().lookupField('new_name'), -1)
            self.assertTrue(layer_a.renameAttribute(attr_idx, 'new_name'))
            self.assertEqual(layer_a.fields().lookupField('new_name'),
                             attr_idx)

            layer_a.undoStack().undo()
            self.assertEqual(layer_a.fields().lookupField(field.name()),
                             attr_idx)
            self.assertEqual(layer_a.fields().lookupField('new_name'), -1)

            layer_a.undoStack().redo()
            self.assertEqual(layer_a.fields().lookupField('new_name'),
                             attr_idx)
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)

            #############################################
            # Try hard to make this fail for transactions
            if autoTransaction:
                self.assertTrue(layer_a.commitChanges())
                self.assertTrue(layer_a.startEditing())
                f = next(layer_a.getFeatures())

                # Do
                for i in range(10):
                    spy_attribute_changed = QSignalSpy(
                        layer_a.attributeValueChanged)
                    layer_a.changeAttributeValue(f.id(), 2, i)
                    self.assertEqual(len(spy_attribute_changed), 1)
                    self.assertEqual(spy_attribute_changed[0], [f.id(), 2, i])
                    buffer = layer_a.editBuffer()
                    self.assertEqual(buffer.changedAttributeValues(),
                                     {f.id(): {
                                          2: i
                                      }})
                    f = next(layer_a.getFeatures())
                    self.assertEqual(f.attribute(2), i)

                # Undo/redo
                for i in range(9):

                    # Undo
                    spy_attribute_changed = QSignalSpy(
                        layer_a.attributeValueChanged)
                    layer_a.undoStack().undo()
                    f = next(layer_a.getFeatures())
                    self.assertEqual(f.attribute(2), 8 - i)
                    self.assertEqual(len(spy_attribute_changed), 1)
                    self.assertEqual(spy_attribute_changed[0],
                                     [f.id(), 2, 8 - i])
                    buffer = layer_a.editBuffer()
                    self.assertEqual(buffer.changedAttributeValues(),
                                     {f.id(): {
                                          2: 8 - i
                                      }})

                    # Redo
                    spy_attribute_changed = QSignalSpy(
                        layer_a.attributeValueChanged)
                    layer_a.undoStack().redo()
                    f = next(layer_a.getFeatures())
                    self.assertEqual(f.attribute(2), 9 - i)
                    self.assertEqual(len(spy_attribute_changed), 1)
                    self.assertEqual(spy_attribute_changed[0],
                                     [f.id(), 2, 9 - i])

                    # Undo again
                    spy_attribute_changed = QSignalSpy(
                        layer_a.attributeValueChanged)
                    layer_a.undoStack().undo()
                    f = next(layer_a.getFeatures())
                    self.assertEqual(f.attribute(2), 8 - i)
                    self.assertEqual(len(spy_attribute_changed), 1)
                    self.assertEqual(spy_attribute_changed[0],
                                     [f.id(), 2, 8 - i])
                    buffer = layer_a.editBuffer()
                    self.assertEqual(buffer.changedAttributeValues(),
                                     {f.id(): {
                                          2: 8 - i
                                      }})

                    # Last check
                    f = next(layer_a.getFeatures())
                    self.assertEqual(f.attribute(2), 8 - i)

                self.assertEqual(buffer.changedAttributeValues(),
                                 {f.id(): {
                                      2: 0
                                  }})
                layer_a.undoStack().undo()
                buffer = layer_a.editBuffer()
                self.assertEqual(buffer.changedAttributeValues(), {})
                f = next(layer_a.getFeatures())
                self.assertEqual(f.attribute(2), None)
Пример #21
0
def update_value_map(layer, exposure_key=None):
    """Assign inasafe values according to definitions for a vector layer.

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

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :return: The classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_inasafe_values_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    keywords = layer.keywords
    inasafe_fields = keywords['inasafe_fields']

    classification = None
    if keywords['layer_purpose'] == layer_purpose_hazard['key']:
        if not inasafe_fields.get(hazard_value_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = hazard_value_field
        new_field = hazard_class_field
        classification = active_classification(layer.keywords, exposure_key)

    elif keywords['layer_purpose'] == layer_purpose_exposure['key']:
        if not inasafe_fields.get(exposure_type_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = exposure_type_field
        new_field = exposure_class_field
    else:
        raise InvalidKeywordsForProcessingAlgorithm

    # It's a hazard layer
    if exposure_key:
        if not active_thresholds_value_maps(keywords, exposure_key):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = active_thresholds_value_maps(keywords, exposure_key)
    # It's exposure layer
    else:
        if not keywords.get('value_map'):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = keywords.get('value_map')

    unclassified_column = inasafe_fields[old_field['key']]
    unclassified_index = layer.fields().lookupField(unclassified_column)

    reversed_value_map = {}
    for inasafe_class, values in list(value_map.items()):
        for val in values:
            reversed_value_map[val] = inasafe_class

    classified_field = QgsField()
    classified_field.setType(new_field['type'])
    classified_field.setName(new_field['field_name'])
    classified_field.setLength(new_field['length'])
    classified_field.setPrecision(new_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fields(). \
        lookupField(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[unclassified_index]
        classified_value = reversed_value_map.get(source_value)

        if not classified_value:
            classified_value = ''

        layer.changeAttributeValue(feature.id(), classified_field_index,
                                   classified_value)

    layer.commitChanges()

    remove_fields(layer, [unclassified_column])

    # We transfer keywords to the output.
    # We add new class field
    inasafe_fields[new_field['key']] = new_field['field_name']

    # and we remove hazard value field
    inasafe_fields.pop(old_field['key'])

    layer.keywords = keywords
    layer.keywords['inasafe_fields'] = inasafe_fields
    if exposure_key:
        value_map_key = 'value_maps'
    else:
        value_map_key = 'value_map'
    if value_map_key in list(layer.keywords.keys()):
        layer.keywords.pop(value_map_key)
    layer.keywords['title'] = output_layer_name
    if classification:
        layer.keywords['classification'] = classification

    check_layer(layer)
    return layer
    def cloneToMemory(self):
        curlayer = self.iface.mapCanvas().currentLayer()
        selectedFeatCount = curlayer.selectedFeatureCount()
        geo = QgsWkbTypes.displayString(
            curlayer.wkbType())  # wkbType string name of geometry

        targetLayer = QgsVectorLayer(geo, self.dlg.lineEdit_2.text(), "memory")
        targetLayer.setCrs(curlayer.sourceCrs())
        QgsProject.instance().addMapLayer(targetLayer, False)
        root = QgsProject.instance().layerTreeRoot()

        self.setStyleLayer(targetLayer)

        if self.dlg.checkBoxAtrib.isChecked():  #copy attributes
            curlayer_attribute_list = curlayer.fields().toList()
            targetLayer_attribute_list = []
            targetLayerpr = targetLayer.dataProvider()

            for attrib in curlayer_attribute_list:
                if targetLayer.fields().lookupField(attrib.name()) == -1:
                    targetLayer_attribute_list.append(
                        QgsField(attrib.name(), attrib.type()))
            with edit(targetLayer):
                for attr in targetLayer_attribute_list:
                    if attr.type(
                    ) == 1:  # иначе игнорируется поле с типом 1 (bool)
                        attr = QgsField(
                            attr.name(),
                            QVariant.String)  # конвертируем bool в string
                    res_add = targetLayer.addAttribute(attr)
                    if not res_add:
                        print(u'Не создано поле {}'.format(attr.name()))
            targetLayer.updateFields()

        # for feat in curlayer.selectedFeatures(): # not work more
        #     targetLayer.dataProvider().addFeatures([feat]) # not work more

        # ИЗ МОДУЛЯ Apend Features To layer -----------------------------------------------
        # В старом варианте в QGIS3 при добавлении объектов с отличающимся набором аттрибутов
        # происходила задержка с выводом сообщений в логи. Что затягивало процесс.
        mapping = dict()
        for target_idx in targetLayer.fields().allAttributesList():
            target_field = targetLayer.fields().field(target_idx)
            source_idx = curlayer.fields().indexOf(target_field.name())
            if source_idx != -1:
                mapping[target_idx] = source_idx

        features = curlayer.selectedFeatures()
        destType = targetLayer.geometryType()
        destIsMulti = QgsWkbTypes.isMultiType(targetLayer.wkbType())
        new_features = []

        for current, in_feature in enumerate(features):
            attrs = {
                target_idx: in_feature[source_idx]
                for target_idx, source_idx in mapping.items()
            }
            geom = QgsGeometry()
            if in_feature.hasGeometry() and targetLayer.isSpatial():
                # Convert geometry to match destination layer
                # Adapted from QGIS qgisapp.cpp, pasteFromClipboard()
                geom = in_feature.geometry()
                if destType != QgsWkbTypes.UnknownGeometry:
                    newGeometry = geom.convertToType(destType, destIsMulti)
                    if newGeometry.isNull():
                        continue
                    geom = newGeometry
                # Avoid intersection if enabled in digitize settings
                geom.avoidIntersections(
                    QgsProject.instance().avoidIntersectionsLayers())

            new_feature = QgsVectorLayerUtils().createFeature(
                targetLayer, geom, attrs)
            new_features.append(new_feature)

        with edit(targetLayer):
            res = targetLayer.addFeatures(new_features)
        # ИЗ МОДУЛЯ Apend Features To layer -----------------------------------------------end

        root.insertLayer(0, targetLayer)
        self.iface.messageBar().clearWidgets()
        self.iface.setActiveLayer(targetLayer)
        curlayer.selectByIds([])

        if res:
            self.iface.messageBar().pushMessage(
                u"Выполнено",
                u"Склонировано {0}/{1} объектов".format(
                    len(new_features), selectedFeatCount),
                duration=5,
                level=0)