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), )
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)
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)
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
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'])
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'])
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)
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
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}
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}
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)
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)
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 )
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
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
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)
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)