def test_check_validity(self): """Test that the output invalid contains the error reason""" polygon_layer = self._make_layer('Polygon') self.assertTrue(polygon_layer.startEditing()) f = QgsFeature(polygon_layer.fields()) f.setAttributes([1]) # Flake! f.setGeometry(QgsGeometry.fromWkt( 'POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))')) self.assertTrue(f.isValid()) f2 = QgsFeature(polygon_layer.fields()) f2.setAttributes([1]) f2.setGeometry(QgsGeometry.fromWkt( 'POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) self.assertTrue(polygon_layer.addFeatures([f, f2])) polygon_layer.commitChanges() polygon_layer.rollBack() self.assertEqual(polygon_layer.featureCount(), 2) QgsProject.instance().addMapLayers([polygon_layer]) alg = self.registry.createAlgorithmById('qgis:checkvalidity') context = QgsProcessingContext() context.setProject(QgsProject.instance()) feedback = ConsoleFeedBack() self.assertIsNotNone(alg) parameters = {} parameters['INPUT_LAYER'] = polygon_layer.id() parameters['VALID_OUTPUT'] = 'memory:' parameters['INVALID_OUTPUT'] = 'memory:' parameters['ERROR_OUTPUT'] = 'memory:' # QGIS method parameters['METHOD'] = 1 ok, results = execute( alg, parameters, context=context, feedback=feedback) self.assertTrue(ok) invalid_layer = QgsProcessingUtils.mapLayerFromString( results['INVALID_OUTPUT'], context) self.assertEqual(invalid_layer.fields().names()[-1], '_errors') self.assertEqual(invalid_layer.featureCount(), 1) f = next(invalid_layer.getFeatures()) self.assertEqual(f.attributes(), [ 1, 'segments 0 and 2 of line 0 intersect at 1, 1']) # GEOS method parameters['METHOD'] = 2 ok, results = execute( alg, parameters, context=context, feedback=feedback) self.assertTrue(ok) invalid_layer = QgsProcessingUtils.mapLayerFromString( results['INVALID_OUTPUT'], context) self.assertEqual(invalid_layer.fields().names()[-1], '_errors') self.assertEqual(invalid_layer.featureCount(), 1) f = next(invalid_layer.getFeatures()) self.assertEqual(f.attributes(), [1, 'Self-intersection'])
def test_fix_geometries(self): polygon_layer = self._make_layer('Polygon') self.assertTrue(polygon_layer.startEditing()) f1 = QgsFeature(polygon_layer.fields()) f1.setAttributes([1]) # Flake! f1.setGeometry(QgsGeometry.fromWkt('POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))')) self.assertTrue(f1.isValid()) f2 = QgsFeature(polygon_layer.fields()) f2.setAttributes([1]) f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) self.assertTrue(polygon_layer.addFeatures([f1, f2])) polygon_layer.commitChanges() polygon_layer.rollBack() self.assertEqual(polygon_layer.featureCount(), 2) QgsProject.instance().addMapLayers([polygon_layer]) old_features, new_features = self._alg_tester( 'native:fixgeometries', polygon_layer, { }, QgsFeatureRequest.GeometrySkipInvalid ) self.assertEqual(polygon_layer.featureCount(), 3) wkt1, wkt2, wkt3 = [f.geometry().asWkt() for f in new_features] self.assertEqual(wkt1, 'Polygon ((0 0, 1 1, 2 0, 0 0))') self.assertEqual(wkt2, 'Polygon ((1 1, 0 2, 2 2, 1 1))') self.assertEqual(re.sub(r'0000\d+', '', wkt3), 'Polygon ((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))') # Test with Z (interpolated) polygonz_layer = self._make_layer('PolygonZ') self.assertTrue(polygonz_layer.startEditing()) f3 = QgsFeature(polygonz_layer.fields()) f3.setAttributes([1]) f3.setGeometry(QgsGeometry.fromWkt('POLYGON Z((0 0 1, 2 2 1, 0 2 3, 2 0 4, 0 0 1))')) self.assertTrue(f3.isValid()) self.assertTrue(polygonz_layer.addFeatures([f3])) polygonz_layer.commitChanges() polygonz_layer.rollBack() self.assertEqual(polygonz_layer.featureCount(), 1) QgsProject.instance().addMapLayers([polygonz_layer]) old_features, new_features = self._alg_tester( 'native:fixgeometries', polygonz_layer, { } ) self.assertEqual(polygonz_layer.featureCount(), 2) wkt1, wkt2 = [f.geometry().asWkt() for f in new_features] self.assertEqual(wkt1, 'PolygonZ ((0 0 1, 1 1 2.25, 2 0 4, 0 0 1))') self.assertEqual(wkt2, 'PolygonZ ((1 1 2.25, 0 2 3, 2 2 1, 1 1 2.25))')
def _test_difference_on_invalid_geometries(self, geom_option): polygon_layer = self._make_layer('Polygon') self.assertTrue(polygon_layer.startEditing()) f = QgsFeature(polygon_layer.fields()) f.setAttributes([1]) # Flake! f.setGeometry(QgsGeometry.fromWkt('Polygon ((0 0, 2 2, 0 2, 2 0, 0 0))')) self.assertTrue(f.isValid()) self.assertTrue(polygon_layer.addFeatures([f])) polygon_layer.commitChanges() polygon_layer.rollBack() self.assertEqual(polygon_layer.featureCount(), 1) overlay_layer = self._make_layer('Polygon') self.assertTrue(overlay_layer.startEditing()) f = QgsFeature(overlay_layer.fields()) f.setAttributes([1]) f.setGeometry(QgsGeometry.fromWkt('Polygon ((0 0, 2 0, 2 2, 0 2, 0 0))')) self.assertTrue(f.isValid()) self.assertTrue(overlay_layer.addFeatures([f])) overlay_layer.commitChanges() overlay_layer.rollBack() self.assertEqual(overlay_layer.featureCount(), 1) QgsProject.instance().addMapLayers([polygon_layer, overlay_layer]) old_features = [f for f in polygon_layer.getFeatures()] # 'Ignore features with invalid geometries' = 1 ProcessingConfig.setSettingValue(ProcessingConfig.FILTER_INVALID_GEOMETRIES, geom_option) feedback = ConsoleFeedBack() context = dataobjects.createContext(feedback) context.setProject(QgsProject.instance()) alg = self.registry.createAlgorithmById('native:difference') self.assertIsNotNone(alg) parameters = { 'OVERLAY': overlay_layer, 'INPUT': polygon_layer, 'OUTPUT': ':memory', } old_features = [f for f in polygon_layer.getFeatures()] self.assertTrue(polygon_layer.startEditing()) polygon_layer.selectAll() ok, _ = execute_in_place_run( alg, parameters, context=context, feedback=feedback, raise_exceptions=True) new_features = [f for f in polygon_layer.getFeatures()] return old_features, new_features
def testGetFeaturesFidsTests(self): fids = [f.id() for f in self.provider.getFeatures()] self.assertEqual(len(fids), 5) request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]]) result = set([f.id() for f in self.provider.getFeatures(request)]) all_valid = all(f.isValid() for f in self.provider.getFeatures(request)) expected = set([fids[0], fids[2]]) assert result == expected, "Expected {} and got {} when testing for feature IDs filter".format(expected, result) self.assertTrue(all_valid) result = set( [f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))] ) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, "Expected {} and got {} when testing for feature IDs filter".format(expected, result) # providers should ignore non-existant fids result = set( [ f.id() for f in self.provider.getFeatures( QgsFeatureRequest().setFilterFids([-101, fids[1], -102, fids[3], -103, fids[4], -104]) ) ] ) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, "Expected {} and got {} when testing for feature IDs filter".format(expected, result) result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([]))]) expected = set([]) assert result == expected, "Expected {} and got {} when testing for feature IDs filter".format(expected, result) # Rewind mid-way request = QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]) feature_it = self.provider.getFeatures(request) feature = QgsFeature() feature.setValid(True) self.assertTrue(feature_it.nextFeature(feature)) self.assertIn(feature.id(), [fids[1], fids[3], fids[4]]) first_feature = feature self.assertTrue(feature.isValid()) # rewind self.assertTrue(feature_it.rewind()) self.assertTrue(feature_it.nextFeature(feature)) self.assertEqual(feature.id(), first_feature.id()) self.assertTrue(feature.isValid()) # grab all features self.assertTrue(feature_it.nextFeature(feature)) self.assertTrue(feature_it.nextFeature(feature)) # none left self.assertFalse(feature_it.nextFeature(feature)) self.assertFalse(feature.isValid())
def testGetFeaturesFidsTests(self): fids = [f.id() for f in self.source.getFeatures()] self.assertEqual(len(fids), 5) # empty list = no features request = QgsFeatureRequest().setFilterFids([]) result = set([f.id() for f in self.source.getFeatures(request)]) self.assertFalse(result) request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]]) result = set([f.id() for f in self.source.getFeatures(request)]) all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) expected = set([fids[0], fids[2]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result) self.assertTrue(all_valid) # test that results match QgsFeatureRequest.acceptFeature for f in self.source.getFeatures(): self.assertEqual(request.acceptFeature(f), f.id() in expected) result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))]) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result) #sources should ignore non-existent fids result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([-101, fids[1], -102, fids[3], -103, fids[4], -104]))]) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result) result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([]))]) expected = set([]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result) # Rewind mid-way request = QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]) feature_it = self.source.getFeatures(request) feature = QgsFeature() feature.setValid(True) self.assertTrue(feature_it.nextFeature(feature)) self.assertIn(feature.id(), [fids[1], fids[3], fids[4]]) first_feature = feature self.assertTrue(feature.isValid()) # rewind self.assertTrue(feature_it.rewind()) self.assertTrue(feature_it.nextFeature(feature)) self.assertEqual(feature.id(), first_feature.id()) self.assertTrue(feature.isValid()) # grab all features self.assertTrue(feature_it.nextFeature(feature)) self.assertTrue(feature_it.nextFeature(feature)) # none left self.assertFalse(feature_it.nextFeature(feature)) self.assertFalse(feature.isValid())
def testClosedIterators(self): """ Test behavior of closed iterators """ # Test retrieving feature after closing iterator f_it = self.source.getFeatures(QgsFeatureRequest()) fet = QgsFeature() assert f_it.nextFeature(fet), 'Could not fetch feature' assert fet.isValid(), 'Feature is not valid' assert f_it.close(), 'Could not close iterator' self.assertFalse(f_it.nextFeature(fet), 'Fetched feature after iterator closed, expected nextFeature() to return False') self.assertFalse(fet.isValid(), 'Valid feature fetched from closed iterator, should be invalid') # Test rewinding closed iterator self.assertFalse(f_it.rewind(), 'Rewinding closed iterator successful, should not be allowed')
def test_snappointstogrid(self): """Check that this runs correctly""" polygon_layer = self._make_layer('Polygon') f1 = QgsFeature(polygon_layer.fields()) f1.setAttributes([1]) f1.setGeometry(QgsGeometry.fromWkt('POLYGON((1.2 1.2, 1.2 2.2, 2.2 2.2, 2.2 1.2, 1.2 1.2))')) f2 = QgsFeature(polygon_layer.fields()) f2.setAttributes([2]) f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) self.assertTrue(polygon_layer.startEditing()) self.assertTrue(polygon_layer.addFeatures([f1, f2])) self.assertEqual(polygon_layer.featureCount(), 2) polygon_layer.commitChanges() self.assertEqual(polygon_layer.featureCount(), 2) QgsProject.instance().addMapLayers([polygon_layer]) polygon_layer.selectByIds([next(polygon_layer.getFeatures()).id()]) self.assertEqual(polygon_layer.selectedFeatureCount(), 1) old_features, new_features = self._alg_tester( 'native:snappointstogrid', polygon_layer, { 'HSPACING': 0.5, 'VSPACING': 0.5, } ) g = [f.geometry() for f in new_features][0] self.assertEqual(g.asWkt(), 'Polygon ((1 1, 1 2, 2 2, 2 1, 1 1))') # Check selected self.assertEqual(polygon_layer.selectedFeatureIds(), [1])
def testClear(self): s = QgsAuxiliaryStorage() self.assertTrue(s.isValid()) # Create a new auxiliary layer with 'pk' as key vl = createLayer() pkf = vl.fields().field(vl.fields().indexOf('pk')) al = s.createAuxiliaryLayer(pkf, vl) self.assertTrue(al.isValid()) vl.setAuxiliaryLayer(al) # Add a field in auxiliary layer p = QgsPropertyDefinition('myprop', QgsPropertyDefinition.DataTypeNumeric, '', '', 'me') self.assertFalse(al.exists(p)) self.assertTrue(al.addAuxiliaryField(p)) self.assertTrue(al.exists(p)) # Count auxiliary features self.assertEqual(al.featureCount(), 0) # Set value for auxiliary fields req = QgsFeatureRequest().setFilterExpression("name = 'Honey'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) afName = QgsAuxiliaryLayer.nameFromProperty(p, True) index = vl.fields().indexOf(afName) vl.changeAttributeValue(f.id(), index, 333) # Count auxiliary features self.assertEqual(al.featureCount(), 1) # Clear and count al.clear() self.assertEqual(al.featureCount(), 0)
def testClosedIterators(self): """ Test behaviour of closed iterators """ # Test retrieving feature after closing iterator f_it = self.provider.getFeatures(QgsFeatureRequest()) fet = QgsFeature() assert f_it.nextFeature(fet), "Could not fetch feature" assert fet.isValid(), "Feature is not valid" assert f_it.close(), "Could not close iterator" self.assertFalse( f_it.nextFeature(fet), "Fetched feature after iterator closed, expected nextFeature() to return False" ) self.assertFalse(fet.isValid(), "Valid feature fetched from closed iterator, should be invalid") # Test rewinding closed iterator self.assertFalse(f_it.rewind(), "Rewinding closed iterator successful, should not be allowed")
def test_clip(self): mask_layer = QgsMemoryProviderUtils.createMemoryLayer( 'mask_layer', self.vl.fields(), QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(4326)) self.assertTrue(mask_layer.isValid()) self.assertTrue(mask_layer.startEditing()) f = QgsFeature(mask_layer.fields()) f.setAttributes([1]) f.setGeometry(QgsGeometry.fromWkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')) self.assertTrue(f.isValid()) f2 = QgsFeature(mask_layer.fields()) f2.setAttributes([1]) f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) self.assertTrue(mask_layer.addFeatures([f, f2])) mask_layer.commitChanges() mask_layer.rollBack() clip_layer = QgsMemoryProviderUtils.createMemoryLayer( 'clip_layer', self.vl.fields(), QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(4326)) self.assertTrue(clip_layer.isValid()) self.assertTrue(clip_layer.startEditing()) f = QgsFeature(clip_layer.fields()) f.setAttributes([1]) f.setGeometry(QgsGeometry.fromWkt('LINESTRING(-1 -1, 3 3)')) self.assertTrue(f.isValid()) self.assertTrue(clip_layer.addFeatures([f])) self.assertEqual(clip_layer.featureCount(), 1) clip_layer.commitChanges() clip_layer.selectAll() clip_layer.rollBack() QgsProject.instance().addMapLayers([clip_layer, mask_layer]) old_features, new_features = self._alg_tester( 'native:clip', clip_layer, { 'OVERLAY': mask_layer.id(), } ) self.assertEqual(len(new_features), 2) self.assertEqual(new_features[0].geometry().asWkt(), 'LineString (0 0, 1 1)') self.assertEqual(new_features[0].attributes(), [1])
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.fromPoint(QgsPoint(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().exportToWkt(), g.exportToWkt()) # 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.setDefaultValueExpression(2, '3*4') f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12.0]) # we 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, 12.0]) # layer with default value expression based on geometry layer.setDefaultValueExpression(2, '3*$x') f = QgsVectorLayerUtils.createFeature(layer, g) self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueExpression(2, None) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_4', 128, NULL])
def test_ValidFeature(self): myPath = os.path.join(unitTestDataPath(), 'points.shp') myLayer = QgsVectorLayer(myPath, 'Points', 'ogr') provider = myLayer.dataProvider() fit = provider.getFeatures() feat = QgsFeature() fit.nextFeature(feat) myValidValue = feat.isValid() myMessage = '\nExpected: %s\nGot: %s' % ("True", myValidValue) assert myValidValue == True, myMessage
def test_ValidFeature(self): myPath = os.path.join(unitTestDataPath(), "points.shp") myLayer = QgsVectorLayer(myPath, "Points", "ogr") provider = myLayer.dataProvider() fit = provider.getFeatures() feat = QgsFeature() fit.nextFeature(feat) fit.close() myValidValue = feat.isValid() myMessage = "\nExpected: %s\nGot: %s" % ("True", myValidValue) assert myValidValue, myMessage
def test_getFeatures(self): layer = createLayerWithOnePoint() f = QgsFeature() fi = layer.getFeatures() assert fi.nextFeature(f) == True assert f.isValid() == True assert f.id() == 1 assert f.geometry().asPoint() == QgsPoint(100,200) assert f["fldtxt"] == "test" assert f["fldint"] == 123 assert fi.nextFeature(f) == False
def test_getFeatures(self): layer = createLayerWithOnePoint() f = QgsFeature() fi = layer.getFeatures() self.assertTrue(fi.nextFeature(f)) self.assertTrue(f.isValid()) self.assertEquals(f.id(), 1) self.assertEquals(f.geometry().asPoint(), QgsPoint(100, 200)) self.assertEquals(f["fldtxt"], "test") self.assertEquals(f["fldint"], 123) self.assertFalse(fi.nextFeature(f))
def testGetFeatures(self): """ Test that expected results are returned when fetching all features """ # IMPORTANT - we do not use `for f in provider.getFeatures()` as we are also # testing that existing attributes & geometry in f are overwritten correctly # (for f in ... uses a new QgsFeature for every iteration) it = self.provider.getFeatures() f = QgsFeature() attributes = {} geometries = {} while it.nextFeature(f): # expect feature to be valid self.assertTrue(f.isValid()) # split off the first 5 attributes only - some provider test datasets will include # additional attributes which we ignore attrs = f.attributes()[0:5] # force the num_char attribute to be text - some providers (eg delimited text) will # automatically detect that this attribute contains numbers and set it as a numeric # field attrs[4] = str(attrs[4]) attributes[f["pk"]] = attrs geometries[f["pk"]] = f.hasGeometry() and f.geometry().exportToWkt() expected_attributes = { 5: [5, -200, NULL, "NuLl", "5"], 3: [3, 300, "Pear", "PEaR", "3"], 1: [1, 100, "Orange", "oranGe", "1"], 2: [2, 200, "Apple", "Apple", "2"], 4: [4, 400, "Honey", "Honey", "4"], } self.assertEqual(attributes, expected_attributes, "Expected {}, got {}".format(expected_attributes, attributes)) expected_geometries = { 1: "Point (-70.332 66.33)", 2: "Point (-68.2 70.8)", 3: None, 4: "Point(-65.32 78.3)", 5: "Point(-71.123 78.23)", } for pk, geom in list(expected_geometries.items()): if geom: assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format( pk, geom, geometries[pk].exportToWkt() ) else: self.assertFalse(geometries[pk], "Expected null geometry for {}".format(pk))
def structure_condition_2_damage_level(code): feature = QgsFeature() layerid = MySettings().value("vl_wastewater_structure_structure_condition") layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is not None: requestText = '"code" = \'{}\''.format(code) request = QgsFeatureRequest().setFilterExpression(requestText) for f in layer.getFeatures(request): feature = QgsFeature(f) # print requestText, feature.isValid() if feature.isValid(): return f['value_en'] else: return None
def damageLevel2vl(code): feature = QgsFeature() layerid = MySettings().value("vl_damage_single_class") layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is not None: requestText = '"value_en" = \'EZ{}\''.format(code) request = QgsFeatureRequest().setFilterExpression(requestText) for f in layer.getFeatures( request ): feature = QgsFeature(f) #print requestText, feature.isValid() if feature.isValid(): return f['code'] else: return None
def testGetFeaturesFidTests(self): fids = [f.id() for f in self.provider.getFeatures()] assert len(fids) == 5, 'Expected 5 features, got {} instead'.format(len(fids)) for id in fids: features = [f for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFid(id))] self.assertEqual(len(features), 1) feature = features[0] self.assertTrue(feature.isValid()) result = [feature.id()] expected = [id] assert result == expected, 'Expected {} and got {} when testing for feature ID filter'.format(expected, result) # bad features it = self.provider.getFeatures(QgsFeatureRequest().setFilterFid(-99999999)) feature = QgsFeature(5) feature.setValid(False) self.assertFalse(it.nextFeature(feature)) self.assertFalse(feature.isValid())
def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]): layer = self._make_layer(layer_wkb_name) layer.startEditing() f = QgsFeature(layer.fields()) f.setAttributes(attrs) f.setGeometry(QgsGeometry.fromWkt(feature_wkt)) self.assertTrue(f.isValid()) context = QgsProcessingContext() context.setProject(QgsProject.instance()) # Fix it! new_features = make_features_compatible([f], layer) for new_f in new_features: self.assertEqual(new_f.geometry().wkbType(), layer.wkbType()) self.assertTrue(layer.addFeatures(new_features), "Fail: %s - %s - %s" % (feature_wkt, attrs, layer_wkb_name)) return layer, new_features
def testOSM(self): """ Test that opening several layers of the same OSM datasource works properly """ datasource = os.path.join(TEST_DATA_DIR, 'test.osm') vl_points = QgsVectorLayer(datasource + "|layername=points", 'test', 'ogr') vl_multipolygons = QgsVectorLayer(datasource + "|layername=multipolygons", 'test', 'ogr') f = QgsFeature() # When sharing the same dataset handle, the spatial filter of test # points layer would apply to the other layers iter_points = vl_points.getFeatures(QgsFeatureRequest().setFilterRect(QgsRectangle(-200, -200, -200, -200))) self.assertFalse(iter_points.nextFeature(f)) iter_multipolygons = vl_multipolygons.getFeatures(QgsFeatureRequest()) self.assertTrue(iter_multipolygons.nextFeature(f)) self.assertTrue(iter_multipolygons.nextFeature(f)) self.assertTrue(iter_multipolygons.nextFeature(f)) self.assertFalse(iter_multipolygons.nextFeature(f)) # Re-start an iterator (tests #20098) iter_multipolygons = vl_multipolygons.getFeatures(QgsFeatureRequest()) self.assertTrue(iter_multipolygons.nextFeature(f)) # Test filter by id (#20308) f = next(vl_multipolygons.getFeatures(QgsFeatureRequest().setFilterFid(8))) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 8) f = next(vl_multipolygons.getFeatures(QgsFeatureRequest().setFilterFid(1))) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 1) f = next(vl_multipolygons.getFeatures(QgsFeatureRequest().setFilterFid(5))) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 5) # 6 doesn't exist it = vl_multipolygons.getFeatures(QgsFeatureRequest().setFilterFids([1, 5, 6, 8])) f = next(it) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 1) f = next(it) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 5) f = next(it) self.assertTrue(f.isValid()) self.assertEqual(f.id(), 8) del it
def test_Validity(self): f = QgsFeature() self.assertFalse(f.isValid()) f.setGeometry(QgsGeometry()) self.assertTrue(f.isValid()) f.setValid(False) self.assertFalse(f.isValid()) fields = QgsFields() field1 = QgsField('my_field') fields.append(field1) field2 = QgsField('my_field2') fields.append(field2) f.setFields(fields) f.setAttribute(0, 0) self.assertTrue(f.isValid()) f.setValid(False) self.assertFalse(f.isValid()) f.setId(27) self.assertTrue(f.isValid())
class FeatureSelectorWidget(QWidget): featureIdentified = pyqtSignal(QgsFeature) def __init__(self, parent): QWidget.__init__(self, parent) editLayout = QHBoxLayout() editLayout.setContentsMargins(0, 0, 0, 0) editLayout.setSpacing(2) self.setLayout(editLayout) self.lineEdit = QLineEdit(self) self.lineEdit.setReadOnly(True) editLayout.addWidget(self.lineEdit) self.highlightFeatureButton = QToolButton(self) self.highlightFeatureButton.setPopupMode(QToolButton.MenuButtonPopup) self.highlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self) self.scaleHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), "Scale and highlight feature", self) self.panHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), "Pan and highlight feature", self) self.highlightFeatureButton.addAction(self.highlightFeatureAction) self.highlightFeatureButton.addAction(self.scaleHighlightFeatureAction) self.highlightFeatureButton.addAction(self.panHighlightFeatureAction) self.highlightFeatureButton.setDefaultAction(self.highlightFeatureAction) editLayout.addWidget(self.highlightFeatureButton) self.mapIdentificationButton = QToolButton(self) self.mapIdentificationButton.setIcon(QgsApplication.getThemeIcon("/mActionMapIdentification.svg")) self.mapIdentificationButton.setText("Select on map") self.mapIdentificationButton.setCheckable(True) editLayout.addWidget(self.mapIdentificationButton) self.mapIdentificationButton.clicked.connect(self.mapIdentification) self.highlightFeatureButton.triggered.connect(self.highlightActionTriggered) self.layer = None self.mapTool = None self.canvas = None self.windowWidget = None self.highlight = None self.feature = QgsFeature() def setCanvas(self, mapCanvas): self.mapTool = QgsMapToolIdentifyFeature(mapCanvas) self.mapTool.setButton(self.mapIdentificationButton) self.canvas = mapCanvas def setLayer(self, layer): self.layer = layer def setFeature(self, feature, canvasExtent = CanvasExtent.Fixed): self.lineEdit.clear() self.feature = feature if not self.feature.isValid() or self.layer is None: return featureTitle = feature.attribute(self.layer.displayField()) if featureTitle == '': featureTitle = feature.id() self.lineEdit.setText(str(featureTitle)) self.highlightFeature(canvasExtent) def clear(self): self.feature = QgsFeature() self.lineEdit.clear() def mapIdentification(self): if self.layer is None or self.mapTool is None or self.canvas is None: return self.mapTool.setLayer(self.layer) self.canvas.setMapTool(self.mapTool) self.windowWidget = QWidget.window(self) self.canvas.window().raise_() self.canvas.activateWindow() self.canvas.setFocus() self.mapTool.featureIdentified.connect(self.mapToolFeatureIdentified) self.mapTool.deactivated.connect(self.mapToolDeactivated) def mapToolFeatureIdentified(self, feature): feature = QgsFeature(feature) self.featureIdentified.emit(feature) self.unsetMapTool() self.setFeature(feature) def mapToolDeactivated(self): if self.windowWidget is not None: self.windowWidget.raise_() self.windowWidget.activateWindow() def highlightFeature(self, canvasExtent = CanvasExtent.Fixed): if self.canvas is None or not self.feature.isValid(): return geom = self.feature.geometry() if geom is None: return if canvasExtent == CanvasExtent.Scale: featBBox = geom.boundingBox() featBBox = self.canvas.mapSettings().layerToMapCoordinates(self.layer, featBBox) extent = self.canvas.extent() if not extent.contains(featBBox): extent.combineExtentWith(featBBox) extent.scale(1.1) self.canvas.setExtent(extent) self.canvas.refresh() elif canvasExtent == CanvasExtent.Pan: centroid = geom.centroid() center = centroid.asPoint() center = self.canvas.mapSettings().layerToMapCoordinates(self.layer, center) self.canvas.zoomByFactor(1.0, center) # refresh is done in this method # highlight self.delete_highlight() self.highlight = QgsHighlight(self.canvas, geom, self.layer) settings = QSettings() color = QColor(settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) buffer = 2*float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) min_width = 2*float(settings.value("/Map/highlight/min_width", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.highlight.setColor(color) # sets also fill with default alpha color.setAlpha(alpha) self.highlight.setFillColor(color) # sets fill with alpha self.highlight.setBuffer(buffer) self.highlight.setMinWidth(min_width) self.highlight.setWidth(4.0) self.highlight.show() self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.delete_highlight) self.timer.start(3000) def delete_highlight(self): if self.highlight is not None: self.highlight.hide() del self.highlight self.highlight = None def unsetMapTool(self): if self.canvas is not None and self.mapTool is not None: # this will call mapToolDeactivated self.canvas.unsetMapTool(self.mapTool) def highlightActionTriggered(self, action): self.highlightFeatureButton.setDefaultAction(action) if action == self.highlightFeatureAction: self.highlightFeature() elif action == self.scaleHighlightFeatureAction: self.highlightFeature(CanvasExtent.Scale) elif action == self.panHighlightFeatureAction: self.highlightFeature(CanvasExtent.Pan)
def testProjectStorage(self): # New project without fileName p0 = QgsProject() self.assertTrue(p0.auxiliaryStorage().isValid()) # Create new layers with key otherwise auxiliary layers are not # automacially created when added in project vl0 = createLayer() vl0Shp = writeShape(vl0, 'vl0.shp') vl1 = createLayer() vl1Shp = writeShape(vl1, 'vl1.shp') vl0 = QgsVectorLayer(vl0Shp, 'points', 'ogr') self.assertTrue(vl0.isValid()) vl1 = QgsVectorLayer(vl1Shp, 'points', 'ogr') self.assertTrue(vl1.isValid()) # Add layers to project and check underlying auxiliary layers p0.addMapLayers([vl0, vl1]) self.assertTrue(vl0.loadAuxiliaryLayer(p0.auxiliaryStorage(), 'pk')) self.assertTrue(vl1.loadAuxiliaryLayer(p0.auxiliaryStorage(), 'num_char')) al0 = vl0.auxiliaryLayer() al1 = vl1.auxiliaryLayer() self.assertEqual(al0.joinInfo().targetFieldName(), 'pk') self.assertEqual(al1.joinInfo().targetFieldName(), 'num_char') # Add a field in auxiliary layers pdef0 = QgsPropertyDefinition('propname', QgsPropertyDefinition.DataTypeNumeric, '', '', 'ut') self.assertTrue(al0.addAuxiliaryField(pdef0)) pdef1 = QgsPropertyDefinition('propname1', QgsPropertyDefinition.DataTypeString, '', '', 'ut') self.assertTrue(al1.addAuxiliaryField(pdef1)) # Check auxiliary fields names af0Name = QgsAuxiliaryLayer.nameFromProperty(pdef0, False) self.assertEqual(af0Name, 'ut_propname') af1Name = QgsAuxiliaryLayer.nameFromProperty(pdef1, False) self.assertEqual(af1Name, 'ut_propname1') # Set value for auxiliary fields req = QgsFeatureRequest().setFilterExpression("name = 'Honey'") f = QgsFeature() vl0.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) af0Name = QgsAuxiliaryLayer.nameFromProperty(pdef0, True) index0 = vl0.fields().indexOf(af0Name) vl0.changeAttributeValue(f.id(), index0, 333) req = QgsFeatureRequest().setFilterExpression("name = 'Apple'") f = QgsFeature() vl1.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) af1Name = QgsAuxiliaryLayer.nameFromProperty(pdef1, True) index1 = vl1.fields().indexOf(af1Name) vl1.changeAttributeValue(f.id(), index0, 'myvalue') req = QgsFeatureRequest().setFilterExpression("name = 'Orange'") f = QgsFeature() vl1.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) vl1.changeAttributeValue(f.id(), index0, 'myvalue1') # Save the project in a zip file f = tmpPath() + '.qgz' p0.write(f) # Open the zip file with embedded auxiliary storage p1 = QgsProject() p1.read(f) # Check that auxiliary fields are well loaded in layers self.assertEqual(len(p1.mapLayers().values()), 2) for vl in p1.mapLayers().values(): al = vl.auxiliaryLayer() self.assertEqual(len(al.auxiliaryFields()), 1) af = al.auxiliaryFields()[0] afPropDef = QgsAuxiliaryLayer.propertyDefinitionFromField(af) self.assertEqual(afPropDef.origin(), 'ut') if vl.auxiliaryLayer().joinInfo().targetFieldName() == 'pk': self.assertEqual(afPropDef.name(), 'propname') self.assertEqual(al.featureCount(), 1) req = QgsFeatureRequest().setFilterExpression("name = 'Honey'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index0], 333.0) else: # num_char self.assertEqual(al.featureCount(), 2) self.assertEqual(afPropDef.name(), 'propname1') req = QgsFeatureRequest().setFilterExpression("name = 'Apple'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index1], 'myvalue') req = QgsFeatureRequest().setFilterExpression("name = 'Orange'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index1], 'myvalue1')
def testGetFeatures(self, source=None, extra_features=[], skip_features=[], changed_attributes={}, changed_geometries={}): """ Test that expected results are returned when fetching all features """ # IMPORTANT - we do not use `for f in source.getFeatures()` as we are also # testing that existing attributes & geometry in f are overwritten correctly # (for f in ... uses a new QgsFeature for every iteration) if not source: source = self.source it = source.getFeatures() f = QgsFeature() attributes = {} geometries = {} while it.nextFeature(f): # expect feature to be valid self.assertTrue(f.isValid()) # some source test datasets will include additional attributes which we ignore, # so cherry pick desired attributes attrs = [f['pk'], f['cnt'], f['name'], f['name2'], f['num_char']] # force the num_char attribute to be text - some sources (e.g., delimited text) will # automatically detect that this attribute contains numbers and set it as a numeric # field attrs[4] = str(attrs[4]) attributes[f['pk']] = attrs geometries[f['pk']] = f.hasGeometry() and f.geometry().asWkt() expected_attributes = {5: [5, -200, NULL, 'NuLl', '5'], 3: [3, 300, 'Pear', 'PEaR', '3'], 1: [1, 100, 'Orange', 'oranGe', '1'], 2: [2, 200, 'Apple', 'Apple', '2'], 4: [4, 400, 'Honey', 'Honey', '4']} expected_geometries = {1: 'Point (-70.332 66.33)', 2: 'Point (-68.2 70.8)', 3: None, 4: 'Point(-65.32 78.3)', 5: 'Point(-71.123 78.23)'} for f in extra_features: expected_attributes[f[0]] = f.attributes() if f.hasGeometry(): expected_geometries[f[0]] = f.geometry().asWkt() else: expected_geometries[f[0]] = None for i in skip_features: del expected_attributes[i] del expected_geometries[i] for i, a in changed_attributes.items(): for attr_idx, v in a.items(): expected_attributes[i][attr_idx] = v for i, g, in changed_geometries.items(): if g: expected_geometries[i] = g.asWkt() else: expected_geometries[i] = None self.assertEqual(attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes)) self.assertEqual(len(expected_geometries), len(geometries)) for pk, geom in list(expected_geometries.items()): if geom: assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format(pk, geom, geometries[pk]) else: self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
def test_check_validity(self): """Test that the output invalid contains the error reason""" polygon_layer = self._make_layer('Polygon') self.assertTrue(polygon_layer.startEditing()) f = QgsFeature(polygon_layer.fields()) f.setAttributes([1]) # Flake! f.setGeometry( QgsGeometry.fromWkt('POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))')) self.assertTrue(f.isValid()) f2 = QgsFeature(polygon_layer.fields()) f2.setAttributes([1]) f2.setGeometry( QgsGeometry.fromWkt( 'POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) self.assertTrue(polygon_layer.addFeatures([f, f2])) polygon_layer.commitChanges() polygon_layer.rollBack() self.assertEqual(polygon_layer.featureCount(), 2) QgsProject.instance().addMapLayers([polygon_layer]) alg = self.registry.createAlgorithmById('qgis:checkvalidity') context = QgsProcessingContext() context.setProject(QgsProject.instance()) feedback = ConsoleFeedBack() self.assertIsNotNone(alg) parameters = {} parameters['INPUT_LAYER'] = polygon_layer.id() parameters['VALID_OUTPUT'] = 'memory:' parameters['INVALID_OUTPUT'] = 'memory:' parameters['ERROR_OUTPUT'] = 'memory:' # QGIS method parameters['METHOD'] = 1 ok, results = execute(alg, parameters, context=context, feedback=feedback) self.assertTrue(ok) invalid_layer = QgsProcessingUtils.mapLayerFromString( results['INVALID_OUTPUT'], context) self.assertEqual(invalid_layer.fields().names()[-1], '_errors') self.assertEqual(invalid_layer.featureCount(), 1) f = next(invalid_layer.getFeatures()) self.assertEqual(f.attributes(), [1, 'segments 0 and 2 of line 0 intersect at 1, 1']) # GEOS method parameters['METHOD'] = 2 ok, results = execute(alg, parameters, context=context, feedback=feedback) self.assertTrue(ok) invalid_layer = QgsProcessingUtils.mapLayerFromString( results['INVALID_OUTPUT'], context) self.assertEqual(invalid_layer.fields().names()[-1], '_errors') self.assertEqual(invalid_layer.featureCount(), 1) f = next(invalid_layer.getFeatures()) self.assertEqual(f.attributes(), [1, 'Self-intersection'])
class DocumentModel(QAbstractTableModel): DocumentIdRole = Qt.UserRole + 1 DocumentPathRole = Qt.UserRole + 2 DocumentNameRole = Qt.UserRole + 3 DocumentExistsRole = Qt.UserRole + 4 DocumentToolTipRole = Qt.UserRole + 5 DocumentIsImageRole = Qt.UserRole + 6 def __init__(self, parent: QObject = None): super(DocumentModel, self).__init__(parent) self._relation = QgsRelation() self._nmRelation = QgsRelation() self._documents_path = str() self._document_filename = str() self._feature = QgsFeature() self._document_list = [] def init(self, relation: QgsRelation, nmRelation: QgsRelation, feature: QgsFeature, documents_path: str, document_filename: str): self._relation = relation self._nmRelation = nmRelation self._documents_path = documents_path self._document_filename = document_filename self._feature = feature self.reloadData() def rowCount(self, parent: QModelIndex = ...) -> int: return len(self._document_list) def columnCount(self, parent: QModelIndex = ...) -> int: return 1 def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...): return None def flags(self, index: QModelIndex) -> Qt.ItemFlags: flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable return flags def data(self, index: QModelIndex, role: int = ...): if index.row() < 0 or index.row() >= self.rowCount(QModelIndex()): return None return self._document_list[index.row()][role] def setData(self, index: QModelIndex, value, role: int = Qt.EditRole) -> bool: if index.row() < 0 or index.row() >= self.rowCount(QModelIndex()): return False return False def roleNames(self): return { self.DocumentIdRole: b'DocumentId', self.DocumentPathRole: b'DocumentPath', self.DocumentNameRole: b'DocumentName', self.DocumentExistsRole: b'DocumentExists', self.DocumentToolTipRole: b'DocumentToolTip', self.DocumentIsImageRole: b'DocumentIsImage' } def reloadData(self): self.beginResetModel() self._document_list = [] if self._relation.isValid() is False or self._feature.isValid( ) is False: self.endResetModel() return feature_list = [] layer = self._relation.referencingLayer() request = self._relation.getRelatedFeaturesRequest(self._feature) for feature in layer.getFeatures(request): feature_list.append(feature) if self._nmRelation.isValid(): filters = [] for joinTableFeature in feature_list: referencedFeatureRequest = self._nmRelation.getReferencedFeatureRequest( joinTableFeature) filterExpression = referencedFeatureRequest.filterExpression() filters.append("(" + filterExpression.expression() + ")") nmRequest = QgsFeatureRequest() nmRequest.setFilterExpression(" OR ".join(filters)) feature_list = [] layer = self._nmRelation.referencedLayer() for documentFeature in layer.getFeatures(nmRequest): feature_list.append(documentFeature) for documentFeature in feature_list: documents_path = str() if self._documents_path: exp = QgsExpression(self._documents_path) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) documents_path = exp.evaluate(context) document_filename = str() if self._document_filename: exp = QgsExpression(self._document_filename) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) document_filename = exp.evaluate(context) file_info = QFileInfo(QDir(str(documents_path)), str(document_filename)) # ToolTip toolTipList = [] toolTipList.append("<ul>") for field in documentFeature.fields(): index = documentFeature.fields().indexFromName(field.name()) toolTipList.append("<li><strong>{0}</strong>: {1}</li>".format( field.displayName(), documentFeature[index])) toolTipList.append("</ul>") self._document_list.append({ self.DocumentIdRole: documentFeature.id(), self.DocumentPathRole: file_info.filePath(), self.DocumentNameRole: file_info.fileName(), self.DocumentExistsRole: file_info.exists(), self.DocumentToolTipRole: "".join(toolTipList), self.DocumentIsImageRole: PreviewImageProvider.isMimeTypeSupported(file_info.filePath()) }) self.endResetModel()
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect( self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage( "Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect( self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer( ).attributeValueChanged.disconnect( self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect( self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config( fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create( "RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled( self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer( ).isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer( ).changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage( "Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled( self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid( ) or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked( ) or not self.referencingFeature.isValid( ) or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length() / 2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates( layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid( ): return self.featureHighlight = QgsHighlight( self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int( settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float( settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float( settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
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 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.fromPoint(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().exportToWkt(), g.exportToWkt()) # 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.setDefaultValueExpression(2, '3*4') f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12.0]) # we 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, 12.0]) # layer with default value expression based on geometry layer.setDefaultValueExpression(2, '3*$x') f = QgsVectorLayerUtils.createFeature(layer, g) self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueExpression(2, None) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_4', 128, NULL])
def testGetFeaturesFidsTests(self): fids = [f.id() for f in self.source.getFeatures()] self.assertEqual(len(fids), 5) request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]]) result = set([f.id() for f in self.source.getFeatures(request)]) all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) expected = set([fids[0], fids[2]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format( expected, result) self.assertTrue(all_valid) # test that results match QgsFeatureRequest.acceptFeature for f in self.source.getFeatures(): self.assertEqual(request.acceptFeature(f), f.id() in expected) result = set([ f.id() for f in self.source.getFeatures( QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]])) ]) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format( expected, result) #sources should ignore non-existent fids result = set([ f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids( [-101, fids[1], -102, fids[3], -103, fids[4], -104])) ]) expected = set([fids[1], fids[3], fids[4]]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format( expected, result) result = set([ f.id() for f in self.source.getFeatures( QgsFeatureRequest().setFilterFids([])) ]) expected = set([]) assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format( expected, result) # Rewind mid-way request = QgsFeatureRequest().setFilterFids( [fids[1], fids[3], fids[4]]) feature_it = self.source.getFeatures(request) feature = QgsFeature() feature.setValid(True) self.assertTrue(feature_it.nextFeature(feature)) self.assertIn(feature.id(), [fids[1], fids[3], fids[4]]) first_feature = feature self.assertTrue(feature.isValid()) # rewind self.assertTrue(feature_it.rewind()) self.assertTrue(feature_it.nextFeature(feature)) self.assertEqual(feature.id(), first_feature.id()) self.assertTrue(feature.isValid()) # grab all features self.assertTrue(feature_it.nextFeature(feature)) self.assertTrue(feature_it.nextFeature(feature)) # none left self.assertFalse(feature_it.nextFeature(feature)) self.assertFalse(feature.isValid())
def testGetFeatures(self, source=None, extra_features=[], skip_features=[], changed_attributes={}, changed_geometries={}): """ Test that expected results are returned when fetching all features """ # IMPORTANT - we do not use `for f in source.getFeatures()` as we are also # testing that existing attributes & geometry in f are overwritten correctly # (for f in ... uses a new QgsFeature for every iteration) if not source: source = self.source it = source.getFeatures() f = QgsFeature() attributes = {} geometries = {} while it.nextFeature(f): # expect feature to be valid self.assertTrue(f.isValid()) # split off the first 5 attributes only - some source test datasets will include # additional attributes which we ignore attrs = f.attributes()[0:5] # force the num_char attribute to be text - some sources (e.g., delimited text) will # automatically detect that this attribute contains numbers and set it as a numeric # field attrs[4] = str(attrs[4]) attributes[f['pk']] = attrs geometries[ f['pk']] = f.hasGeometry() and f.geometry().exportToWkt() expected_attributes = { 5: [5, -200, NULL, 'NuLl', '5'], 3: [3, 300, 'Pear', 'PEaR', '3'], 1: [1, 100, 'Orange', 'oranGe', '1'], 2: [2, 200, 'Apple', 'Apple', '2'], 4: [4, 400, 'Honey', 'Honey', '4'] } expected_geometries = { 1: 'Point (-70.332 66.33)', 2: 'Point (-68.2 70.8)', 3: None, 4: 'Point(-65.32 78.3)', 5: 'Point(-71.123 78.23)' } for f in extra_features: expected_attributes[f[0]] = f.attributes() if f.hasGeometry(): expected_geometries[f[0]] = f.geometry().exportToWkt() else: expected_geometries[f[0]] = None for i in skip_features: del expected_attributes[i] del expected_geometries[i] for i, a in changed_attributes.items(): for attr_idx, v in a.items(): expected_attributes[i][attr_idx] = v for i, g, in changed_geometries.items(): if g: expected_geometries[i] = g.exportToWkt() else: expected_geometries[i] = None self.assertEqual( attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes)) self.assertEqual(len(expected_geometries), len(geometries)) for pk, geom in list(expected_geometries.items()): if geom: assert compareWkt( geom, geometries[pk] ), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format( pk, geom, geometries[pk]) else: self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
def on_importButton_clicked(self): self.cannotImportLabel.hide() # init progress bar c = 0 for p_id in self.data.keys(): for s_id, section in self.data[p_id]['Sections'].items(): c += 1 self.progressBar.setMaximum(c) self.progressBar.setMinimum(0) self.progressBar.setValue(0) self.progressBar.setFormat('Contrôle les collecteurs %v/%m') self.progressBar.show() self.cancelButton.show() self.importButton.hide() self.cancel = False i = 0 # initilaize maintenance and damage layers and features maintenance_layer_id = self.settings.value("maintenance_layer") maintenance_layer = QgsMapLayerRegistry.instance().mapLayer(maintenance_layer_id) damage_layer_id = self.settings.value("damage_layer") damage_layer = QgsMapLayerRegistry.instance().mapLayer(damage_layer_id) join_layer_id = self.settings.value("join_maintence_wastewaterstructure_layer") join_layer = QgsMapLayerRegistry.instance().mapLayer(join_layer_id) if join_layer is None: self.cannotImportLabel.show() self.cannotImportLabel.setText('La couche de jointure re_maintenance_event_wastewater_structureest manquante.') self.hide_progress() return features = {} # dictionnary with waste water structure id (reach) as key, and as values: a dict with maintenance event and damages for p_id in self.data.keys(): previous_section_imported = True for s_id, section in self.data[p_id]['Sections'].items(): if self.cancel: self.hide_progress() return if section['Import'] is not True: previous_section_imported = False continue for i_id, inspection in self.data[p_id]['Sections'][s_id]['Inspections'].iteritems(): if inspection['Import']: # offset in case of several sections in inspection data correspond to a single section in qgep data distance_offset = 0 if section['UsePreviousSection'] is not True: previous_section_imported = True # get corresponding reaches in qgep project reach_features = [] for fid in [section['QgepChannelId{}'.format(i)] for i in (1, 2, 3)]: if fid is None: break f = sectionAtId(fid) if f.isValid() is False: self.cannotImportLabel.show() self.cannotImportLabel.setText('L''inspection {0} chambre {1} à {2} a un collecteur assigné qui n''existe pas ou plus.' .format(section['Counter'], section['StartNode'], section['EndNode'])) self.sectionWidget.select_section(s_id) self.hide_progress() return reach_features.append(QgsFeature(f)) if len(reach_features) == 0: self.cannotImportLabel.show() self.cannotImportLabel.setText('L''inspection {} chambre {} à {} n''a pas de collecteur assigné.' .format(section['Counter'], section['StartNode'], section['EndNode'])) self.sectionWidget.select_section(s_id) self.hide_progress() return # create maintenance/examination event (one per qgep reach feature) for rf in reach_features: # in case several sections in qgep data correspond to a single section in inspection data mf = QgsFeature() init_fields = maintenance_layer.dataProvider().fields() mf.setFields(init_fields) mf.initAttributes(init_fields.size()) mf['obj_id'] = maintenance_layer.dataProvider().defaultValue(maintenance_layer.fieldNameIndex('obj_id')) # mf['identifier'] = i_id # use custom id to retrieve feature mf['maintenance_type'] = 'examination' mf['kind'] = 4564 # vl_maintenance_event_kind: inspection mf['operator'] = inspection['Operator'] mf['time_point'] = QDateTime(inspection['InspDate']) mf['remark'] = '' mf['status'] = 2550 # vl_maintenance_event: accomplished mf['inspected_length'] = section['Sectionlength'] mf['videonumber'] = inspection['VideoName'] mf['base_data'] = self.pdf_path_widget.filePath() if self.relationWidgetWrapper is not None: mf['fk_operating_company'] = self.relationWidgetWrapper.value() if inspection['CodeInspectionDir'] == 'D': mf['fk_reach_point'] = rf['rp_from_obj_id'] else: mf['fk_reach_point'] = rf['rp_to_obj_id'] features[rf['ws_obj_id']] = {'maintenance': QgsFeature(mf), 'damages': [], 'pictures': [], 'structure_condition': 4} else: # in case several sections in inspection data correspond to a single section in qgep data # substract length from previous sections in inspection data if not previous_section_imported: self.cannotImportLabel.show() self.cannotImportLabel.setText( 'L''inspection {} chambre {} à {} utilise le collecteur précédent mais il n''est pas défini.' .format(section['Counter'], section['StartNode'], section['EndNode'])) self.sectionWidget.select_section(s_id) self.hide_progress() return distance_offset = 0 offset_section_id = s_id while self.data[p_id]['Sections'][offset_section_id]['UsePreviousSection'] is True: # get previous section id # http://stackoverflow.com/questions/28035490/in-python-how-can-i-get-the-next-and-previous-keyvalue-of-a-particular-key-in offset_section_id = self.data[p_id]['Sections']._OrderedDict__map[offset_section_id][0][2] print offset_section_id # cumulate offset distance_offset -= self.data[p_id]['Sections'][offset_section_id]['Sectionlength'] # add corresponding damages reach_index = 0 structure_condition = 4 # = ok for observation in self.data[p_id]['Sections'][s_id]['Inspections'][i_id]['Observations'].values(): if observation['Import']: distance = observation['Position'] + distance_offset if not observation['ForceImport']: while distance > reach_features[reach_index]['length_effective']: if reach_index < len(reach_features)-1: distance -= reach_features[reach_index]['length_effective'] reach_index += 1 else: if distance <= reach_features[reach_index]['length_effective'] + self.settings.value('tolerance_channel_length'): # add 50cm tolerance break else: self.cannotImportLabel.show() self.cannotImportLabel.setText('L''inspection {} chambre {} à {} a des observations à des positions supérieures à la longueur' ' du ou des collecteurs assignés.'.format(section['Counter'], section['StartNode'], section['EndNode'])) self.sectionWidget.select_section(s_id) self.hide_progress() return # create maintenance/examination event df = QgsFeature() initFields = damage_layer.dataProvider().fields() df.setFields(initFields) df.initAttributes(initFields.size()) df['obj_id'] = damage_layer.dataProvider().defaultValue(damage_layer.fieldNameIndex('obj_id')) df['damage_type'] = 'channel' df['comments'] = observation['Text'] df['single_damage_class'] = damageLevel2vl(observation['Rate']) df['channel_damage_code'] = int(damageCode2vl(observation['OpCode'])) df['distance'] = distance df['video_counter'] = observation['MPEGPosition'] # pictures pics = observation['PhotoFilename'] # get wastewater structure id ws_obj_id = reach_features[reach_index]['ws_obj_id'] features[ws_obj_id]['damages'].append(df) features[ws_obj_id]['pictures'].append(pics) structure_condition = min(structure_condition, observation['Rate']) features[ws_obj_id]['structure_condition'] = structure_condition self.progressBar.setValue(i) i += 1 self.progressBar.setMaximum(len(features)) self.progressBar.setMinimum(0) self.progressBar.setValue(0) self.progressBar.setFormat(u'Import des données %v/%m') self.progressBar.show() self.cancelButton.show() self.importButton.hide() self.cancel = False with edit(maintenance_layer): i = 0 for ws_obj_id, elements in features.iteritems(): QCoreApplication.processEvents() if self.cancel: break maintenance = elements['maintenance'] damages = elements['damages'] pictures = elements['pictures'] structure_condition = elements['structure_condition'] if len(damages) == 0: continue # write maintenance feature maintenance_layer.addFeature(maintenance) # write video layer_id = MySettings().value("file_layer") file_layer = QgsMapLayerRegistry.instance().mapLayer(layer_id) of = QgsFeature() init_fields = file_layer.dataProvider().fields() of.setFields(init_fields) of.initAttributes(init_fields.size()) of['obj_id'] = file_layer.dataProvider().defaultValue(file_layer.fieldNameIndex('obj_id')) of['class'] = 3825 # i.e. maintenance event of['kind'] = 3771 # i.e. video of['object'] = maintenance['obj_id'] of['identifier'] = maintenance['videonumber'] of['path_relative'] = self.data_path_line_edit.text() + '\Video' file_layer.addFeature(of) # set fkey maintenance event id to all damages for k, _ in enumerate(damages): damages[k]['fk_examination'] = maintenance['obj_id'] # write damages for k, damage in enumerate(damages): damage_layer.addFeature(damage, False) # add pictures to od_file with reference to damage layer_id = MySettings().value("file_layer") file_layer = QgsMapLayerRegistry.instance().mapLayer(layer_id) for pic in pictures[k]: of = QgsFeature() init_fields = file_layer.dataProvider().fields() of.setFields(init_fields) of.initAttributes(init_fields.size()) of['obj_id'] = file_layer.dataProvider().defaultValue(file_layer.fieldNameIndex('obj_id')) of['class'] = 3871 # i.e. damage of['kind'] = 3772 # i.e. photo of['object'] = damage['obj_id'] of['identifier'] = pic of['path_relative'] = self.data_path_line_edit.text() + '\Picture' file_layer.addFeature(of) # write in relation table (wastewater structure - maintenance events) jf = QgsFeature() init_fields = join_layer.dataProvider().fields() jf.setFields(init_fields) jf.initAttributes(init_fields.size()) jf['obj_id'] = join_layer.dataProvider().defaultValue(join_layer.fieldNameIndex('obj_id')) jf['fk_wastewater_structure'] = ws_obj_id jf['fk_maintenance_event'] = maintenance['obj_id'] join_layer.addFeature(jf) # get current reach rf = QgsFeature() layer_id = MySettings().value("wastewater_structure") wsl = QgsMapLayerRegistry.instance().mapLayer(layer_id) if wsl is not None: request = QgsFeatureRequest().setFilterExpression('"obj_id" = \'{}\''.format(ws_obj_id)) for f in wsl.getFeatures(request): rf = QgsFeature(f) if rf.isValid(): # update structure condition if worse old_level = structure_condition_2_damage_level(rf['structure_condition']) if old_level is None or old_level > 'Z{}'.format(structure_condition): rf['structure_condition'] = damage_level_2_structure_condition(structure_condition) wsl.updateFeature(rf) i += 1 self.progressBar.setValue(i) self.progressBar.hide() self.cancelButton.hide() self.importButton.show()
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage("Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect(self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect(self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config(fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create("RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled(self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer().isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer().changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage("Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled(self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid() or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked() or not self.referencingFeature.isValid() or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length()/2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates(layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid(): return self.featureHighlight = QgsHighlight(self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float(settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
"joinlayer", "memory") pr = joinLayer.dataProvider() f1 = QgsFeature() f1.setAttributes([QVariant("foo"), QVariant(123), QVariant(321)]) f1.setGeometry(QgsGeometry.fromPoint(QgsPoint(1,1))) f2 = QgsFeature() f2.setAttributes([QVariant("bar"), QVariant(456), QVariant(654)]) f2.setGeometry(QgsGeometry.fromPoint(QgsPoint(2,2))) assert pr.addFeatures([f1, f2]) assert joinLayer.pendingFeatureCount() == 2 return joinLayer def dumpFeature(f): print "--- FEATURE DUMP ---" print "valid: %d | id: %d" % (f.isValid(), f.id()) geom = f.geometry() if geom: print "geometry wkb: %d" % geom.wkbType() else: print "no geometry" print "attrs: %s" % str(f.attributes()) def formatAttributes(attrs): return repr([ unicode(a.toString()) for a in attrs ]) def dumpEditBuffer(layer): editBuffer = layer.editBuffer() if not editBuffer:
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])
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 testProjectStorage(self): # New project without fileName p0 = QgsProject() self.assertTrue(p0.auxiliaryStorage().isValid()) # Create new layers with key otherwise auxiliary layers are not # automacially created when added in project vl0 = createLayer() vl0Shp = writeShape(vl0, 'vl0.shp') vl1 = createLayer() vl1Shp = writeShape(vl1, 'vl1.shp') vl0 = QgsVectorLayer(vl0Shp, 'points', 'ogr') self.assertTrue(vl0.isValid()) vl1 = QgsVectorLayer(vl1Shp, 'points', 'ogr') self.assertTrue(vl1.isValid()) # Add layers to project and check underlying auxiliary layers p0.addMapLayers([vl0, vl1]) self.assertTrue(vl0.loadAuxiliaryLayer(p0.auxiliaryStorage(), 'pk')) self.assertTrue( vl1.loadAuxiliaryLayer(p0.auxiliaryStorage(), 'num_char')) al0 = vl0.auxiliaryLayer() al1 = vl1.auxiliaryLayer() self.assertEqual(al0.joinInfo().targetFieldName(), 'pk') self.assertEqual(al1.joinInfo().targetFieldName(), 'num_char') # Add a field in auxiliary layers pdef0 = QgsPropertyDefinition('propname', QgsPropertyDefinition.DataTypeNumeric, '', '', 'ut') self.assertTrue(al0.addAuxiliaryField(pdef0)) pdef1 = QgsPropertyDefinition('propname1', QgsPropertyDefinition.DataTypeString, '', '', 'ut') self.assertTrue(al1.addAuxiliaryField(pdef1)) # Check auxiliary fields names af0Name = QgsAuxiliaryLayer.nameFromProperty(pdef0, False) self.assertEqual(af0Name, 'ut_propname') af1Name = QgsAuxiliaryLayer.nameFromProperty(pdef1, False) self.assertEqual(af1Name, 'ut_propname1') # Set value for auxiliary fields req = QgsFeatureRequest().setFilterExpression("name = 'Honey'") f = QgsFeature() vl0.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) af0Name = QgsAuxiliaryLayer.nameFromProperty(pdef0, True) index0 = vl0.fields().indexOf(af0Name) vl0.changeAttributeValue(f.id(), index0, 333) req = QgsFeatureRequest().setFilterExpression("name = 'Apple'") f = QgsFeature() vl1.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) af1Name = QgsAuxiliaryLayer.nameFromProperty(pdef1, True) index1 = vl1.fields().indexOf(af1Name) vl1.changeAttributeValue(f.id(), index0, 'myvalue') req = QgsFeatureRequest().setFilterExpression("name = 'Orange'") f = QgsFeature() vl1.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) vl1.changeAttributeValue(f.id(), index0, 'myvalue1') # Save the project in a zip file f = tmpPath() + '.qgz' p0.write(f) # Open the zip file with embedded auxiliary storage p1 = QgsProject() p1.read(f) # Check that auxiliary fields are well loaded in layers self.assertEqual(len(p1.mapLayers().values()), 2) for vl in p1.mapLayers().values(): al = vl.auxiliaryLayer() self.assertEqual(len(al.auxiliaryFields()), 1) af = al.auxiliaryFields()[0] afPropDef = QgsAuxiliaryLayer.propertyDefinitionFromField(af) self.assertEqual(afPropDef.origin(), 'ut') if vl.auxiliaryLayer().joinInfo().targetFieldName() == 'pk': self.assertEqual(afPropDef.name(), 'propname') self.assertEqual(al.featureCount(), 1) req = QgsFeatureRequest().setFilterExpression("name = 'Honey'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index0], 333.0) else: # num_char self.assertEqual(al.featureCount(), 2) self.assertEqual(afPropDef.name(), 'propname1') req = QgsFeatureRequest().setFilterExpression("name = 'Apple'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index1], 'myvalue') req = QgsFeatureRequest().setFilterExpression( "name = 'Orange'") f = QgsFeature() vl.getFeatures(req).nextFeature(f) self.assertTrue(f.isValid()) self.assertEqual(f.attributes()[index1], 'myvalue1')
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 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])