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])
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 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 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 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 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 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 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 processAlgorithm(self, parameters, context, feedback): # get input variables source = self.parameterAsSource(parameters, self.INPUT_LINE, context) swath_angle_field = self.parameterAsString(parameters, self.SWATH_ANGLE_FIELD, context) swath_angle_fallback = self.parameterAsInt(parameters, self.SWATH_ANGLE, context) raster_layer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context) band_number = self.parameterAsInt(parameters, self.BAND, context) # copy of the field name for later swath_angle_field_name = swath_angle_field # set new default values in config feedback.pushConsoleInfo( self.tr(f'Storing new default settings in config...')) self.config.set(self.module, 'swath_angle', swath_angle_fallback) # get crs's crs_line = source.sourceCrs() crs_raster = raster_layer.crs() # get project transform_context transform_context = context.transformContext() # CRS transformation to "WGS84/World Mercator" for MBES coverage operations crs_mercator = QgsCoordinateReferenceSystem('EPSG:3395') trans_line2merc = QgsCoordinateTransform(crs_line, crs_mercator, transform_context) # CRS transformation to raster layer CRS for depth sampling trans_merc2raster = QgsCoordinateTransform(crs_mercator, crs_raster, transform_context) trans_line2raster = QgsCoordinateTransform(crs_line, crs_raster, transform_context) crs_geo = QgsCoordinateReferenceSystem('EPSG:4326') trans_line2geo = QgsCoordinateTransform(crs_line, crs_geo, transform_context) # initialize distance tool da = QgsDistanceArea() da.setSourceCrs(crs_mercator, transform_context) da.setEllipsoid(crs_mercator.ellipsoidAcronym()) # empty lists for unioned buffers buffer_union_list = [] # get (selected) features features = source.getFeatures() # feedback total = 100.0 / source.featureCount() if source.featureCount() else 0 # loop through features feedback.pushConsoleInfo(self.tr(f'Densifying line features...')) feedback.pushConsoleInfo(self.tr(f'Extracting vertices...')) feedback.pushConsoleInfo(self.tr(f'Sampling values...')) feedback.pushConsoleInfo( self.tr(f'Computing depth dependent buffers...')) feedback.pushConsoleInfo(self.tr(f'Unionizing output features...')) for feature_id, feature in enumerate(features): # get feature geometry feature_geom = feature.geometry() # check for LineString geometry if QgsWkbTypes.isSingleType(feature_geom.wkbType()): # get list of vertices vertices_list = feature_geom.asPolyline() vertices_list = [vertices_list] # check for MultiLineString geometry elif QgsWkbTypes.isMultiType(feature_geom.wkbType()): # get list of list of vertices per multiline part vertices_list = feature_geom.asMultiPolyline() for part_id, vertices in enumerate(vertices_list): # transform vertices CRS to "WGS 84 / World Mercator" vertices_t = [] for vertex in vertices: vertex_trans = trans_line2merc.transform(vertex) vertices_t.append(vertex_trans) # get centroid as point for UTM zone selection centroid = feature_geom.centroid() centroid_point = centroid.asPoint() # check if centroid needs to be transformed to get x/y in lon/lat if not crs_line.isGeographic(): centroid_point = trans_line2geo.transform(centroid_point) # get UTM zone of feature for buffering lat, lon = centroid_point.y(), centroid_point.x() crs_utm = self.get_UTM_zone(lat, lon) # create back and forth transformations for later trans_merc2utm = QgsCoordinateTransform( crs_mercator, crs_utm, transform_context) trans_utm2line = QgsCoordinateTransform( crs_utm, crs_line, transform_context) # split line into segments for segment_id in range(len(vertices_t) - 1): # ===== (1) DENSIFY LINE VERTICES ===== # create new Polyline geometry for line segment segment_geom = QgsGeometry.fromPolylineXY( [vertices_t[segment_id], vertices_t[segment_id + 1]]) # measure ellipsoidal distance between start and end vertex segment_length = da.measureLength(segment_geom) # calculate number of extra vertices to insert extra_vertices = int(segment_length // self.vertex_distance) # create additional vertices along line segment segment_geom_dense = segment_geom.densifyByCount( extra_vertices - 1) # initialize additional fields feature_id_field = QgsField('feature_id', QVariant.Int, 'Integer', len=5, prec=0) part_id_field = QgsField('part_id', QVariant.Int, 'Integer', len=5, prec=0) segment_id_field = QgsField('segment_id', QVariant.Int, 'Integer', len=5, prec=0) # list for segment buffers buffer_list = [] # loop over all vertices of line segment for i, vertex in enumerate(segment_geom_dense.vertices()): # === CREATE POINT FEATURE === # initialize feature and set geometry fpoint = QgsFeature() fpoint.setGeometry(vertex) # sample bathymetry grid # get point geometry (as QgsPointXY) fpoint_geom = fpoint.geometry() pointXY = fpoint_geom.asPoint() # transform point to raster CRS pointXY_raster = trans_merc2raster.transform( pointXY) #trans_line2raster.transform(pointXY) # sample raster at point location pointXY_depth, error_check = raster_layer.dataProvider( ).sample(pointXY_raster, 1) # check if valid depth was sampled, otherwise skip point if error_check == False: continue # create depth-dependant buffer: # check if swath_angle field is selected if swath_angle_field != '': # get value from field swath_angle_field_value = feature.attribute( swath_angle_field) # check if value is set (not NOLL) if swath_angle_field_value == None: # if NULL, set fallback swath_angle = swath_angle_fallback else: # othervise take value from field swath_angle = swath_angle_field_value # or if no field was selected use fallback value right away else: swath_angle = swath_angle_fallback # calculate buffer radius (swath width from depth and swath angle) buffer_radius = round( tan(radians(swath_angle / 2)) * abs(pointXY_depth), 0) # transform point from mercator zu UTM fpoint_geom.transform(trans_merc2utm) # create buffer buffer = fpoint_geom.buffer(buffer_radius, 10) # transform buffer back to initial input CRS buffer.transform(trans_utm2line) # store buffer in list buffer_list.append(buffer) # check if any points in this segment have been sampled if buffer_list == []: continue # dissolve point buffers of line segment: # dissolve all polygons based on line vertices into single feature buffer_union = QgsGeometry().unaryUnion(buffer_list) # set fields and attributes: # empty fields buffer_fields = QgsFields() # loop through line feature fields for field in feature.fields(): # and append all but the 'fid' field (if it exists) if field.name() != 'fid': buffer_fields.append(field) # append extra buffer fields (intial feature id, part id and segment id for buffer_field in [ feature_id_field, part_id_field, segment_id_field ]: buffer_fields.append(buffer_field) # if no input swath_angle field was selected on input, create one if swath_angle_field == '': swath_angle_field_name = 'mbes_swath_angle' buffer_fields.append( QgsField(swath_angle_field_name, QVariant.Int, 'Integer', len=5, prec=0)) # initialize polygon feature fpoly = QgsFeature(buffer_fields) # set attributes for polygon feature for field in feature.fields(): # ignore 'fid' again if field.name() != 'fid': # set attribute from feature to buffer fpoly.setAttribute(field.name(), feature.attribute(field.name())) # set addtional buffer fields fpoly.setAttribute('feature_id', feature_id) fpoly.setAttribute('part_id', part_id) fpoly.setAttribute('segment_id', segment_id) fpoly.setAttribute(swath_angle_field_name, swath_angle) # set geometry fpoly.setGeometry(buffer_union) # store segment coverage polygon if fpoly.hasGeometry() and fpoly.isValid(): buffer_union_list.append(fpoly) # set progess feedback.setProgress(int(feature_id * total)) # if buffer_union_list is empty, no buffer features where created if buffer_union_list == []: raise Exception( 'No depth values could be sampled from the input raster!') # creating feature sink feedback.pushConsoleInfo(self.tr(f'Creating feature sink...')) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, buffer_fields, QgsWkbTypes.MultiPolygon, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # write coverage features to sink feedback.pushConsoleInfo(self.tr(f'Writing features...')) sink.addFeatures(buffer_union_list, QgsFeatureSink.FastInsert) # make variables accessible for post processing self.output = dest_id result = {self.OUTPUT: self.output} return result
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])