def testUpdatedFields(self): """Test when referenced layer update its fields https://issues.qgis.org/issues/20893 """ ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem", "memory") self.assertEqual(ml.isValid(), True) QgsProject.instance().addMapLayer(ml) ml.startEditing() f1 = QgsFeature(ml.fields()) f1.setGeometry(QgsGeometry.fromWkt('POINT(2 3)')) ml.addFeatures([f1]) ml.commitChanges() vl = QgsVectorLayer("?query=select a, geometry from mem", "vl", "virtual") self.assertEqual(vl.isValid(), True) # add one more field ml.dataProvider().addAttributes([QgsField('newfield', QVariant.Int)]) ml.updateFields() self.assertEqual(ml.featureCount(), vl.featureCount()) self.assertEqual(vl.fields().count(), 1) geometry = next(vl.getFeatures()).geometry() self.assertTrue(geometry) point = geometry.asPoint() self.assertEqual(point.x(), 2) self.assertEqual(point.y(), 3) QgsProject.instance().removeMapLayer(ml)
def testFieldsWithSpecialCharacters(self): ml = QgsVectorLayer("Point?srid=EPSG:4326&field=123:int", "mem_with_nontext_fieldnames", "memory") self.assertEqual(ml.isValid(), True) QgsProject.instance().addMapLayer(ml) ml.startEditing() self.assertTrue(ml.addAttribute(QgsField('abc:123', QVariant.String))) self.assertTrue(ml.addAttribute(QgsField('map', QVariant.String))) # matches QGIS expression function name f1 = QgsFeature(ml.fields()) f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)')) f1.setAttributes([1, 'a', 'b']) f2 = QgsFeature(ml.fields()) f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)')) f2.setAttributes([2, 'c', 'd']) ml.addFeatures([f1, f2]) ml.commitChanges() vl = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames", "vl", "virtual") self.assertEqual(vl.isValid(), True) self.assertEqual(vl.fields().at(0).name(), '123') self.assertEqual(vl.fields().at(1).name(), 'abc:123') self.assertEqual(vl.featureCount(), 2) features = [f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression('"abc:123"=\'c\''))] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [2, 'c', 'd']) features = [f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression('"map"=\'b\''))] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [1, 'a', 'b']) vl2 = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames where \"abc:123\"='c'", "vl", "virtual") self.assertEqual(vl2.isValid(), True) self.assertEqual(vl2.fields().at(0).name(), '123') self.assertEqual(vl2.fields().at(1).name(), 'abc:123') self.assertEqual(vl2.featureCount(), 1) features = [f for f in vl2.getFeatures()] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [2, 'c', 'd']) vl3 = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames where \"map\"='b'", "vl", "virtual") self.assertEqual(vl3.isValid(), True) self.assertEqual(vl3.fields().at(0).name(), '123') self.assertEqual(vl3.fields().at(1).name(), 'abc:123') self.assertEqual(vl3.featureCount(), 1) features = [f for f in vl3.getFeatures()] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [1, 'a', 'b']) QgsProject.instance().removeMapLayer(ml)
def testVectorLayerUtilsUniqueWithProviderDefault(self): vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres") default_clause = "nextval('qgis_test.\"someData_pk_seq\"'::regclass)" self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause) self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4)) vl.startEditing() f = QgsFeature(vl.fields()) f.setAttribute(0, default_clause) self.assertTrue(vl.addFeatures([f])) self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
def dict2lyr(self): new_feats = [] for k in self.ndict: cs = self.ndict[k]['attrs'] for points in self.ndict[k]['points']: distance = points['distance'] elem = QgsFeature() elem.setGeometry(point_geometry(points['X'], points['Y'])) elem.setAttributes(cs + [distance]) new_feats.append(elem) crs = self.layer.crs().authid() vl = QgsVectorLayer('Point?crs={0}'.format(crs), self.outname, 'memory') pr = vl.dataProvider() pr.addAttributes(self.flds) vl.startEditing() vl.addFeatures(new_feats) vl.updateExtents() vl.commitChanges() vl.removeSelection() return vl
def f(): crs = iface.mapCanvas().mapRenderer().destinationCrs() uri = "%s?srsname=%s&typename=geonode:%s&version=1.0.0&request=GetFeature&service=WFS" % (url, crs.authid(), name) qgslayer = QgsVectorLayer(uri, name, "WFS") if not qgslayer.isValid(): raise Exception ("Layer at %s is not a valid layer" % uri) fieldname = self._getTimeField(qgslayer) if fieldname is None: QgsMapLayerRegistry.instance().addMapLayers([qgslayer]) else: memlayer = QgsVectorLayer("%s?crs=%s" % (GEOM_TYPE_MAP[qgslayer.wkbType()], crs.authid()), name, "memory") memlayer.startEditing() for field in qgslayer.pendingFields(): memlayer.addAttribute(field) for feat in qgslayer.getFeatures(): memlayer.addFeatures([feat]) memlayer.commitChanges() QgsMapLayerRegistry.instance().addMapLayers([memlayer]) memlayer.setSelectedFeatures([]) addWfsAnimation(memlayer, fieldname)
def testFiltersWithoutUid(self): ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem_no_uid", "memory") self.assertEqual(ml.isValid(), True) QgsProject.instance().addMapLayer(ml) # a memory layer with 10 features ml.startEditing() for i in range(10): f = QgsFeature(ml.fields()) f.setGeometry(QgsGeometry.fromWkt('POINT({} 0)'.format(i))) f.setAttributes([i]) ml.addFeatures([f]) ml.commitChanges() df = QgsVirtualLayerDefinition() df.setQuery('select * from mem_no_uid') vl = QgsVectorLayer(df.toString(), "vl", "virtual") self.assertEqual(vl.isValid(), True) # make sure the returned id with a filter is the same as # if there is no filter req = QgsFeatureRequest().setFilterRect(QgsRectangle(4.5, -1, 5.5, 1)) fids = [f.id() for f in vl.getFeatures(req)] self.assertEqual(fids, [5]) req = QgsFeatureRequest().setFilterExpression("a = 5") fids = [f.id() for f in vl.getFeatures(req)] self.assertEqual(fids, [5]) req = QgsFeatureRequest().setFilterFid(5) a = [(f.id(), f['a']) for f in vl.getFeatures(req)] self.assertEqual(a, [(5, 5)]) req = QgsFeatureRequest().setFilterFids([5, 6, 8]) a = [(f.id(), f['a']) for f in vl.getFeatures(req)] self.assertEqual(a, [(5, 5), (6, 6), (8, 8)]) QgsProject.instance().removeMapLayer(ml)
def test_queryOnMemoryLayer(self): ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem", "memory") self.assertEqual(ml.isValid(), True) QgsProject.instance().addMapLayer(ml) ml.startEditing() f1 = QgsFeature(ml.fields()) f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)')) f2 = QgsFeature(ml.fields()) f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)')) ml.addFeatures([f1, f2]) ml.commitChanges() vl = QgsVectorLayer("?query=select * from mem", "vl", "virtual") self.assertEqual(vl.isValid(), True) self.assertEqual(ml.featureCount(), vl.featureCount()) # test access to pending features as well ml.startEditing() f3 = QgsFeature(ml.fields()) ml.addFeatures([f3]) self.assertEqual(ml.featureCount(), vl.featureCount())
def testVectorLayerUtilsUniqueWithProviderDefault(self): vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres") default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)' vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False) self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause) self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4)) vl.startEditing() f = QgsFeature(vl.fields()) f.setAttribute(0, default_clause) self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause)) self.assertTrue(vl.addFeatures([f])) # the default value clause should exist... self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, default_clause)) # but it should not prevent the attribute being validated self.assertTrue(QgsVectorLayerUtils.validateAttribute(vl, f, 0)) vl.rollBack()
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 add_geojson_features(geojson, project, qgis_layer_id=None, connection_id=None, new_layer_name=None, name=None, style=None): """Add geojson features to a destination layer, the layer can be specified by passing a QgsVectorLayer instance or by specifying a connection and a new layer name plus the QDjango project for the new layer. The connection may assume the special values `__shapefile__`, `__spatialite__` or `__geopackage__` for the creation of new OGR files of the corresponding type. The creation of the new layer may raise an exception is a layer with the same name as new_layer_name already exists. For already existing layers the `qgis_layer_id` can be used, provided that the layer belongs to the current `project`. Returns the qgis_layer_id :param geojson: new features in GeoJSON format :type geojson: str :param project: QDjango Project instance for the new or the existing layer :type project: Project instance :param layer_id: optional, QGIS layer id :type layer_id: QGIS layer id :param connection: optional, connection id or the special value `__shapefile__`, `__spatialite__` or `__geopackage__` :type connection: str :param new_layer_name: optional, name of the new layer :type new_layer_name: str :param name: optional, name of the isochrone, default to current datetime :type name: str :param style: optional, dictionary with style properties: example {'color': [100, 50, 123], 'transparency': 0.5, 'stroke_width: 3 } :type style: dict :raises Exception: raise on error :rtype: str """ # Additional fields that are not returned by the service as feature attributes json_data = json.loads(geojson) if name is None: name = "Isochrone %s" % QDateTime.currentDateTime().toString( Qt.ISODateWithMs) metadata = { 'range_type': json_data['metadata']['query']['range_type'], 'name': name, # 'timestamp': json_data['metadata']['timestamp'], // Not supported } for f in json_data['features']: f['properties'].update(metadata) geojson = json.dumps(json_data) fields = QgsJsonUtils.stringToFields(geojson) # Patch timestamp type to DateTime // Not supported # fields.remove(fields.lookupField('timestamp')) #fields.append(QgsField('timestamp', QVariant.DateTime)) # Create the new layer if connection_id is not None: def _write_to_ogr(destination_path, new_layer_name, driverName=None): """Writes features to new or existing OGR layer""" tmp_dir = QTemporaryDir() tmp_path = os.path.join(tmp_dir.path(), 'isochrone.json') with open(tmp_path, 'w+') as f: f.write(geojson) tmp_layer = QgsVectorLayer(tmp_path, 'tmp_isochrone', 'ogr') if not tmp_layer.isValid(): raise Exception( _('Cannot create temporary layer for isochrone result.')) # Note: shp attribute names are max 10 chars long save_options = QgsVectorFileWriter.SaveVectorOptions() if driverName is not None: save_options.driverName = driverName save_options.layerName = new_layer_name save_options.fileEncoding = 'utf-8' # This is nonsense to me: if the file does not exist the actionOnExistingFile # should be ignored instead of raising an error, probable QGIS bug if os.path.exists(destination_path): # Check if the layer already exists layer_exists = QgsVectorFileWriter.targetLayerExists( destination_path, new_layer_name) if layer_exists: raise Exception( _('Cannot save isochrone result to destination layer: layer already exists (use "qgis_layer_id" instead)!' )) save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer error_code, error_message = QgsVectorFileWriter.writeAsVectorFormatV2( tmp_layer, destination_path, project.qgis_project.transformContext(), save_options) if error_code != QgsVectorFileWriter.NoError: raise Exception( _('Cannot save isochrone result to destination layer: ') + error_message) layer_uri = destination_path if driverName != 'ESRI Shapefile': layer_uri += '|layername=' + new_layer_name provider = 'ogr' return layer_uri, provider destination_path = None if connection_id == '__shapefile__': # new shapefile driverName = 'ESRI Shapefile' extension = 'shp' elif connection_id == '__spatialite__': # new sqlite driverName = 'SpatiaLite' extension = 'sqlite' elif connection_id == '__geopackage__': # new gpkg driverName = 'GPKG' extension = 'gpkg' else: # Add new table to an existing DB connection try: connection = get_db_connections( project.qgis_project)[connection_id] except: raise Exception(_('Wrong connection id.')) if connection['provider'] == 'ogr': destination_path = connection_id driverName = 'GPKG' if destination_path.lower().endswith( '.gpkg') else 'SpatiaLite' else: driverName = None # Create a new file/layer if driverName is not None: new_layer_name = os.path.basename(new_layer_name) if destination_path is None: # new files! destination_path = os.path.join( settings.DATASOURCE_PATH, "{}.{}".format(new_layer_name, extension)) i = 0 while os.path.exists(destination_path): i += 1 destination_path = os.path.join( settings.DATASOURCE_PATH, "{}_{}.{}".format(new_layer_name, i, extension)) layer_uri, provider = _write_to_ogr(destination_path, new_layer_name, driverName) # Create a new DB table else: assert connection['provider'] != 'ogr' md = QgsProviderRegistry.instance().providerMetadata( connection['provider']) if not md: raise Exception( _('Error creating destination layer connection.')) conn = md.createConnection(connection_id, {}) try: conn.createVectorTable(connection['schema'], new_layer_name, fields, QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(4326), False, {'geometryColumn': 'geom'}) except QgsProviderConnectionException as ex: raise Exception( _('Error creating destination layer: ') + str(ex)) uri = QgsDataSourceUri(conn.uri()) uri.setTable(new_layer_name) uri.setSchema(connection['schema']) uri.setSrid('4326') uri.setGeometryColumn('geom') provider = connection['provider'] layer_uri = uri.uri() # Now reload the new layer and add it to the project qgis_layer = QgsVectorLayer(layer_uri, new_layer_name, provider) if not qgis_layer.isValid(): raise Exception( _('Error creating destination layer: layer is not valid!')) qgis_layer_id = qgis_layer.id() with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, True, name) project.qgis_project.addMapLayers([qgis_layer]) root = project.qgis_project.layerTreeRoot() if qgis_layer_id not in root.findLayerIds(): # Append layer at the end root.insertLayer(-1, qgis_layer) if not project.update_qgis_project(): raise Exception( _('Error saving the destination layer: could not write project!' )) # Retrieve the layer again because saving the project deleted it qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) # Create Layer object instance, created = Layer.objects.get_or_create( qgs_layer_id=qgis_layer_id, project=project, defaults={ 'origname': new_layer_name, 'name': new_layer_name, 'title': new_layer_name, 'is_visible': True, 'layer_type': provider, 'srid': 4326, 'datasource': layer_uri, 'geometrytype': 'Polygon', # TODO: make this a property of the Layer object 'database_columns': str([{ 'name': f.name(), 'type': QVariant.typeToName(f.type()).upper(), 'label': f.displayName() } for f in qgis_layer.fields()]), }) if not created: raise Exception( _('Error adding destination Layer to the project: layer already exists.' )) # for OGR (already filled with features) returns the id of the new layer if driverName is not None: return qgis_layer_id # Append to an existing layer qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) if qgis_layer is None: raise Exception( _('Error opening destination layer %s: layer not found in QGIS project!' % qgis_layer_id)) features = QgsJsonUtils.stringToFeatureList(geojson, fields) compatible_features = [] for f in features: compatible_features.extend( QgsVectorLayerUtils.makeFeatureCompatible(f, qgis_layer)) if qgis_layer.crs().isValid( ) and qgis_layer.crs() != QgsCoordinateReferenceSystem(4326): ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem(4326), qgis_layer.crs(), project.qgis_project) for f in compatible_features: geom = f.geometry() if geom.transform(ct) != QgsGeometry.Success: raise Exception( _('Error transforming geometry from 4326 to destination layer CRS.' )) f.setGeometry(geom) if len(compatible_features) == 0: raise Exception(_('No compatible features returned from isochrone.')) if not qgis_layer.startEditing(): raise Exception(_('Destination layer is not editable.')) # Add features to the layer if not qgis_layer.addFeatures(compatible_features): raise Exception(_('Error adding features to the destination layer.')) if not qgis_layer.commitChanges(): raise Exception( _('Error committing features to the destination layer.')) if style is not None: with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, False, name) project.update_qgis_project() return qgis_layer_id
def testTypeValidation(self): """Test that incompatible types in attributes raise errors""" vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertTrue(vl.isValid()) invalid = QgsFeature(vl.fields()) invalid.setAttribute('int', 'A string') invalid.setGeometry(QgsGeometry.fromWkt('point(9 45)')) self.assertTrue(vl.startEditing()) # Validation happens on commit self.assertTrue(vl.addFeatures([invalid])) self.assertFalse(vl.commitChanges()) self.assertTrue(vl.rollBack()) self.assertFalse(vl.hasFeatures()) # Add a valid feature valid = QgsFeature(vl.fields()) valid.setAttribute('int', 123) self.assertTrue(vl.startEditing()) self.assertTrue(vl.addFeatures([valid])) self.assertTrue(vl.commitChanges()) self.assertEqual(vl.featureCount(), 1) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 123) # Add both vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertEqual(vl.featureCount(), 0) self.assertTrue(vl.startEditing()) self.assertTrue(vl.addFeatures([valid, invalid])) self.assertFalse(vl.commitChanges()) self.assertEqual(vl.featureCount(), 2) self.assertTrue(vl.rollBack()) self.assertEqual(vl.featureCount(), 0) # Add both swapped vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertTrue(vl.startEditing()) self.assertTrue(vl.addFeatures([invalid, valid])) self.assertFalse(vl.commitChanges()) self.assertEqual(vl.featureCount(), 2) self.assertTrue(vl.rollBack()) self.assertEqual(vl.featureCount(), 0) # Change attribute value vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertTrue(vl.startEditing()) self.assertTrue(vl.addFeatures([valid])) self.assertTrue(vl.commitChanges()) self.assertTrue(vl.startEditing()) self.assertTrue(vl.changeAttributeValue(1, 0, 'A string')) self.assertFalse(vl.commitChanges()) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 'A string') self.assertTrue(vl.rollBack()) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 123) # Change attribute values vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertTrue(vl.startEditing()) self.assertTrue(vl.addFeatures([valid])) self.assertTrue(vl.commitChanges()) self.assertTrue(vl.startEditing()) self.assertTrue(vl.changeAttributeValues(1, {0: 'A string'})) self.assertFalse(vl.commitChanges()) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 'A string') self.assertTrue(vl.rollBack()) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 123) ############################################## # Test direct data provider calls # No rollback (old behavior) vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') dp = vl.dataProvider() self.assertFalse(dp.addFeatures([valid, invalid])[0]) self.assertEqual([f.attributes() for f in dp.getFeatures()], [[123]]) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 123) # Roll back vl = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer', 'test', 'memory') dp = vl.dataProvider() self.assertFalse(dp.addFeatures([valid, invalid], QgsFeatureSink.RollBackOnErrors)[0]) self.assertFalse(dp.hasFeatures()) # Expected behavior for changeAttributeValues is to always roll back self.assertTrue(dp.addFeatures([valid])[0]) self.assertFalse(dp.changeAttributeValues({1: {0: 'A string'}})) f = vl.getFeature(1) self.assertEqual(f.attribute('int'), 123)
def doGeocode(self): if self.dockwidget.apiKeyEdit.text() == '': self.iface.messageBar().pushMessage("Erro", u"API key não definida!", level=Qgis.Critical, duration=5) return progressDialog = QProgressDialog() progressDialog.setMinimum(0) progressDialog.setCancelButtonText('Cancelar') progressDialog.setWindowModality(Qt.WindowModal) progressDialog.setMinimumWidth(300) progressDialog.show() inputFile = self.dockwidget.inputFileWidget.filePath() errorFile = self.dockwidget.errorOutputFileWidget.filePath() addrPartsIdxs = [] inputAddrList = [] if self.dockwidget.toolBox.currentIndex() == 0: addrPartsIdxs.append(self.dockwidget.addressCombo.currentIndex()) if self.dockwidget.neighborhoodCombo.currentIndex( ) != self.dockwidget.neighborhoodCombo.count() - 1: addrPartsIdxs.append( self.dockwidget.neighborhoodCombo.currentIndex()) if self.dockwidget.cityCombo.currentIndex( ) != self.dockwidget.cityCombo.count() - 1: addrPartsIdxs.append(self.dockwidget.cityCombo.currentIndex()) if self.dockwidget.stateCombo.currentIndex( ) != self.dockwidget.stateCombo.count() - 1: addrPartsIdxs.append(self.dockwidget.stateCombo.currentIndex()) if self.dockwidget.countryCombo.currentIndex( ) != self.dockwidget.countryCombo.count() - 1: addrPartsIdxs.append( self.dockwidget.countryCombo.currentIndex()) else: addrPartsIdxs.append( self.dockwidget.fullAddressCombo.currentIndex()) with open(inputFile, 'r') as iFile: csv_reader = csv.reader(iFile, delimiter=';') lineCount = 0 for row in csv_reader: if lineCount == 0: lineCount += 1 else: addr = ', '.join(row[i] for i in addrPartsIdxs) addr = '"' + addr + '"' inputAddrList.append(addr) # geolocator = Bing(api_key=apiKey) geolocator = self.getGeocoder() temp = QgsVectorLayer("Point?crs=epsg:4326", "Geocoding output", "memory") temp_data = temp.dataProvider() temp.startEditing() fl = open(inputFile, 'r') fo = open(errorFile, 'w') header = fl.readline() header = header.replace('\n', '') header = header.split(';') temp_data.addAttributes( [QgsField(header[i], QVariant.String) for i in range(len(header))]) temp_data.addAttributes([QgsField('addr_geocoded', QVariant.String)]) temp.updateFields() fo.writelines(';'.join(header)) totalAddresses = len(inputAddrList) progressDialog.setWindowTitle(u'Geocodificando ' + str(totalAddresses) + u' endereços...') progressDialog.setMaximum(totalAddresses) counter = 2 for a in inputAddrList: if progressDialog.wasCanceled(): self.iface.messageBar().clearWidgets() self.iface.messageBar().pushMessage( u"Geocodificação cancelada", u'Salvos ' + str(counter - 2) + u' endereços.', level=Qgis.Info, duration=5) break address, (latitude, longitude) = geolocator.reverse(a) print(counter, address, latitude, longitude) counter += 1 feat = QgsFeature() feat.setGeometry( QgsGeometry.fromPointXY(QgsPointXY(longitude, latitude))) attributes = fl.readline().replace('\n', '').split(';') attributes.append(address) feat.setAttributes(attributes) temp.addFeatures([feat]) temp.updateExtents() # progress.setValue(counter-2) progressDialog.setValue(counter - 2) self.iface.mainWindow().repaint() temp.commitChanges() QgsProject.instance().addMapLayer(temp) self.iface.messageBar().clearWidgets() self.iface.messageBar().pushMessage( u"Successo", u'Geocodificados ' + str(totalAddresses) + u' endereços!', level=Qgis.Success, duration=5)
def copyLayerToMemory(layer, layer_name, bOnlySelectedFeat=False, bAddUFI=True): """ Make a copy of an existing layer as a Memory layer Args: layer (): The layer to copy to memory layer_name (): The name for the memory layer bOnlySelectedFeat (): Only copy selected features bAddUFI (): Add a unique identifier to the memory layer Returns: Qgis Memory layer """ # Create an in memory layer and add UFI mem_layer = QgsVectorLayer("{}?crs={}&index=yes".format(getGeometryTypeAsString(layer.wkbType()), layer.crs().authid()), layer_name, "memory") if not mem_layer.isValid(): raise Exception('Could not create memory Layer called {}' ' from layer {}'.format(layer_name, layer.name())) mem_data_prov = mem_layer.dataProvider() # Add the fields b_calc_ufi = False attr = [] if layer.fields().lookupField("FID") == -1 and bAddUFI: b_calc_ufi = True attr = [QgsField('FID', QVariant.Int)] invalid_fields = [] for eaFld in layer.dataProvider().fields(): old_name = eaFld.name() new_name = re.sub('[^A-Za-z0-9_-]+', '', old_name)[:10] if old_name != new_name: invalid_fields.append(' ' + old_name + ' to ' + new_name) eaFld.setName(new_name) # update the name attr.append(eaFld) if len(invalid_fields) > 0: LOGGER.warning('{} fieldnames are not ESRI Compatible. Renaming...'.format(len(invalid_fields))) for i, ea in enumerate(invalid_fields): LOGGER.warning(ea) # Copy all existing fields mem_data_prov.addAttributes(attr) # tell the vector layer to fetch changes from the provider mem_layer.updateFields() # start editing and copy all features and attributes mem_layer.startEditing() sel_feat_ids = [] if bOnlySelectedFeat and layer.selectedFeatureCount() > 0: # Get a list of selected features..... sel_feat_ids = [f.id() for f in layer.selectedFeatures()] features = layer.getFeatures() feat = QgsFeature() id = 0 for f in features: # Check the list of features against the selected list. # This will maintain the order of features when saved to shapefile. if bOnlySelectedFeat and len(sel_feat_ids) > 0: # Skip features which aren't in the selection if not f.id() in sel_feat_ids: continue feat.setGeometry(f.geometry()) f_attr = [] if b_calc_ufi: f_attr += [id] # The order of fields hasn't changed so just add attributes. f_attr += f.attributes() feat.setAttributes(f_attr) mem_layer.addFeatures([feat]) id += 1 mem_layer.updateExtents() mem_layer.commitChanges() return mem_layer
def eliminate(self, inLayer, boundary, progressBar, outFileName): # keep references to the features to eliminate fidsToEliminate = inLayer.selectedFeaturesIds() if outFileName: # user wants a new shape file to be created as result provider = inLayer.dataProvider() error = QgsVectorFileWriter.writeAsVectorFormat(inLayer, outFileName, provider.encoding(), inLayer.crs(), "ESRI Shapefile") if error != QgsVectorFileWriter.NoError: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Error creating output file")) return None outLayer = QgsVectorLayer(outFileName, QFileInfo(outFileName).completeBaseName(), "ogr") else: QMessageBox.information(self, self.tr("Eliminate"), self.tr("Please specify output shapefile")) return None # delete features to be eliminated in outLayer outLayer.setSelectedFeatures(fidsToEliminate) outLayer.startEditing() if outLayer.deleteSelectedFeatures(): if self.saveChanges(outLayer): outLayer.startEditing() else: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not delete features")) return None # ANALYZE start = 20.00 progressBar.setValue(start) add = 80.00 / len(fidsToEliminate) lastLen = 0 # we go through the list and see if we find any polygons we can merge the selected with # if we have no success with some we merge and then restart the whole story while (lastLen != inLayer.selectedFeatureCount()): # check if we made any progress lastLen = inLayer.selectedFeatureCount() fidsToDeselect = [] #iterate over the polygons to eliminate for fid2Eliminate in inLayer.selectedFeaturesIds(): feat = QgsFeature() if inLayer.getFeatures( QgsFeatureRequest().setFilterFid( fid2Eliminate ).setSubsetOfAttributes([]) ).nextFeature( feat ): geom2Eliminate = feat.geometry() bbox = geom2Eliminate.boundingBox() fit = outLayer.getFeatures( QgsFeatureRequest().setFilterRect( bbox ) ) mergeWithFid = None mergeWithGeom = None max = 0 selFeat = QgsFeature() while fit.nextFeature(selFeat): selGeom = selFeat.geometry() if geom2Eliminate.intersects(selGeom): # we have a candidate iGeom = geom2Eliminate.intersection(selGeom) if boundary: selValue = iGeom.length() else: # we need a common boundary if 0 < iGeom.length(): selValue = selGeom.area() else: selValue = 0 if selValue > max: max = selValue mergeWithFid = selFeat.id() mergeWithGeom = QgsGeometry(selGeom) # deep copy of the geometry if mergeWithFid is not None: # a successful candidate newGeom = mergeWithGeom.combine(geom2Eliminate) if outLayer.changeGeometry(mergeWithFid, newGeom): # write change back to disc if self.saveChanges(outLayer): outLayer.startEditing() else: return None # mark feature as eliminated in inLayer fidsToDeselect.append(fid2Eliminate) else: QMessageBox.warning( self, self.tr("Eliminate"), self.tr("Could not replace geometry of feature with id %s") % (mergeWithFid)) return None start = start + add progressBar.setValue(start) # end for fid2Eliminate # deselect features that are already eliminated in inLayer inLayer.deselect(fidsToDeselect) #end while if inLayer.selectedFeatureCount() > 0: # copy all features that could not be eliminated to outLayer if outLayer.addFeatures(inLayer.selectedFeatures()): # inform user fidList = "" for fid in inLayer.selectedFeaturesIds(): if not fidList == "": fidList += ", " fidList += unicode(fid) QMessageBox.information( self, self.tr("Eliminate"), self.tr("Could not eliminate features with these ids:\n%s") % (fidList)) else: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not add features")) # stop editing outLayer and commit any pending changes if not self.saveChanges(outLayer): return None if outFileName: if self.addToCanvasCheck.isChecked(): ftools_utils.addShapeToCanvas(outFileName) else: QMessageBox.information( self, self.tr("Eliminate"), self.tr("Created output shapefile:\n%s") % (outFileName)) self.iface.mapCanvas().refresh()
def geojson_to_memory(path, name): my_WkbType = { 'Unknown': 0, 'Point': 1, 'LineString': 2, 'Polygon': 3, 'MultiPoint': 4, 'MultiLineString': 5, 'MultiPolygon': 6, 'NoGeometry': 7, 'Point25D': 8, 'LineString25D': 9, 'Polygon25D': 10, 'MultiPoint25D': 11, 'MultiLineString25D': 12, 'MultiPolygon25D': 13} # for i,j in my_WkbType.items(): # #print i + " : " + str(j) my_rev_WkbType = {v: k for k, v in my_WkbType.items()} # for i,j in my_WkbType.items(): # #print i + " : " + str(j) #print 'Path to geojson: %s' % path input_layer=QgsVectorLayer(path, "input_layer", "ogr") print 'path: %s' % path print 'input_layer %s' % input_layer.name() dp = input_layer.dataProvider() QGisWKBType = dp.geometryType() #print 'QGisWKBType: ' + str(QGisWKBType) EPSG_code = int(dp.crs().authid().split(":")[1]) print str(EPSG_code) destination_layer = QgsVectorLayer( my_rev_WkbType[QGisWKBType] + '?crs=epsg:' + str(EPSG_code) + '&index=yes', name, 'memory') destination_layer_data_provider = destination_layer.dataProvider() input_layer_attrib_names = input_layer.dataProvider().fields() oldattributeList = input_layer.dataProvider().fields().toList() newattributeList = [] for attrib in oldattributeList: if destination_layer.fieldNameIndex(attrib.name()) == -1: newattributeList.append(QgsField(attrib.name(), attrib.type())) destination_layer_data_provider.addAttributes(newattributeList) destination_layer.updateFields() destination_layer.startEditing() # cfeature = QgsFeature() cfeatures = [] xfeatures = input_layer.getFeatures() for xfeature in xfeatures: xgeometry = xfeature.geometry() cfeature_Attributes = [] cfeature_Attributes.extend(xfeature.attributes()) cfeature = QgsFeature() cfeature.setGeometry(xgeometry) cfeature.setAttributes(cfeature_Attributes) cfeatures.append(cfeature) destination_layer.addFeatures(cfeatures) destination_layer.commitChanges() return destination_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 testWriteShapefileWithSingleConversion(self): """Check writing geometries from a POLYGON ESRI shapefile does not convert to multi when "forceSinglePartGeometryType" options is TRUE also checks failing cases. OGR provider always report MULTI for POLYGON and LINESTRING, but if we set the import option "forceSinglePartGeometryType" the writer must respect the actual single-part type if the features in the data provider are actually single and not multi. """ ml = QgsVectorLayer(('Polygon?crs=epsg:4326&field=id:int'), 'test', 'memory') provider = ml.dataProvider() ft = QgsFeature() ft.setGeometry( QgsGeometry.fromWkt('Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))')) ft.setAttributes([1]) res, features = provider.addFeatures([ft]) dest_file_name = os.path.join(self.basetestpath, 'multipart.shp') write_result, error_message = QgsVectorLayerExporter.exportLayer( ml, dest_file_name, 'ogr', ml.crs(), False, {"driverName": "ESRI Shapefile"}) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Open the newly created layer shapefile_layer = QgsVectorLayer(dest_file_name) dest_singlepart_file_name = os.path.join(self.basetestpath, 'singlepart.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_singlepart_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": True, "driverName": "GPKG", }) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Load result layer and check that it's NOT MULTI single_layer = QgsVectorLayer(dest_singlepart_file_name) self.assertTrue(single_layer.isValid()) self.assertTrue(QgsWkbTypes.isSingleType(single_layer.wkbType())) # Now save the shapfile layer into a gpkg with no force options dest_multipart_file_name = os.path.join(self.basetestpath, 'multipart.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_multipart_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": False, "driverName": "GPKG", }) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Load result layer and check that it's MULTI multi_layer = QgsVectorLayer(dest_multipart_file_name) self.assertTrue(multi_layer.isValid()) self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType())) # Failing case: add a real multi to the shapefile and try to force to single self.assertTrue(shapefile_layer.startEditing()) ft = QgsFeature() ft.setGeometry( QgsGeometry.fromWkt( 'MultiPolygon (((0 0, 0 1, 1 1, 1 0, 0 0)), ((-10 -10,-10 -9,-9 -9,-10 -10)))' )) ft.setAttributes([2]) self.assertTrue(shapefile_layer.addFeatures([ft])) self.assertTrue(shapefile_layer.commitChanges()) dest_multipart_failure_file_name = os.path.join( self.basetestpath, 'multipart_failure.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_multipart_failure_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": True, "driverName": "GPKG", }) self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType())) self.assertEqual( write_result, QgsVectorLayerExporter.ErrFeatureWriteFailed, "Failed to transform a feature with ID '1' to single part. Writing stopped." )
class TestLayerDependencies(unittest.TestCase): @classmethod def setUpClass(cls): """Run before all tests""" # create a temp spatialite db with a trigger fo = tempfile.NamedTemporaryFile() fn = fo.name fo.close() cls.fn = fn con = spatialite_connect(fn) cur = con.cursor() cur.execute("SELECT InitSpatialMetadata(1)") cur.execute("create table node(id integer primary key autoincrement);") cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');") cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);") cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');") cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;") cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));") cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));") cur.execute("create table node2(id integer primary key autoincrement);") cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');") cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;") con.commit() con.close() cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite") assert (cls.pointsLayer.isValid()) cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite") assert (cls.linesLayer.isValid()) cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite") assert (cls.pointsLayer2.isValid()) QgsMapLayerRegistry.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2]) # save the project file fo = tempfile.NamedTemporaryFile() fn = fo.name fo.close() cls.projectFile = fn QgsProject.instance().setFileName(cls.projectFile) QgsProject.instance().write() @classmethod def tearDownClass(cls): """Run after all tests""" pass def setUp(self): """Run before each test.""" pass def tearDown(self): """Run after each test.""" pass def test_resetSnappingIndex(self): self.pointsLayer.setDependencies([]) self.linesLayer.setDependencies([]) self.pointsLayer2.setDependencies([]) ms = QgsMapSettings() ms.setOutputSize(QSize(100, 100)) ms.setExtent(QgsRectangle(0, 0, 1, 1)) self.assertTrue(ms.hasValidSettings()) u = QgsSnappingUtils() u.setMapSettings(ms) cfg = u.config() cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) cfg.setIndividualLayerSettings(self.pointsLayer, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) m = u.snapToMap(QPoint(95, 100)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPoint(1, 0)) f = QgsFeature(self.linesLayer.fields()) f.setFeatureId(1) geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() l1 = len([f for f in self.pointsLayer.getFeatures()]) self.assertEqual(l1, 4) m = u.snapToMap(QPoint(95, 0)) # snapping not updated self.pointsLayer.setDependencies([]) self.assertEqual(m.isValid(), False) # set layer dependencies self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) # add another line f = QgsFeature(self.linesLayer.fields()) f.setFeatureId(2) geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the snapped point is ok m = u.snapToMap(QPoint(45, 50)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPoint(0.5, 0.5)) self.pointsLayer.setDependencies([]) # test chained layer dependencies A -> B -> C cfg.setIndividualLayerSettings(self.pointsLayer2, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]) # add another line f = QgsFeature(self.linesLayer.fields()) f.setFeatureId(3) geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the second snapped point is ok m = u.snapToMap(QPoint(75, 100 - 80)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPoint(0.7, 0.8)) self.pointsLayer.setDependencies([]) self.pointsLayer2.setDependencies([]) def test_cycleDetection(self): self.assertTrue(self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])) self.assertFalse(self.linesLayer.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])) self.pointsLayer.setDependencies([]) self.linesLayer.setDependencies([]) def test_layerDefinitionRewriteId(self): tmpfile = os.path.join(tempfile.tempdir, "test.qlr") ltr = QgsProject.instance().layerTreeRoot() self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr]) grp = ltr.addGroup("imported") QgsLayerDefinition.loadLayerDefinition(tmpfile, grp) newPointsLayer = None newLinesLayer = None for l in grp.findLayers(): if l.layerId().startswith('points'): newPointsLayer = l.layer() elif l.layerId().startswith('lines'): newLinesLayer = l.layer() self.assertFalse(newPointsLayer is None) self.assertFalse(newLinesLayer is None) self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()]) self.pointsLayer.setDependencies([]) def test_signalConnection(self): # remove all layers QgsMapLayerRegistry.instance().removeAllMapLayers() # set dependencies and add back layers self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite") assert (self.pointsLayer.isValid()) self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite") assert (self.linesLayer.isValid()) self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite") assert (self.pointsLayer2.isValid()) self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]) # this should update connections between layers QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer]) QgsMapLayerRegistry.instance().addMapLayers([self.linesLayer]) QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer2]) ms = QgsMapSettings() ms.setOutputSize(QSize(100, 100)) ms.setExtent(QgsRectangle(0, 0, 1, 1)) self.assertTrue(ms.hasValidSettings()) u = QgsSnappingUtils() u.setMapSettings(ms) cfg = u.config() cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) cfg.setIndividualLayerSettings(self.pointsLayer, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) cfg.setIndividualLayerSettings(self.pointsLayer2, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) # add another line f = QgsFeature(self.linesLayer.fields()) f.setFeatureId(4) geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the second snapped point is ok m = u.snapToMap(QPoint(75, 100 - 0)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPoint(0.8, 0.0)) self.pointsLayer.setDependencies([]) self.pointsLayer2.setDependencies([])
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 132}) self.assertEqual(f.attributes(), ['test_5', 132, NULL]) """ test creating a feature respecting unique values of postgres provider """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # init connection string dbconn = 'dbname=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: dbconn = os.environ['QGIS_PGTEST_DB'] # create a vector layer pg_layer = QgsVectorLayer('{} table="qgis_test"."authors" sql='.format(dbconn), "authors", "postgres") self.assertTrue(pg_layer.isValid()) # check the default clause default_clause = 'nextval(\'qgis_test.authors_pk_seq\'::regclass)' self.assertEqual(pg_layer.dataProvider().defaultValueClause(0), default_clause) # though default_clause is after the first create not unique (until save), it should fill up all the features with it pg_layer.startEditing() f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) self.assertTrue(QgsVectorLayerUtils.valueExists(pg_layer, 0, default_clause)) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) # if a unique value is passed, use it f = QgsVectorLayerUtils.createFeature(pg_layer, attributes={0: 40, 1: NULL}) self.assertEqual(f.attributes(), [40, NULL]) # and if a default value is configured use it as well pg_layer.setDefaultValueDefinition(0, QgsDefaultValue('11*4')) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [44, NULL]) pg_layer.rollBack()
def test_invalidGeometryFilter(self): layer = QgsVectorLayer( "Polygon?field=x:string", "joinlayer", "memory") # add some features, one has invalid geometry pr = layer.dataProvider() f1 = QgsFeature(1) f1.setAttributes(["a"]) f1.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid f2 = QgsFeature(2) f2.setAttributes(["b"]) f2.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))')) # invalid f3 = QgsFeature(3) f3.setAttributes(["c"]) f3.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid self.assertTrue(pr.addFeatures([f1, f2, f3])) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))] self.assertEqual(res, ['a', 'b', 'c']) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))] self.assertEqual(res, ['a', 'c']) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))] self.assertEqual(res, ['a']) # with callback self.callback_feature_val = None def callback(feature): self.callback_feature_val = feature['x'] res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck( QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(callback))] self.assertEqual(res, ['a']) self.assertEqual(self.callback_feature_val, 'b') # clear callback res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck( QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(None))] self.assertEqual(res, ['a']) # check with filter fids res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))] self.assertEqual(res, ['b']) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))] self.assertEqual(res, []) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))] self.assertEqual(res, []) f4 = QgsFeature(4) f4.setAttributes(["d"]) f4.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))')) # invalid # check with added features layer.startEditing() self.assertTrue(layer.addFeatures([f4])) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))] self.assertEqual(set(res), {'a', 'b', 'c', 'd'}) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))] self.assertEqual(set(res), {'a', 'c'}) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))] self.assertEqual(res, ['a']) # check with features with changed geometry layer.rollBack() layer.startEditing() layer.changeGeometry(2, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid layer.changeGeometry(3, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))# invalid res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))] self.assertEqual(set(res), {'a', 'b', 'c'}) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))] self.assertEqual(set(res), {'a', 'b'}) res = [f['x'] for f in layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))] self.assertEqual(res, ['a', 'b']) layer.rollBack()
def testFieldsWithSpecialCharacters(self): ml = QgsVectorLayer("Point?srid=EPSG:4326&field=123:int", "mem_with_nontext_fieldnames", "memory") self.assertEqual(ml.isValid(), True) QgsProject.instance().addMapLayer(ml) ml.startEditing() self.assertTrue(ml.addAttribute(QgsField('abc:123', QVariant.String))) self.assertTrue(ml.addAttribute(QgsField( 'map', QVariant.String))) # matches QGIS expression function name f1 = QgsFeature(ml.fields()) f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)')) f1.setAttributes([1, 'a', 'b']) f2 = QgsFeature(ml.fields()) f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)')) f2.setAttributes([2, 'c', 'd']) ml.addFeatures([f1, f2]) ml.commitChanges() vl = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames", "vl", "virtual") self.assertEqual(vl.isValid(), True) self.assertEqual(vl.fields().at(0).name(), '123') self.assertEqual(vl.fields().at(1).name(), 'abc:123') self.assertEqual(vl.featureCount(), 2) features = [ f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( '"abc:123"=\'c\'')) ] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [2, 'c', 'd']) features = [ f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( '"map"=\'b\'')) ] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [1, 'a', 'b']) vl2 = QgsVectorLayer( "?query=select * from mem_with_nontext_fieldnames where \"abc:123\"='c'", "vl", "virtual") self.assertEqual(vl2.isValid(), True) self.assertEqual(vl2.fields().at(0).name(), '123') self.assertEqual(vl2.fields().at(1).name(), 'abc:123') self.assertEqual(vl2.featureCount(), 1) features = [f for f in vl2.getFeatures()] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [2, 'c', 'd']) vl3 = QgsVectorLayer( "?query=select * from mem_with_nontext_fieldnames where \"map\"='b'", "vl", "virtual") self.assertEqual(vl3.isValid(), True) self.assertEqual(vl3.fields().at(0).name(), '123') self.assertEqual(vl3.fields().at(1).name(), 'abc:123') self.assertEqual(vl3.featureCount(), 1) features = [f for f in vl3.getFeatures()] self.assertEqual(len(features), 1) self.assertEqual(features[0].attributes(), [1, 'a', 'b']) QgsProject.instance().removeMapLayer(ml)
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 132 }) self.assertEqual(f.attributes(), ['test_5', 132, NULL]) """ test creating a feature respecting unique values of postgres provider """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # init connection string dbconn = 'dbname=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: dbconn = os.environ['QGIS_PGTEST_DB'] # create a vector layer pg_layer = QgsVectorLayer( '{} table="qgis_test"."authors" sql='.format(dbconn), "authors", "postgres") self.assertTrue(pg_layer.isValid()) # check the default clause default_clause = 'nextval(\'qgis_test.authors_pk_seq\'::regclass)' self.assertEqual(pg_layer.dataProvider().defaultValueClause(0), default_clause) # though default_clause is after the first create not unique (until save), it should fill up all the features with it pg_layer.startEditing() f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) self.assertTrue( QgsVectorLayerUtils.valueExists(pg_layer, 0, default_clause)) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) # if a unique value is passed, use it f = QgsVectorLayerUtils.createFeature(pg_layer, attributes={ 0: 40, 1: NULL }) self.assertEqual(f.attributes(), [40, NULL]) # and if a default value is configured use it as well pg_layer.setDefaultValueDefinition(0, QgsDefaultValue('11*4')) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [44, NULL]) pg_layer.rollBack()
class TestLayerDependencies(unittest.TestCase): @classmethod def setUpClass(cls): """Run before all tests""" # create a temp SpatiaLite db with a trigger fo = tempfile.NamedTemporaryFile() fn = fo.name fo.close() cls.fn = fn con = spatialite_connect(fn) cur = con.cursor() cur.execute("SELECT InitSpatialMetadata(1)") cur.execute("create table node(id integer primary key autoincrement);") cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');") cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);") cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');") cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;") cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));") cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));") cur.execute("create table node2(id integer primary key autoincrement);") cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');") cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;") con.commit() con.close() cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite") assert (cls.pointsLayer.isValid()) cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite") assert (cls.linesLayer.isValid()) cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite") assert (cls.pointsLayer2.isValid()) QgsProject.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2]) # save the project file fo = tempfile.NamedTemporaryFile() fn = fo.name fo.close() cls.projectFile = fn QgsProject.instance().setFileName(cls.projectFile) QgsProject.instance().write() @classmethod def tearDownClass(cls): """Run after all tests""" pass def setUp(self): """Run before each test.""" pass def tearDown(self): """Run after each test.""" pass def test_resetSnappingIndex(self): self.pointsLayer.setDependencies([]) self.linesLayer.setDependencies([]) self.pointsLayer2.setDependencies([]) ms = QgsMapSettings() ms.setOutputSize(QSize(100, 100)) ms.setExtent(QgsRectangle(0, 0, 1, 1)) self.assertTrue(ms.hasValidSettings()) u = QgsSnappingUtils() u.setMapSettings(ms) cfg = u.config() cfg.setEnabled(True) cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) cfg.setIndividualLayerSettings(self.pointsLayer, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) m = u.snapToMap(QPoint(95, 100)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPointXY(1, 0)) f = QgsFeature(self.linesLayer.fields()) f.setId(1) geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() l1 = len([f for f in self.pointsLayer.getFeatures()]) self.assertEqual(l1, 4) m = u.snapToMap(QPoint(95, 0)) # snapping not updated self.pointsLayer.setDependencies([]) self.assertEqual(m.isValid(), False) # set layer dependencies self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) # add another line f = QgsFeature(self.linesLayer.fields()) f.setId(2) geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the snapped point is OK m = u.snapToMap(QPoint(45, 50)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPointXY(0.5, 0.5)) self.pointsLayer.setDependencies([]) # test chained layer dependencies A -> B -> C cfg.setIndividualLayerSettings(self.pointsLayer2, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]) # add another line f = QgsFeature(self.linesLayer.fields()) f.setId(3) geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the second snapped point is OK m = u.snapToMap(QPoint(75, 100 - 80)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPointXY(0.7, 0.8)) self.pointsLayer.setDependencies([]) self.pointsLayer2.setDependencies([]) def test_cycleDetection(self): self.assertTrue(self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])) self.assertFalse(self.linesLayer.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])) self.pointsLayer.setDependencies([]) self.linesLayer.setDependencies([]) def test_layerDefinitionRewriteId(self): tmpfile = os.path.join(tempfile.tempdir, "test.qlr") ltr = QgsProject.instance().layerTreeRoot() self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr]) grp = ltr.addGroup("imported") QgsLayerDefinition.loadLayerDefinition(tmpfile, QgsProject.instance(), grp) newPointsLayer = None newLinesLayer = None for l in grp.findLayers(): if l.layerId().startswith('points'): newPointsLayer = l.layer() elif l.layerId().startswith('lines'): newLinesLayer = l.layer() self.assertIsNotNone(newPointsLayer) self.assertIsNotNone(newLinesLayer) self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()]) self.pointsLayer.setDependencies([]) def test_signalConnection(self): # remove all layers QgsProject.instance().removeAllMapLayers() # set dependencies and add back layers self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite") assert (self.pointsLayer.isValid()) self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite") assert (self.linesLayer.isValid()) self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite") assert (self.pointsLayer2.isValid()) self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]) # this should update connections between layers QgsProject.instance().addMapLayers([self.pointsLayer]) QgsProject.instance().addMapLayers([self.linesLayer]) QgsProject.instance().addMapLayers([self.pointsLayer2]) ms = QgsMapSettings() ms.setOutputSize(QSize(100, 100)) ms.setExtent(QgsRectangle(0, 0, 1, 1)) self.assertTrue(ms.hasValidSettings()) u = QgsSnappingUtils() u.setMapSettings(ms) cfg = u.config() cfg.setEnabled(True) cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) cfg.setIndividualLayerSettings(self.pointsLayer, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) cfg.setIndividualLayerSettings(self.pointsLayer2, QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) u.setConfig(cfg) # add another line f = QgsFeature(self.linesLayer.fields()) f.setId(4) geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)") f.setGeometry(geom) self.linesLayer.startEditing() self.linesLayer.addFeatures([f]) self.linesLayer.commitChanges() # check the second snapped point is OK m = u.snapToMap(QPoint(75, 100 - 0)) self.assertTrue(m.isValid()) self.assertTrue(m.hasVertex()) self.assertEqual(m.point(), QgsPointXY(0.8, 0.0)) self.pointsLayer.setDependencies([]) self.pointsLayer2.setDependencies([])
def eliminate(self, inLayer, boundary, progressBar, outFileName): # keep references to the features to eliminate fidsToEliminate = inLayer.selectedFeaturesIds() if outFileName: # user wants a new shape file to be created as result provider = inLayer.dataProvider() error = QgsVectorFileWriter.writeAsVectorFormat( inLayer, outFileName, provider.encoding(), inLayer.crs(), "ESRI Shapefile") if error != QgsVectorFileWriter.NoError: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Error creating output file")) return None outLayer = QgsVectorLayer( outFileName, QFileInfo(outFileName).completeBaseName(), "ogr") else: QMessageBox.information(self, self.tr("Eliminate"), self.tr("Please specify output shapefile")) return None # delete features to be eliminated in outLayer outLayer.setSelectedFeatures(fidsToEliminate) outLayer.startEditing() if outLayer.deleteSelectedFeatures(): if self.saveChanges(outLayer): outLayer.startEditing() else: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not delete features")) return None # ANALYZE start = 20.00 progressBar.setValue(start) add = 80.00 / len(fidsToEliminate) lastLen = 0 # we go through the list and see if we find any polygons we can merge the selected with # if we have no success with some we merge and then restart the whole story while (lastLen != inLayer.selectedFeatureCount() ): # check if we made any progress lastLen = inLayer.selectedFeatureCount() fidsToDeselect = [] #iterate over the polygons to eliminate for fid2Eliminate in inLayer.selectedFeaturesIds(): feat = QgsFeature() if inLayer.getFeatures(QgsFeatureRequest().setFilterFid( fid2Eliminate).setSubsetOfAttributes( [])).nextFeature(feat): geom2Eliminate = feat.geometry() bbox = geom2Eliminate.boundingBox() fit = outLayer.getFeatures( QgsFeatureRequest().setFilterRect(bbox)) mergeWithFid = None mergeWithGeom = None max = 0 selFeat = QgsFeature() while fit.nextFeature(selFeat): selGeom = selFeat.geometry() if geom2Eliminate.intersects( selGeom): # we have a candidate iGeom = geom2Eliminate.intersection(selGeom) if boundary: selValue = iGeom.length() else: # we need a common boundary if 0 < iGeom.length(): selValue = selGeom.area() else: selValue = 0 if selValue > max: max = selValue mergeWithFid = selFeat.id() mergeWithGeom = QgsGeometry( selGeom) # deep copy of the geometry if mergeWithFid is not None: # a successful candidate newGeom = mergeWithGeom.combine(geom2Eliminate) if outLayer.changeGeometry(mergeWithFid, newGeom): # write change back to disc if self.saveChanges(outLayer): outLayer.startEditing() else: return None # mark feature as eliminated in inLayer fidsToDeselect.append(fid2Eliminate) else: QMessageBox.warning( self, self.tr("Eliminate"), self. tr("Could not replace geometry of feature with id %s" ) % (mergeWithFid)) return None start = start + add progressBar.setValue(start) # end for fid2Eliminate # deselect features that are already eliminated in inLayer inLayer.deselect(fidsToDeselect) #end while if inLayer.selectedFeatureCount() > 0: # copy all features that could not be eliminated to outLayer if outLayer.addFeatures(inLayer.selectedFeatures()): # inform user fidList = "" for fid in inLayer.selectedFeaturesIds(): if not fidList == "": fidList += ", " fidList += unicode(fid) QErrorMessage(self).showMessage( self.tr("Could not eliminate features with these ids:\n%s") % (fidList)) else: QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not add features")) # stop editing outLayer and commit any pending changes if not self.saveChanges(outLayer): return None if outFileName: if self.addToCanvasCheck.isChecked(): ftools_utils.addShapeToCanvas(outFileName) else: QMessageBox.information( self, self.tr("Eliminate"), self.tr("Created output shapefile:\n%s") % (outFileName)) self.iface.mapCanvas().refresh()