Exemple #1
0
    def testUpdatedFields(self):
        """Test when referenced layer update its fields
        https://issues.qgis.org/issues/20893
        """

        ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem", "memory")
        self.assertEqual(ml.isValid(), True)
        QgsProject.instance().addMapLayer(ml)

        ml.startEditing()
        f1 = QgsFeature(ml.fields())
        f1.setGeometry(QgsGeometry.fromWkt('POINT(2 3)'))
        ml.addFeatures([f1])
        ml.commitChanges()

        vl = QgsVectorLayer("?query=select a, geometry from mem", "vl", "virtual")
        self.assertEqual(vl.isValid(), True)

        # add one more field
        ml.dataProvider().addAttributes([QgsField('newfield', QVariant.Int)])
        ml.updateFields()

        self.assertEqual(ml.featureCount(), vl.featureCount())
        self.assertEqual(vl.fields().count(), 1)

        geometry = next(vl.getFeatures()).geometry()
        self.assertTrue(geometry)

        point = geometry.asPoint()
        self.assertEqual(point.x(), 2)
        self.assertEqual(point.y(), 3)

        QgsProject.instance().removeMapLayer(ml)
Exemple #2
0
    def testFieldsWithSpecialCharacters(self):
        ml = QgsVectorLayer("Point?srid=EPSG:4326&field=123:int", "mem_with_nontext_fieldnames", "memory")
        self.assertEqual(ml.isValid(), True)
        QgsProject.instance().addMapLayer(ml)

        ml.startEditing()
        self.assertTrue(ml.addAttribute(QgsField('abc:123', QVariant.String)))
        self.assertTrue(ml.addAttribute(QgsField('map', QVariant.String)))  # matches QGIS expression function name
        f1 = QgsFeature(ml.fields())
        f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)'))
        f1.setAttributes([1, 'a', 'b'])
        f2 = QgsFeature(ml.fields())
        f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)'))
        f2.setAttributes([2, 'c', 'd'])
        ml.addFeatures([f1, f2])
        ml.commitChanges()

        vl = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames", "vl", "virtual")
        self.assertEqual(vl.isValid(), True)
        self.assertEqual(vl.fields().at(0).name(), '123')
        self.assertEqual(vl.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl.featureCount(), 2)

        features = [f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression('"abc:123"=\'c\''))]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [2, 'c', 'd'])

        features = [f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression('"map"=\'b\''))]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [1, 'a', 'b'])

        vl2 = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames where \"abc:123\"='c'", "vl", "virtual")
        self.assertEqual(vl2.isValid(), True)
        self.assertEqual(vl2.fields().at(0).name(), '123')
        self.assertEqual(vl2.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl2.featureCount(), 1)

        features = [f for f in vl2.getFeatures()]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [2, 'c', 'd'])

        vl3 = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames where \"map\"='b'", "vl", "virtual")
        self.assertEqual(vl3.isValid(), True)
        self.assertEqual(vl3.fields().at(0).name(), '123')
        self.assertEqual(vl3.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl3.featureCount(), 1)

        features = [f for f in vl3.getFeatures()]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [1, 'a', 'b'])

        QgsProject.instance().removeMapLayer(ml)
    def testVectorLayerUtilsUniqueWithProviderDefault(self):
        vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
        default_clause = "nextval('qgis_test.\"someData_pk_seq\"'::regclass)"
        self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause)
        self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4))

        vl.startEditing()
        f = QgsFeature(vl.fields())
        f.setAttribute(0, default_clause)
        self.assertTrue(vl.addFeatures([f]))
        self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
    def dict2lyr(self):
        new_feats = []
        for k in self.ndict:
            cs = self.ndict[k]['attrs']
            for points in self.ndict[k]['points']:
                distance = points['distance']
                elem = QgsFeature()
                elem.setGeometry(point_geometry(points['X'], points['Y']))
                elem.setAttributes(cs + [distance])
                new_feats.append(elem)

        crs = self.layer.crs().authid()
        vl = QgsVectorLayer('Point?crs={0}'.format(crs), self.outname, 'memory')
        pr = vl.dataProvider()
        pr.addAttributes(self.flds)
        vl.startEditing()
        vl.addFeatures(new_feats)
        vl.updateExtents()
        vl.commitChanges()
        vl.removeSelection()
        return vl
            def f():
                crs = iface.mapCanvas().mapRenderer().destinationCrs()
                uri = "%s?srsname=%s&typename=geonode:%s&version=1.0.0&request=GetFeature&service=WFS" % (url, crs.authid(), name)
                qgslayer = QgsVectorLayer(uri, name, "WFS")
                if not qgslayer.isValid():
                    raise Exception ("Layer at %s is not a valid layer" % uri)

                fieldname = self._getTimeField(qgslayer)

                if fieldname is None:
                    QgsMapLayerRegistry.instance().addMapLayers([qgslayer])
                else:
                    memlayer = QgsVectorLayer("%s?crs=%s" % (GEOM_TYPE_MAP[qgslayer.wkbType()], crs.authid()), name, "memory")
                    memlayer.startEditing()
                    for field in qgslayer.pendingFields():
                        memlayer.addAttribute(field)
                    for feat in qgslayer.getFeatures():
                        memlayer.addFeatures([feat])
                    memlayer.commitChanges()
                    QgsMapLayerRegistry.instance().addMapLayers([memlayer])
                    memlayer.setSelectedFeatures([])
                    addWfsAnimation(memlayer, fieldname)
Exemple #6
0
    def testFiltersWithoutUid(self):
        ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem_no_uid", "memory")
        self.assertEqual(ml.isValid(), True)
        QgsProject.instance().addMapLayer(ml)

        # a memory layer with 10 features
        ml.startEditing()
        for i in range(10):
            f = QgsFeature(ml.fields())
            f.setGeometry(QgsGeometry.fromWkt('POINT({} 0)'.format(i)))
            f.setAttributes([i])
            ml.addFeatures([f])
        ml.commitChanges()

        df = QgsVirtualLayerDefinition()
        df.setQuery('select * from mem_no_uid')
        vl = QgsVectorLayer(df.toString(), "vl", "virtual")
        self.assertEqual(vl.isValid(), True)

        # make sure the returned id with a filter is the same as
        # if there is no filter
        req = QgsFeatureRequest().setFilterRect(QgsRectangle(4.5, -1, 5.5, 1))
        fids = [f.id() for f in vl.getFeatures(req)]
        self.assertEqual(fids, [5])

        req = QgsFeatureRequest().setFilterExpression("a = 5")
        fids = [f.id() for f in vl.getFeatures(req)]
        self.assertEqual(fids, [5])

        req = QgsFeatureRequest().setFilterFid(5)
        a = [(f.id(), f['a']) for f in vl.getFeatures(req)]
        self.assertEqual(a, [(5, 5)])

        req = QgsFeatureRequest().setFilterFids([5, 6, 8])
        a = [(f.id(), f['a']) for f in vl.getFeatures(req)]
        self.assertEqual(a, [(5, 5), (6, 6), (8, 8)])

        QgsProject.instance().removeMapLayer(ml)
Exemple #7
0
    def test_queryOnMemoryLayer(self):
        ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem", "memory")
        self.assertEqual(ml.isValid(), True)
        QgsProject.instance().addMapLayer(ml)

        ml.startEditing()
        f1 = QgsFeature(ml.fields())
        f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)'))
        f2 = QgsFeature(ml.fields())
        f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)'))
        ml.addFeatures([f1, f2])
        ml.commitChanges()

        vl = QgsVectorLayer("?query=select * from mem", "vl", "virtual")
        self.assertEqual(vl.isValid(), True)

        self.assertEqual(ml.featureCount(), vl.featureCount())

        # test access to pending features as well
        ml.startEditing()
        f3 = QgsFeature(ml.fields())
        ml.addFeatures([f3])
        self.assertEqual(ml.featureCount(), vl.featureCount())
    def testVectorLayerUtilsUniqueWithProviderDefault(self):
        vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
        default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
        vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
        self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause)
        self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4))

        vl.startEditing()
        f = QgsFeature(vl.fields())
        f.setAttribute(0, default_clause)
        self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
        self.assertTrue(vl.addFeatures([f]))

        # the default value clause should exist...
        self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
        # but it should not prevent the attribute being validated
        self.assertTrue(QgsVectorLayerUtils.validateAttribute(vl, f, 0))
        vl.rollBack()
        def _test(autoTransaction):
            """Test buffer methods within and without transactions

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

            def _check_feature(wkt):

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

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

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

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

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

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

            self.assertTrue(layer_a.isValid())

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

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

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

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

            _check_feature('POINT (7 45)')

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

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

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

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

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

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

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

            layer_a.undoStack().undo()

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

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

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

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

            self.assertTrue(layer_a.commitChanges())

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            self.assertTrue(layer_a.commitChanges())

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

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

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

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

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

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

            self.assertTrue(layer_a.rollBack())

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

            self.assertTrue(layer_a.startEditing())

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

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

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

            layer_a.undoStack().redo()
            self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx)
            self.assertEqual(layer_a.fields().lookupField(field.name()), -1)
Exemple #10
0
def add_geojson_features(geojson,
                         project,
                         qgis_layer_id=None,
                         connection_id=None,
                         new_layer_name=None,
                         name=None,
                         style=None):
    """Add geojson features to a destination layer, the layer can be specified
    by passing a QgsVectorLayer instance or by specifying a connection and
    a new layer name plus the QDjango project for the new layer.

    The connection may assume the special values `__shapefile__`, `__spatialite__` or `__geopackage__`
    for the creation of new OGR files of the corresponding type.

    The creation of the new layer may raise an exception is a layer with the same name as
    new_layer_name already exists. For already existing layers the `qgis_layer_id` can be used,
    provided that the layer belongs to the current `project`.

    Returns the qgis_layer_id

    :param geojson: new features in GeoJSON format
    :type geojson: str
    :param project: QDjango Project instance for the new or the existing layer
    :type project: Project instance
    :param layer_id: optional, QGIS layer id
    :type layer_id: QGIS layer id
    :param connection: optional, connection id or the special value `__shapefile__`, `__spatialite__` or `__geopackage__`
    :type connection: str
    :param new_layer_name: optional, name of the new layer
    :type new_layer_name: str
    :param name: optional, name of the isochrone, default to current datetime
    :type name: str
    :param style: optional, dictionary with style properties: example {'color': [100, 50, 123], 'transparency': 0.5, 'stroke_width: 3 }
    :type style: dict
    :raises Exception: raise on error
    :rtype: str
    """

    # Additional fields that are not returned by the service as feature attributes
    json_data = json.loads(geojson)

    if name is None:
        name = "Isochrone %s" % QDateTime.currentDateTime().toString(
            Qt.ISODateWithMs)

    metadata = {
        'range_type': json_data['metadata']['query']['range_type'],
        'name': name,
        # 'timestamp': json_data['metadata']['timestamp'],  // Not supported
    }

    for f in json_data['features']:
        f['properties'].update(metadata)

    geojson = json.dumps(json_data)

    fields = QgsJsonUtils.stringToFields(geojson)

    # Patch timestamp type to DateTime // Not supported
    # fields.remove(fields.lookupField('timestamp'))
    #fields.append(QgsField('timestamp', QVariant.DateTime))

    # Create the new layer
    if connection_id is not None:

        def _write_to_ogr(destination_path, new_layer_name, driverName=None):
            """Writes features to new or existing OGR layer"""

            tmp_dir = QTemporaryDir()
            tmp_path = os.path.join(tmp_dir.path(), 'isochrone.json')
            with open(tmp_path, 'w+') as f:
                f.write(geojson)

            tmp_layer = QgsVectorLayer(tmp_path, 'tmp_isochrone', 'ogr')

            if not tmp_layer.isValid():
                raise Exception(
                    _('Cannot create temporary layer for isochrone result.'))

            # Note: shp attribute names are max 10 chars long
            save_options = QgsVectorFileWriter.SaveVectorOptions()
            if driverName is not None:
                save_options.driverName = driverName
            save_options.layerName = new_layer_name
            save_options.fileEncoding = 'utf-8'

            # This is nonsense to me: if the file does not exist the actionOnExistingFile
            # should be ignored instead of raising an error, probable QGIS bug
            if os.path.exists(destination_path):
                # Check if the layer already exists
                layer_exists = QgsVectorFileWriter.targetLayerExists(
                    destination_path, new_layer_name)

                if layer_exists:
                    raise Exception(
                        _('Cannot save isochrone result to destination layer: layer already exists (use "qgis_layer_id" instead)!'
                          ))

                save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer

            error_code, error_message = QgsVectorFileWriter.writeAsVectorFormatV2(
                tmp_layer, destination_path,
                project.qgis_project.transformContext(), save_options)

            if error_code != QgsVectorFileWriter.NoError:
                raise Exception(
                    _('Cannot save isochrone result to destination layer: ') +
                    error_message)

            layer_uri = destination_path

            if driverName != 'ESRI Shapefile':
                layer_uri += '|layername=' + new_layer_name

            provider = 'ogr'
            return layer_uri, provider

        destination_path = None

        if connection_id == '__shapefile__':  # new shapefile
            driverName = 'ESRI Shapefile'
            extension = 'shp'
        elif connection_id == '__spatialite__':  # new sqlite
            driverName = 'SpatiaLite'
            extension = 'sqlite'
        elif connection_id == '__geopackage__':  # new gpkg
            driverName = 'GPKG'
            extension = 'gpkg'
        else:  # Add new table to an existing DB connection

            try:
                connection = get_db_connections(
                    project.qgis_project)[connection_id]
            except:
                raise Exception(_('Wrong connection id.'))

            if connection['provider'] == 'ogr':
                destination_path = connection_id
                driverName = 'GPKG' if destination_path.lower().endswith(
                    '.gpkg') else 'SpatiaLite'
            else:
                driverName = None

        # Create a new file/layer
        if driverName is not None:
            new_layer_name = os.path.basename(new_layer_name)

            if destination_path is None:  # new files!
                destination_path = os.path.join(
                    settings.DATASOURCE_PATH,
                    "{}.{}".format(new_layer_name, extension))
                i = 0
                while os.path.exists(destination_path):
                    i += 1
                    destination_path = os.path.join(
                        settings.DATASOURCE_PATH,
                        "{}_{}.{}".format(new_layer_name, i, extension))

            layer_uri, provider = _write_to_ogr(destination_path,
                                                new_layer_name, driverName)

        # Create a new DB table
        else:
            assert connection['provider'] != 'ogr'
            md = QgsProviderRegistry.instance().providerMetadata(
                connection['provider'])
            if not md:
                raise Exception(
                    _('Error creating destination layer connection.'))
            conn = md.createConnection(connection_id, {})
            try:
                conn.createVectorTable(connection['schema'], new_layer_name,
                                       fields, QgsWkbTypes.Polygon,
                                       QgsCoordinateReferenceSystem(4326),
                                       False, {'geometryColumn': 'geom'})
            except QgsProviderConnectionException as ex:
                raise Exception(
                    _('Error creating destination layer: ') + str(ex))

            uri = QgsDataSourceUri(conn.uri())
            uri.setTable(new_layer_name)
            uri.setSchema(connection['schema'])
            uri.setSrid('4326')
            uri.setGeometryColumn('geom')
            provider = connection['provider']
            layer_uri = uri.uri()

        # Now reload the new layer and add it to the project
        qgis_layer = QgsVectorLayer(layer_uri, new_layer_name, provider)
        if not qgis_layer.isValid():
            raise Exception(
                _('Error creating destination layer: layer is not valid!'))

        qgis_layer_id = qgis_layer.id()

        with QgisProjectFileLocker(project) as project:
            apply_style(qgis_layer, style, True, name)
            project.qgis_project.addMapLayers([qgis_layer])
            root = project.qgis_project.layerTreeRoot()
            if qgis_layer_id not in root.findLayerIds():
                # Append layer at the end
                root.insertLayer(-1, qgis_layer)
            if not project.update_qgis_project():
                raise Exception(
                    _('Error saving the destination layer: could not write project!'
                      ))

        # Retrieve the layer again because saving the project deleted it
        qgis_layer = project.qgis_project.mapLayer(qgis_layer_id)

        # Create Layer object
        instance, created = Layer.objects.get_or_create(
            qgs_layer_id=qgis_layer_id,
            project=project,
            defaults={
                'origname':
                new_layer_name,
                'name':
                new_layer_name,
                'title':
                new_layer_name,
                'is_visible':
                True,
                'layer_type':
                provider,
                'srid':
                4326,
                'datasource':
                layer_uri,
                'geometrytype':
                'Polygon',
                # TODO: make this a property of the Layer object
                'database_columns':
                str([{
                    'name': f.name(),
                    'type': QVariant.typeToName(f.type()).upper(),
                    'label': f.displayName()
                } for f in qgis_layer.fields()]),
            })

        if not created:
            raise Exception(
                _('Error adding destination Layer to the project: layer already exists.'
                  ))

        # for OGR (already filled with features) returns the id of the new layer
        if driverName is not None:
            return qgis_layer_id

    # Append to an existing layer
    qgis_layer = project.qgis_project.mapLayer(qgis_layer_id)
    if qgis_layer is None:
        raise Exception(
            _('Error opening destination layer %s: layer not found in QGIS project!'
              % qgis_layer_id))

    features = QgsJsonUtils.stringToFeatureList(geojson, fields)

    compatible_features = []
    for f in features:
        compatible_features.extend(
            QgsVectorLayerUtils.makeFeatureCompatible(f, qgis_layer))

    if qgis_layer.crs().isValid(
    ) and qgis_layer.crs() != QgsCoordinateReferenceSystem(4326):
        ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem(4326),
                                    qgis_layer.crs(), project.qgis_project)
        for f in compatible_features:
            geom = f.geometry()
            if geom.transform(ct) != QgsGeometry.Success:
                raise Exception(
                    _('Error transforming geometry from 4326 to destination layer CRS.'
                      ))
            f.setGeometry(geom)

    if len(compatible_features) == 0:
        raise Exception(_('No compatible features returned from isochrone.'))

    if not qgis_layer.startEditing():
        raise Exception(_('Destination layer is not editable.'))

    # Add features to the layer
    if not qgis_layer.addFeatures(compatible_features):
        raise Exception(_('Error adding features to the destination layer.'))

    if not qgis_layer.commitChanges():
        raise Exception(
            _('Error committing features to the destination layer.'))

    if style is not None:
        with QgisProjectFileLocker(project) as project:
            apply_style(qgis_layer, style, False, name)
            project.update_qgis_project()

    return qgis_layer_id
Exemple #11
0
    def testTypeValidation(self):
        """Test that incompatible types in attributes raise errors"""

        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        self.assertTrue(vl.isValid())
        invalid = QgsFeature(vl.fields())
        invalid.setAttribute('int', 'A string')
        invalid.setGeometry(QgsGeometry.fromWkt('point(9 45)'))
        self.assertTrue(vl.startEditing())
        # Validation happens on commit
        self.assertTrue(vl.addFeatures([invalid]))
        self.assertFalse(vl.commitChanges())
        self.assertTrue(vl.rollBack())
        self.assertFalse(vl.hasFeatures())

        # Add a valid feature
        valid = QgsFeature(vl.fields())
        valid.setAttribute('int', 123)
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.addFeatures([valid]))
        self.assertTrue(vl.commitChanges())
        self.assertEqual(vl.featureCount(), 1)

        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 123)

        # Add both
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        self.assertEqual(vl.featureCount(), 0)
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.addFeatures([valid, invalid]))
        self.assertFalse(vl.commitChanges())
        self.assertEqual(vl.featureCount(), 2)
        self.assertTrue(vl.rollBack())
        self.assertEqual(vl.featureCount(), 0)

        # Add both swapped
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.addFeatures([invalid, valid]))
        self.assertFalse(vl.commitChanges())
        self.assertEqual(vl.featureCount(), 2)
        self.assertTrue(vl.rollBack())
        self.assertEqual(vl.featureCount(), 0)

        # Change attribute value
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.addFeatures([valid]))
        self.assertTrue(vl.commitChanges())
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.changeAttributeValue(1, 0, 'A string'))
        self.assertFalse(vl.commitChanges())
        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 'A string')
        self.assertTrue(vl.rollBack())

        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 123)

        # Change attribute values
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.addFeatures([valid]))
        self.assertTrue(vl.commitChanges())
        self.assertTrue(vl.startEditing())
        self.assertTrue(vl.changeAttributeValues(1, {0: 'A string'}))
        self.assertFalse(vl.commitChanges())
        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 'A string')
        self.assertTrue(vl.rollBack())

        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 123)

        ##############################################
        # Test direct data provider calls

        # No rollback (old behavior)
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        dp = vl.dataProvider()
        self.assertFalse(dp.addFeatures([valid, invalid])[0])
        self.assertEqual([f.attributes() for f in dp.getFeatures()], [[123]])
        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 123)

        # Roll back
        vl = QgsVectorLayer(
            'Point?crs=epsg:4326&field=int:integer',
            'test', 'memory')
        dp = vl.dataProvider()
        self.assertFalse(dp.addFeatures([valid, invalid], QgsFeatureSink.RollBackOnErrors)[0])
        self.assertFalse(dp.hasFeatures())

        # Expected behavior for changeAttributeValues is to always roll back
        self.assertTrue(dp.addFeatures([valid])[0])
        self.assertFalse(dp.changeAttributeValues({1: {0: 'A string'}}))
        f = vl.getFeature(1)
        self.assertEqual(f.attribute('int'), 123)
Exemple #12
0
    def doGeocode(self):

        if self.dockwidget.apiKeyEdit.text() == '':
            self.iface.messageBar().pushMessage("Erro",
                                                u"API key não definida!",
                                                level=Qgis.Critical,
                                                duration=5)
            return

        progressDialog = QProgressDialog()
        progressDialog.setMinimum(0)
        progressDialog.setCancelButtonText('Cancelar')
        progressDialog.setWindowModality(Qt.WindowModal)
        progressDialog.setMinimumWidth(300)
        progressDialog.show()

        inputFile = self.dockwidget.inputFileWidget.filePath()
        errorFile = self.dockwidget.errorOutputFileWidget.filePath()

        addrPartsIdxs = []
        inputAddrList = []

        if self.dockwidget.toolBox.currentIndex() == 0:
            addrPartsIdxs.append(self.dockwidget.addressCombo.currentIndex())
            if self.dockwidget.neighborhoodCombo.currentIndex(
            ) != self.dockwidget.neighborhoodCombo.count() - 1:
                addrPartsIdxs.append(
                    self.dockwidget.neighborhoodCombo.currentIndex())
            if self.dockwidget.cityCombo.currentIndex(
            ) != self.dockwidget.cityCombo.count() - 1:
                addrPartsIdxs.append(self.dockwidget.cityCombo.currentIndex())
            if self.dockwidget.stateCombo.currentIndex(
            ) != self.dockwidget.stateCombo.count() - 1:
                addrPartsIdxs.append(self.dockwidget.stateCombo.currentIndex())
            if self.dockwidget.countryCombo.currentIndex(
            ) != self.dockwidget.countryCombo.count() - 1:
                addrPartsIdxs.append(
                    self.dockwidget.countryCombo.currentIndex())

        else:
            addrPartsIdxs.append(
                self.dockwidget.fullAddressCombo.currentIndex())

        with open(inputFile, 'r') as iFile:
            csv_reader = csv.reader(iFile, delimiter=';')
            lineCount = 0

            for row in csv_reader:
                if lineCount == 0:
                    lineCount += 1
                else:
                    addr = ', '.join(row[i] for i in addrPartsIdxs)
                    addr = '"' + addr + '"'
                    inputAddrList.append(addr)

        # geolocator = Bing(api_key=apiKey)
        geolocator = self.getGeocoder()

        temp = QgsVectorLayer("Point?crs=epsg:4326", "Geocoding output",
                              "memory")
        temp_data = temp.dataProvider()
        temp.startEditing()

        fl = open(inputFile, 'r')
        fo = open(errorFile, 'w')

        header = fl.readline()
        header = header.replace('\n', '')
        header = header.split(';')
        temp_data.addAttributes(
            [QgsField(header[i], QVariant.String) for i in range(len(header))])
        temp_data.addAttributes([QgsField('addr_geocoded', QVariant.String)])
        temp.updateFields()

        fo.writelines(';'.join(header))

        totalAddresses = len(inputAddrList)
        progressDialog.setWindowTitle(u'Geocodificando ' +
                                      str(totalAddresses) + u' endereços...')
        progressDialog.setMaximum(totalAddresses)

        counter = 2
        for a in inputAddrList:
            if progressDialog.wasCanceled():
                self.iface.messageBar().clearWidgets()
                self.iface.messageBar().pushMessage(
                    u"Geocodificação cancelada",
                    u'Salvos ' + str(counter - 2) + u' endereços.',
                    level=Qgis.Info,
                    duration=5)
                break
            address, (latitude, longitude) = geolocator.reverse(a)
            print(counter, address, latitude, longitude)
            counter += 1

            feat = QgsFeature()
            feat.setGeometry(
                QgsGeometry.fromPointXY(QgsPointXY(longitude, latitude)))
            attributes = fl.readline().replace('\n', '').split(';')
            attributes.append(address)
            feat.setAttributes(attributes)
            temp.addFeatures([feat])
            temp.updateExtents()

            # progress.setValue(counter-2)
            progressDialog.setValue(counter - 2)
            self.iface.mainWindow().repaint()

        temp.commitChanges()

        QgsProject.instance().addMapLayer(temp)

        self.iface.messageBar().clearWidgets()
        self.iface.messageBar().pushMessage(
            u"Successo",
            u'Geocodificados ' + str(totalAddresses) + u' endereços!',
            level=Qgis.Success,
            duration=5)
def copyLayerToMemory(layer, layer_name, bOnlySelectedFeat=False, bAddUFI=True):
    """ Make a copy of an existing layer as a Memory layer

    Args:
        layer (): The layer to copy to memory
        layer_name (): The name for the memory layer
        bOnlySelectedFeat (): Only copy selected features
        bAddUFI (): Add a unique identifier to the memory layer

    Returns:
        Qgis Memory layer

    """

    # Create an in memory layer and add UFI
    mem_layer = QgsVectorLayer("{}?crs={}&index=yes".format(getGeometryTypeAsString(layer.wkbType()),
                                                           layer.crs().authid()), layer_name,
                              "memory")
    if not mem_layer.isValid():
        raise Exception('Could not create memory Layer called {}'
                        ' from layer {}'.format(layer_name, layer.name()))

    mem_data_prov = mem_layer.dataProvider()

    # Add the fields
    b_calc_ufi = False
    attr = []

    if layer.fields().lookupField("FID") == -1 and bAddUFI:
        b_calc_ufi = True
        attr = [QgsField('FID', QVariant.Int)]

    invalid_fields = []
    for eaFld in layer.dataProvider().fields():
        old_name = eaFld.name()
        new_name = re.sub('[^A-Za-z0-9_-]+', '', old_name)[:10]

        if old_name != new_name:
            invalid_fields.append('   ' + old_name + '   to   ' + new_name)
            eaFld.setName(new_name)  # update the name
        attr.append(eaFld)

    if len(invalid_fields) > 0:
        LOGGER.warning('{} fieldnames are not ESRI Compatible. Renaming...'.format(len(invalid_fields)))

        for i, ea in enumerate(invalid_fields):
            LOGGER.warning(ea)

    # Copy all existing fields
    mem_data_prov.addAttributes(attr)

    # tell the vector layer to fetch changes from the provider
    mem_layer.updateFields()

    # start editing and copy all features and attributes
    mem_layer.startEditing()
    sel_feat_ids = []
    if bOnlySelectedFeat and layer.selectedFeatureCount() > 0:
        # Get a list of selected features.....
        sel_feat_ids = [f.id() for f in layer.selectedFeatures()]

    features = layer.getFeatures()
    feat = QgsFeature()

    id = 0
    for f in features:
        # Check the list of features against the selected list.
        # This will maintain the order of features when saved to shapefile.
        if bOnlySelectedFeat and len(sel_feat_ids) > 0:
            # Skip features which aren't in the selection
            if not f.id() in sel_feat_ids:
                continue

        feat.setGeometry(f.geometry())
        f_attr = []

        if b_calc_ufi:
            f_attr += [id]

        # The order of fields hasn't changed so just add attributes.
        f_attr += f.attributes()
        feat.setAttributes(f_attr)
        mem_layer.addFeatures([feat])
        id += 1

    mem_layer.updateExtents()
    mem_layer.commitChanges()

    return mem_layer
Exemple #14
0
    def eliminate(self, inLayer, boundary, progressBar, outFileName):
        # keep references to the features to eliminate
        fidsToEliminate = inLayer.selectedFeaturesIds()

        if outFileName: # user wants a new shape file to be created as result
            provider = inLayer.dataProvider()
            error = QgsVectorFileWriter.writeAsVectorFormat(inLayer, outFileName, provider.encoding(), inLayer.crs(), "ESRI Shapefile")

            if error != QgsVectorFileWriter.NoError:
                QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Error creating output file"))
                return None

            outLayer = QgsVectorLayer(outFileName, QFileInfo(outFileName).completeBaseName(), "ogr")

        else:
            QMessageBox.information(self, self.tr("Eliminate"), self.tr("Please specify output shapefile"))
            return None

        # delete features to be eliminated in outLayer
        outLayer.setSelectedFeatures(fidsToEliminate)
        outLayer.startEditing()

        if outLayer.deleteSelectedFeatures():
            if self.saveChanges(outLayer):
                outLayer.startEditing()
        else:
            QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not delete features"))
            return None

        # ANALYZE
        start = 20.00
        progressBar.setValue(start)
        add = 80.00 / len(fidsToEliminate)

        lastLen = 0

        # we go through the list and see if we find any polygons we can merge the selected with
        # if we have no success with some we merge and then restart the whole story
        while (lastLen != inLayer.selectedFeatureCount()):  # check if we made any progress
            lastLen = inLayer.selectedFeatureCount()
            fidsToDeselect = []

            #iterate over the polygons to eliminate
            for fid2Eliminate in inLayer.selectedFeaturesIds():
                feat = QgsFeature()

                if inLayer.getFeatures( QgsFeatureRequest().setFilterFid( fid2Eliminate ).setSubsetOfAttributes([]) ).nextFeature( feat ):
                    geom2Eliminate = feat.geometry()
                    bbox = geom2Eliminate.boundingBox()
                    fit = outLayer.getFeatures( QgsFeatureRequest().setFilterRect( bbox ) )
                    mergeWithFid = None
                    mergeWithGeom = None
                    max = 0

                    selFeat = QgsFeature()
                    while fit.nextFeature(selFeat):
                            selGeom = selFeat.geometry()

                            if geom2Eliminate.intersects(selGeom): # we have a candidate
                                iGeom = geom2Eliminate.intersection(selGeom)

                                if boundary:
                                    selValue = iGeom.length()
                                else:
                                    # we need a common boundary
                                    if 0 < iGeom.length():
                                        selValue = selGeom.area()
                                    else:
                                        selValue = 0

                                if selValue > max:
                                    max = selValue
                                    mergeWithFid = selFeat.id()
                                    mergeWithGeom = QgsGeometry(selGeom) # deep copy of the geometry

                    if mergeWithFid is not None:  # a successful candidate
                        newGeom = mergeWithGeom.combine(geom2Eliminate)

                        if outLayer.changeGeometry(mergeWithFid, newGeom):
                            # write change back to disc
                            if self.saveChanges(outLayer):
                                outLayer.startEditing()
                            else:
                                return None

                            # mark feature as eliminated in inLayer
                            fidsToDeselect.append(fid2Eliminate)
                        else:
                            QMessageBox.warning(
                                self,
                                self.tr("Eliminate"),
                                self.tr("Could not replace geometry of feature with id %s") % (mergeWithFid))
                            return None

                        start = start + add
                        progressBar.setValue(start)
            # end for fid2Eliminate

            # deselect features that are already eliminated in inLayer
            inLayer.deselect(fidsToDeselect)

        #end while

        if inLayer.selectedFeatureCount() > 0:
            # copy all features that could not be eliminated to outLayer
            if outLayer.addFeatures(inLayer.selectedFeatures()):
                # inform user
                fidList = ""

                for fid in inLayer.selectedFeaturesIds():
                    if not fidList == "":
                        fidList += ", "

                    fidList += unicode(fid)

                QMessageBox.information(
                    self,
                    self.tr("Eliminate"),
                    self.tr("Could not eliminate features with these ids:\n%s") % (fidList))
            else:
                QMessageBox.warning(self, self.tr("Eliminate"), self.tr("Could not add features"))

        # stop editing outLayer and commit any pending changes
        if not self.saveChanges(outLayer):
            return None

        if outFileName:
            if self.addToCanvasCheck.isChecked():
                ftools_utils.addShapeToCanvas(outFileName)
            else:
                QMessageBox.information(
                    self,
                    self.tr("Eliminate"),
                    self.tr("Created output shapefile:\n%s") % (outFileName))

        self.iface.mapCanvas().refresh()
Exemple #15
0
def geojson_to_memory(path, name):
    my_WkbType = {
        'Unknown': 0,
         'Point': 1,
         'LineString': 2,
         'Polygon': 3,
         'MultiPoint': 4,
         'MultiLineString': 5,
         'MultiPolygon': 6,
         'NoGeometry': 7,
         'Point25D': 8,
         'LineString25D': 9,
         'Polygon25D': 10,
         'MultiPoint25D': 11,
         'MultiLineString25D': 12,
         'MultiPolygon25D': 13}

    # for i,j in my_WkbType.items():
    #     #print i + " : " + str(j)
    my_rev_WkbType = {v: k for k, v in my_WkbType.items()}
    # for i,j in my_WkbType.items():
    #     #print i + " : " + str(j)
    #print 'Path to geojson: %s' % path
    input_layer=QgsVectorLayer(path,
                               "input_layer", "ogr")
    print 'path: %s' % path
    print 'input_layer %s' % input_layer.name()
    dp = input_layer.dataProvider()
    QGisWKBType = dp.geometryType()
    #print 'QGisWKBType: ' + str(QGisWKBType)
    EPSG_code = int(dp.crs().authid().split(":")[1])
    print str(EPSG_code)

    destination_layer = QgsVectorLayer(
        my_rev_WkbType[QGisWKBType] + '?crs=epsg:' + str(EPSG_code) + '&index=yes',
        name,
        'memory')
    destination_layer_data_provider = destination_layer.dataProvider()

    input_layer_attrib_names = input_layer.dataProvider().fields()

    oldattributeList = input_layer.dataProvider().fields().toList()
    newattributeList = []
    for attrib in oldattributeList:
        if destination_layer.fieldNameIndex(attrib.name()) == -1:
            newattributeList.append(QgsField(attrib.name(), attrib.type()))

    destination_layer_data_provider.addAttributes(newattributeList)
    destination_layer.updateFields()

    destination_layer.startEditing()
    # cfeature = QgsFeature()
    cfeatures = []
    xfeatures = input_layer.getFeatures()
    for xfeature in xfeatures:
        xgeometry = xfeature.geometry()
        cfeature_Attributes = []
        cfeature_Attributes.extend(xfeature.attributes())
        cfeature = QgsFeature()
        cfeature.setGeometry(xgeometry)
        cfeature.setAttributes(cfeature_Attributes)
        cfeatures.append(cfeature)

    destination_layer.addFeatures(cfeatures)
    destination_layer.commitChanges()
    return destination_layer
        def _test(autoTransaction):
            """Test buffer methods within and without transactions

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

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

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

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

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

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

            self.assertTrue(layer_a.isValid())

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

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

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

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

            _check_feature('POINT (7 45)')

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

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

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

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

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

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

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

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

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

            layer_a.undoStack().undo()

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

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

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

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

            self.assertTrue(layer_a.commitChanges())

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            self.assertTrue(layer_a.commitChanges())

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                self.assertEqual(buffer.changedAttributeValues(),
                                 {f.id(): {
                                      2: 0
                                  }})
                layer_a.undoStack().undo()
                buffer = layer_a.editBuffer()
                self.assertEqual(buffer.changedAttributeValues(), {})
                f = next(layer_a.getFeatures())
                self.assertEqual(f.attribute(2), None)
Exemple #17
0
    def testWriteShapefileWithSingleConversion(self):
        """Check writing geometries from a POLYGON ESRI shapefile does not
        convert to multi when "forceSinglePartGeometryType" options is TRUE
        also checks failing cases.

        OGR provider always report MULTI for POLYGON and LINESTRING, but if we set
        the import option "forceSinglePartGeometryType" the writer must respect the
        actual single-part type if the features in the data provider are actually single
        and not multi.
        """

        ml = QgsVectorLayer(('Polygon?crs=epsg:4326&field=id:int'), 'test',
                            'memory')

        provider = ml.dataProvider()
        ft = QgsFeature()
        ft.setGeometry(
            QgsGeometry.fromWkt('Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))'))
        ft.setAttributes([1])
        res, features = provider.addFeatures([ft])

        dest_file_name = os.path.join(self.basetestpath, 'multipart.shp')
        write_result, error_message = QgsVectorLayerExporter.exportLayer(
            ml, dest_file_name, 'ogr', ml.crs(), False,
            {"driverName": "ESRI Shapefile"})
        self.assertEqual(write_result, QgsVectorLayerExporter.NoError,
                         error_message)

        # Open the newly created layer
        shapefile_layer = QgsVectorLayer(dest_file_name)

        dest_singlepart_file_name = os.path.join(self.basetestpath,
                                                 'singlepart.gpkg')
        write_result, error_message = QgsVectorLayerExporter.exportLayer(
            shapefile_layer, dest_singlepart_file_name, 'ogr',
            shapefile_layer.crs(), False, {
                "forceSinglePartGeometryType": True,
                "driverName": "GPKG",
            })
        self.assertEqual(write_result, QgsVectorLayerExporter.NoError,
                         error_message)

        # Load result layer and check that it's NOT MULTI
        single_layer = QgsVectorLayer(dest_singlepart_file_name)
        self.assertTrue(single_layer.isValid())
        self.assertTrue(QgsWkbTypes.isSingleType(single_layer.wkbType()))

        # Now save the shapfile layer into a gpkg with no force options
        dest_multipart_file_name = os.path.join(self.basetestpath,
                                                'multipart.gpkg')
        write_result, error_message = QgsVectorLayerExporter.exportLayer(
            shapefile_layer, dest_multipart_file_name, 'ogr',
            shapefile_layer.crs(), False, {
                "forceSinglePartGeometryType": False,
                "driverName": "GPKG",
            })
        self.assertEqual(write_result, QgsVectorLayerExporter.NoError,
                         error_message)
        # Load result layer and check that it's MULTI
        multi_layer = QgsVectorLayer(dest_multipart_file_name)
        self.assertTrue(multi_layer.isValid())
        self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType()))

        # Failing case: add a real multi to the shapefile and try to force to single
        self.assertTrue(shapefile_layer.startEditing())
        ft = QgsFeature()
        ft.setGeometry(
            QgsGeometry.fromWkt(
                'MultiPolygon (((0 0, 0 1, 1 1, 1 0, 0 0)), ((-10 -10,-10 -9,-9 -9,-10 -10)))'
            ))
        ft.setAttributes([2])
        self.assertTrue(shapefile_layer.addFeatures([ft]))
        self.assertTrue(shapefile_layer.commitChanges())

        dest_multipart_failure_file_name = os.path.join(
            self.basetestpath, 'multipart_failure.gpkg')
        write_result, error_message = QgsVectorLayerExporter.exportLayer(
            shapefile_layer, dest_multipart_failure_file_name, 'ogr',
            shapefile_layer.crs(), False, {
                "forceSinglePartGeometryType": True,
                "driverName": "GPKG",
            })
        self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType()))
        self.assertEqual(
            write_result, QgsVectorLayerExporter.ErrFeatureWriteFailed,
            "Failed to transform a feature with ID '1' to single part. Writing stopped."
        )
class TestLayerDependencies(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        """Run before all tests"""

        # create a temp spatialite db with a trigger
        fo = tempfile.NamedTemporaryFile()
        fn = fo.name
        fo.close()
        cls.fn = fn
        con = spatialite_connect(fn)
        cur = con.cursor()
        cur.execute("SELECT InitSpatialMetadata(1)")
        cur.execute("create table node(id integer primary key autoincrement);")
        cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');")
        cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);")
        cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');")
        cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;")
        cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));")
        cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));")
        cur.execute("create table node2(id integer primary key autoincrement);")
        cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');")
        cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;")
        con.commit()
        con.close()

        cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite")
        assert (cls.pointsLayer.isValid())
        cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite")
        assert (cls.linesLayer.isValid())
        cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite")
        assert (cls.pointsLayer2.isValid())
        QgsMapLayerRegistry.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2])

        # save the project file
        fo = tempfile.NamedTemporaryFile()
        fn = fo.name
        fo.close()
        cls.projectFile = fn
        QgsProject.instance().setFileName(cls.projectFile)
        QgsProject.instance().write()

    @classmethod
    def tearDownClass(cls):
        """Run after all tests"""
        pass

    def setUp(self):
        """Run before each test."""
        pass

    def tearDown(self):
        """Run after each test."""
        pass

    def test_resetSnappingIndex(self):
        self.pointsLayer.setDependencies([])
        self.linesLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])

        ms = QgsMapSettings()
        ms.setOutputSize(QSize(100, 100))
        ms.setExtent(QgsRectangle(0, 0, 1, 1))
        self.assertTrue(ms.hasValidSettings())

        u = QgsSnappingUtils()
        u.setMapSettings(ms)
        cfg = u.config()
        cfg.setMode(QgsSnappingConfig.AdvancedConfiguration)
        cfg.setIndividualLayerSettings(self.pointsLayer,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)

        m = u.snapToMap(QPoint(95, 100))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPoint(1, 0))

        f = QgsFeature(self.linesLayer.fields())
        f.setFeatureId(1)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()

        l1 = len([f for f in self.pointsLayer.getFeatures()])
        self.assertEqual(l1, 4)
        m = u.snapToMap(QPoint(95, 0))
        # snapping not updated
        self.pointsLayer.setDependencies([])
        self.assertEqual(m.isValid(), False)

        # set layer dependencies
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setFeatureId(2)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the snapped point is ok
        m = u.snapToMap(QPoint(45, 50))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPoint(0.5, 0.5))
        self.pointsLayer.setDependencies([])

        # test chained layer dependencies A -> B -> C
        cfg.setIndividualLayerSettings(self.pointsLayer2,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setFeatureId(3)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the second snapped point is ok
        m = u.snapToMap(QPoint(75, 100 - 80))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPoint(0.7, 0.8))
        self.pointsLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])

    def test_cycleDetection(self):
        self.assertTrue(self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]))
        self.assertFalse(self.linesLayer.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]))
        self.pointsLayer.setDependencies([])
        self.linesLayer.setDependencies([])

    def test_layerDefinitionRewriteId(self):
        tmpfile = os.path.join(tempfile.tempdir, "test.qlr")

        ltr = QgsProject.instance().layerTreeRoot()

        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])

        QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr])

        grp = ltr.addGroup("imported")
        QgsLayerDefinition.loadLayerDefinition(tmpfile, grp)

        newPointsLayer = None
        newLinesLayer = None
        for l in grp.findLayers():
            if l.layerId().startswith('points'):
                newPointsLayer = l.layer()
            elif l.layerId().startswith('lines'):
                newLinesLayer = l.layer()
        self.assertFalse(newPointsLayer is None)
        self.assertFalse(newLinesLayer is None)
        self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()])

        self.pointsLayer.setDependencies([])

    def test_signalConnection(self):
        # remove all layers
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        # set dependencies and add back layers
        self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite")
        assert (self.pointsLayer.isValid())
        self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite")
        assert (self.linesLayer.isValid())
        self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite")
        assert (self.pointsLayer2.isValid())
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])
        # this should update connections between layers
        QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer])
        QgsMapLayerRegistry.instance().addMapLayers([self.linesLayer])
        QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer2])

        ms = QgsMapSettings()
        ms.setOutputSize(QSize(100, 100))
        ms.setExtent(QgsRectangle(0, 0, 1, 1))
        self.assertTrue(ms.hasValidSettings())

        u = QgsSnappingUtils()
        u.setMapSettings(ms)
        cfg = u.config()
        cfg.setMode(QgsSnappingConfig.AdvancedConfiguration)
        cfg.setIndividualLayerSettings(self.pointsLayer,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        cfg.setIndividualLayerSettings(self.pointsLayer2,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setFeatureId(4)
        geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the second snapped point is ok
        m = u.snapToMap(QPoint(75, 100 - 0))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPoint(0.8, 0.0))

        self.pointsLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])
    def testCreateFeature(self):
        """ test creating a feature respecting defaults and constraints """
        layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double",
                               "addfeat", "memory")
        # add a bunch of features
        f = QgsFeature()
        f.setAttributes(["test", 123, 1.0])
        f1 = QgsFeature(2)
        f1.setAttributes(["test_1", 124, 1.1])
        f2 = QgsFeature(3)
        f2.setAttributes(["test_2", 125, 2.4])
        f3 = QgsFeature(4)
        f3.setAttributes(["test_3", 126, 1.7])
        f4 = QgsFeature(5)
        f4.setAttributes(["superpig", 127, 0.8])
        self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4]))

        # no layer
        self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid())

        # basic tests
        f = QgsVectorLayerUtils.createFeature(layer)
        self.assertTrue(f.isValid())
        self.assertEqual(f.fields(), layer.fields())
        self.assertFalse(f.hasGeometry())
        self.assertEqual(f.attributes(), [NULL, NULL, NULL])

        # set geometry
        g = QgsGeometry.fromPointXY(QgsPointXY(100, 200))
        f = QgsVectorLayerUtils.createFeature(layer, g)
        self.assertTrue(f.hasGeometry())
        self.assertEqual(f.geometry().asWkt(), g.asWkt())

        # using attribute map
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0})
        self.assertEqual(f.attributes(), ['a', NULL, 6.0])

        # layer with default value expression
        layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4'))
        f = QgsVectorLayerUtils.createFeature(layer)
        self.assertEqual(f.attributes(), [NULL, NULL, 12])
        # we do not expect the default value expression to take precedence over the attribute map
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0})
        self.assertEqual(f.attributes(), ['a', NULL, 6.0])
        # layer with default value expression based on geometry
        layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x'))
        f = QgsVectorLayerUtils.createFeature(layer, g)
        #adjusted so that input value and output feature are the same
        self.assertEqual(f.attributes(), [NULL, NULL, 300.0])
        layer.setDefaultValueDefinition(2, QgsDefaultValue(None))

        # test with violated unique constraints
        layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique)
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123})
        # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128
        self.assertEqual(f.attributes(), ['test_1', 128, NULL])
        layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique)
        # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123})
        self.assertEqual(f.attributes(), ['test_4', 128, NULL])

        # test with violated unique constraints and default value expression providing unique value
        layer.setDefaultValueDefinition(1, QgsDefaultValue('130'))
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123})
        # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value
        self.assertEqual(f.attributes(), ['test_4', 130, NULL])
        # fallback: test with violated unique constraints and default value expression providing already existing value
        # add the feature with the default value:
        self.assertTrue(layer.dataProvider().addFeatures([f]))
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123})
        # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value
        # and since the default value providing an already existing value (130) it generates a unique value (next int: 131)
        self.assertEqual(f.attributes(), ['test_5', 131, NULL])
        layer.setDefaultValueDefinition(1, QgsDefaultValue(None))

        # test with manually correct unique constraint
        f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 132})
        self.assertEqual(f.attributes(), ['test_5', 132, NULL])

        """ test creating a feature respecting unique values of postgres provider """
        layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double",
                               "addfeat", "memory")

        # init connection string
        dbconn = 'dbname=\'qgis_test\''
        if 'QGIS_PGTEST_DB' in os.environ:
            dbconn = os.environ['QGIS_PGTEST_DB']

        # create a vector layer
        pg_layer = QgsVectorLayer('{} table="qgis_test"."authors" sql='.format(dbconn), "authors", "postgres")
        self.assertTrue(pg_layer.isValid())
        # check the default clause
        default_clause = 'nextval(\'qgis_test.authors_pk_seq\'::regclass)'
        self.assertEqual(pg_layer.dataProvider().defaultValueClause(0), default_clause)

        # though default_clause is after the first create not unique (until save), it should fill up all the features with it
        pg_layer.startEditing()
        f = QgsVectorLayerUtils.createFeature(pg_layer)
        self.assertEqual(f.attributes(), [default_clause, NULL])
        self.assertTrue(pg_layer.addFeatures([f]))
        self.assertTrue(QgsVectorLayerUtils.valueExists(pg_layer, 0, default_clause))
        f = QgsVectorLayerUtils.createFeature(pg_layer)
        self.assertEqual(f.attributes(), [default_clause, NULL])
        self.assertTrue(pg_layer.addFeatures([f]))
        f = QgsVectorLayerUtils.createFeature(pg_layer)
        self.assertEqual(f.attributes(), [default_clause, NULL])
        self.assertTrue(pg_layer.addFeatures([f]))
        # if a unique value is passed, use it
        f = QgsVectorLayerUtils.createFeature(pg_layer, attributes={0: 40, 1: NULL})
        self.assertEqual(f.attributes(), [40, NULL])
        # and if a default value is configured use it as well
        pg_layer.setDefaultValueDefinition(0, QgsDefaultValue('11*4'))
        f = QgsVectorLayerUtils.createFeature(pg_layer)
        self.assertEqual(f.attributes(), [44, NULL])
        pg_layer.rollBack()
Exemple #20
0
    def test_invalidGeometryFilter(self):
        layer = QgsVectorLayer(
            "Polygon?field=x:string",
            "joinlayer", "memory")

        # add some features, one has invalid geometry
        pr = layer.dataProvider()
        f1 = QgsFeature(1)
        f1.setAttributes(["a"])
        f1.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid
        f2 = QgsFeature(2)
        f2.setAttributes(["b"])
        f2.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))  # invalid
        f3 = QgsFeature(3)
        f3.setAttributes(["c"])
        f3.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))'))  # valid
        self.assertTrue(pr.addFeatures([f1, f2, f3]))

        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(res, ['a', 'b', 'c'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(res, ['a', 'c'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a'])

        # with callback
        self.callback_feature_val = None

        def callback(feature):
            self.callback_feature_val = feature['x']

        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(
                   QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(callback))]
        self.assertEqual(res, ['a'])
        self.assertEqual(self.callback_feature_val, 'b')
        # clear callback
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(
                   QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(None))]
        self.assertEqual(res, ['a'])

        # check with filter fids
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(res, ['b'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(res, [])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, [])

        f4 = QgsFeature(4)
        f4.setAttributes(["d"])
        f4.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))  # invalid

        # check with added features
        layer.startEditing()
        self.assertTrue(layer.addFeatures([f4]))
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(set(res), {'a', 'b', 'c', 'd'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(set(res), {'a', 'c'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a'])

        # check with features with changed geometry
        layer.rollBack()
        layer.startEditing()
        layer.changeGeometry(2, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))'))  # valid
        layer.changeGeometry(3, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))# invalid
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(set(res), {'a', 'b', 'c'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(set(res), {'a', 'b'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a', 'b'])
        layer.rollBack()
    def testFieldsWithSpecialCharacters(self):
        ml = QgsVectorLayer("Point?srid=EPSG:4326&field=123:int",
                            "mem_with_nontext_fieldnames", "memory")
        self.assertEqual(ml.isValid(), True)
        QgsProject.instance().addMapLayer(ml)

        ml.startEditing()
        self.assertTrue(ml.addAttribute(QgsField('abc:123', QVariant.String)))
        self.assertTrue(ml.addAttribute(QgsField(
            'map', QVariant.String)))  # matches QGIS expression function name
        f1 = QgsFeature(ml.fields())
        f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)'))
        f1.setAttributes([1, 'a', 'b'])
        f2 = QgsFeature(ml.fields())
        f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)'))
        f2.setAttributes([2, 'c', 'd'])
        ml.addFeatures([f1, f2])
        ml.commitChanges()

        vl = QgsVectorLayer("?query=select * from mem_with_nontext_fieldnames",
                            "vl", "virtual")
        self.assertEqual(vl.isValid(), True)
        self.assertEqual(vl.fields().at(0).name(), '123')
        self.assertEqual(vl.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl.featureCount(), 2)

        features = [
            f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression(
                '"abc:123"=\'c\''))
        ]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [2, 'c', 'd'])

        features = [
            f for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression(
                '"map"=\'b\''))
        ]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [1, 'a', 'b'])

        vl2 = QgsVectorLayer(
            "?query=select * from mem_with_nontext_fieldnames where \"abc:123\"='c'",
            "vl", "virtual")
        self.assertEqual(vl2.isValid(), True)
        self.assertEqual(vl2.fields().at(0).name(), '123')
        self.assertEqual(vl2.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl2.featureCount(), 1)

        features = [f for f in vl2.getFeatures()]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [2, 'c', 'd'])

        vl3 = QgsVectorLayer(
            "?query=select * from mem_with_nontext_fieldnames where \"map\"='b'",
            "vl", "virtual")
        self.assertEqual(vl3.isValid(), True)
        self.assertEqual(vl3.fields().at(0).name(), '123')
        self.assertEqual(vl3.fields().at(1).name(), 'abc:123')

        self.assertEqual(vl3.featureCount(), 1)

        features = [f for f in vl3.getFeatures()]
        self.assertEqual(len(features), 1)
        self.assertEqual(features[0].attributes(), [1, 'a', 'b'])

        QgsProject.instance().removeMapLayer(ml)
    def test_invalidGeometryFilter(self):
        layer = QgsVectorLayer(
            "Polygon?field=x:string",
            "joinlayer", "memory")

        # add some features, one has invalid geometry
        pr = layer.dataProvider()
        f1 = QgsFeature(1)
        f1.setAttributes(["a"])
        f1.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid
        f2 = QgsFeature(2)
        f2.setAttributes(["b"])
        f2.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))  # invalid
        f3 = QgsFeature(3)
        f3.setAttributes(["c"])
        f3.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))'))  # valid
        self.assertTrue(pr.addFeatures([f1, f2, f3]))

        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(res, ['a', 'b', 'c'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(res, ['a', 'c'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a'])

        # with callback
        self.callback_feature_val = None

        def callback(feature):
            self.callback_feature_val = feature['x']

        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(
                   QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(callback))]
        self.assertEqual(res, ['a'])
        self.assertEqual(self.callback_feature_val, 'b')
        # clear callback
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(
                   QgsFeatureRequest.GeometryAbortOnInvalid).setInvalidGeometryCallback(None))]
        self.assertEqual(res, ['a'])

        # check with filter fids
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(res, ['b'])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(res, [])
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setFilterFid(f2.id()).setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, [])

        f4 = QgsFeature(4)
        f4.setAttributes(["d"])
        f4.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))  # invalid

        # check with added features
        layer.startEditing()
        self.assertTrue(layer.addFeatures([f4]))
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(set(res), {'a', 'b', 'c', 'd'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(set(res), {'a', 'c'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a'])

        # check with features with changed geometry
        layer.rollBack()
        layer.startEditing()
        layer.changeGeometry(2, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))'))  # valid
        layer.changeGeometry(3, QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))'))# invalid
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
        self.assertEqual(set(res), {'a', 'b', 'c'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
        self.assertEqual(set(res), {'a', 'b'})
        res = [f['x'] for f in
               layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
        self.assertEqual(res, ['a', 'b'])
        layer.rollBack()
    def 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()
Exemple #24
0
class TestLayerDependencies(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        """Run before all tests"""

        # create a temp SpatiaLite db with a trigger
        fo = tempfile.NamedTemporaryFile()
        fn = fo.name
        fo.close()
        cls.fn = fn
        con = spatialite_connect(fn)
        cur = con.cursor()
        cur.execute("SELECT InitSpatialMetadata(1)")
        cur.execute("create table node(id integer primary key autoincrement);")
        cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');")
        cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);")
        cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');")
        cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;")
        cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));")
        cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));")
        cur.execute("create table node2(id integer primary key autoincrement);")
        cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');")
        cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;")
        con.commit()
        con.close()

        cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite")
        assert (cls.pointsLayer.isValid())
        cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite")
        assert (cls.linesLayer.isValid())
        cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite")
        assert (cls.pointsLayer2.isValid())
        QgsProject.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2])

        # save the project file
        fo = tempfile.NamedTemporaryFile()
        fn = fo.name
        fo.close()
        cls.projectFile = fn
        QgsProject.instance().setFileName(cls.projectFile)
        QgsProject.instance().write()

    @classmethod
    def tearDownClass(cls):
        """Run after all tests"""
        pass

    def setUp(self):
        """Run before each test."""
        pass

    def tearDown(self):
        """Run after each test."""
        pass

    def test_resetSnappingIndex(self):
        self.pointsLayer.setDependencies([])
        self.linesLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])

        ms = QgsMapSettings()
        ms.setOutputSize(QSize(100, 100))
        ms.setExtent(QgsRectangle(0, 0, 1, 1))
        self.assertTrue(ms.hasValidSettings())

        u = QgsSnappingUtils()
        u.setMapSettings(ms)
        cfg = u.config()
        cfg.setEnabled(True)
        cfg.setMode(QgsSnappingConfig.AdvancedConfiguration)
        cfg.setIndividualLayerSettings(self.pointsLayer,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)

        m = u.snapToMap(QPoint(95, 100))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPointXY(1, 0))

        f = QgsFeature(self.linesLayer.fields())
        f.setId(1)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()

        l1 = len([f for f in self.pointsLayer.getFeatures()])
        self.assertEqual(l1, 4)
        m = u.snapToMap(QPoint(95, 0))
        # snapping not updated
        self.pointsLayer.setDependencies([])
        self.assertEqual(m.isValid(), False)

        # set layer dependencies
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setId(2)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the snapped point is OK
        m = u.snapToMap(QPoint(45, 50))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPointXY(0.5, 0.5))
        self.pointsLayer.setDependencies([])

        # test chained layer dependencies A -> B -> C
        cfg.setIndividualLayerSettings(self.pointsLayer2,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setId(3)
        geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the second snapped point is OK
        m = u.snapToMap(QPoint(75, 100 - 80))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPointXY(0.7, 0.8))
        self.pointsLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])

    def test_cycleDetection(self):
        self.assertTrue(self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]))
        self.assertFalse(self.linesLayer.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]))
        self.pointsLayer.setDependencies([])
        self.linesLayer.setDependencies([])

    def test_layerDefinitionRewriteId(self):
        tmpfile = os.path.join(tempfile.tempdir, "test.qlr")

        ltr = QgsProject.instance().layerTreeRoot()

        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])

        QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr])

        grp = ltr.addGroup("imported")
        QgsLayerDefinition.loadLayerDefinition(tmpfile, QgsProject.instance(), grp)

        newPointsLayer = None
        newLinesLayer = None
        for l in grp.findLayers():
            if l.layerId().startswith('points'):
                newPointsLayer = l.layer()
            elif l.layerId().startswith('lines'):
                newLinesLayer = l.layer()
        self.assertIsNotNone(newPointsLayer)
        self.assertIsNotNone(newLinesLayer)
        self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()])

        self.pointsLayer.setDependencies([])

    def test_signalConnection(self):
        # remove all layers
        QgsProject.instance().removeAllMapLayers()
        # set dependencies and add back layers
        self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite")
        assert (self.pointsLayer.isValid())
        self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite")
        assert (self.linesLayer.isValid())
        self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite")
        assert (self.pointsLayer2.isValid())
        self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())])
        self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())])
        # this should update connections between layers
        QgsProject.instance().addMapLayers([self.pointsLayer])
        QgsProject.instance().addMapLayers([self.linesLayer])
        QgsProject.instance().addMapLayers([self.pointsLayer2])

        ms = QgsMapSettings()
        ms.setOutputSize(QSize(100, 100))
        ms.setExtent(QgsRectangle(0, 0, 1, 1))
        self.assertTrue(ms.hasValidSettings())

        u = QgsSnappingUtils()
        u.setMapSettings(ms)
        cfg = u.config()
        cfg.setEnabled(True)
        cfg.setMode(QgsSnappingConfig.AdvancedConfiguration)
        cfg.setIndividualLayerSettings(self.pointsLayer,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        cfg.setIndividualLayerSettings(self.pointsLayer2,
                                       QgsSnappingConfig.IndividualLayerSettings(True,
                                                                                 QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels))
        u.setConfig(cfg)
        # add another line
        f = QgsFeature(self.linesLayer.fields())
        f.setId(4)
        geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)")
        f.setGeometry(geom)
        self.linesLayer.startEditing()
        self.linesLayer.addFeatures([f])
        self.linesLayer.commitChanges()
        # check the second snapped point is OK
        m = u.snapToMap(QPoint(75, 100 - 0))
        self.assertTrue(m.isValid())
        self.assertTrue(m.hasVertex())
        self.assertEqual(m.point(), QgsPointXY(0.8, 0.0))

        self.pointsLayer.setDependencies([])
        self.pointsLayer2.setDependencies([])
Exemple #25
0
    def eliminate(self, inLayer, boundary, progressBar, outFileName):
        # keep references to the features to eliminate
        fidsToEliminate = inLayer.selectedFeaturesIds()

        if outFileName:  # user wants a new shape file to be created as result
            provider = inLayer.dataProvider()
            error = QgsVectorFileWriter.writeAsVectorFormat(
                inLayer, outFileName, provider.encoding(), inLayer.crs(),
                "ESRI Shapefile")

            if error != QgsVectorFileWriter.NoError:
                QMessageBox.warning(self, self.tr("Eliminate"),
                                    self.tr("Error creating output file"))
                return None

            outLayer = QgsVectorLayer(
                outFileName,
                QFileInfo(outFileName).completeBaseName(), "ogr")

        else:
            QMessageBox.information(self, self.tr("Eliminate"),
                                    self.tr("Please specify output shapefile"))
            return None

        # delete features to be eliminated in outLayer
        outLayer.setSelectedFeatures(fidsToEliminate)
        outLayer.startEditing()

        if outLayer.deleteSelectedFeatures():
            if self.saveChanges(outLayer):
                outLayer.startEditing()
        else:
            QMessageBox.warning(self, self.tr("Eliminate"),
                                self.tr("Could not delete features"))
            return None

        # ANALYZE
        start = 20.00
        progressBar.setValue(start)
        add = 80.00 / len(fidsToEliminate)

        lastLen = 0

        # we go through the list and see if we find any polygons we can merge the selected with
        # if we have no success with some we merge and then restart the whole story
        while (lastLen != inLayer.selectedFeatureCount()
               ):  # check if we made any progress
            lastLen = inLayer.selectedFeatureCount()
            fidsToDeselect = []

            #iterate over the polygons to eliminate
            for fid2Eliminate in inLayer.selectedFeaturesIds():
                feat = QgsFeature()

                if inLayer.getFeatures(QgsFeatureRequest().setFilterFid(
                        fid2Eliminate).setSubsetOfAttributes(
                            [])).nextFeature(feat):
                    geom2Eliminate = feat.geometry()
                    bbox = geom2Eliminate.boundingBox()
                    fit = outLayer.getFeatures(
                        QgsFeatureRequest().setFilterRect(bbox))
                    mergeWithFid = None
                    mergeWithGeom = None
                    max = 0

                    selFeat = QgsFeature()
                    while fit.nextFeature(selFeat):
                        selGeom = selFeat.geometry()

                        if geom2Eliminate.intersects(
                                selGeom):  # we have a candidate
                            iGeom = geom2Eliminate.intersection(selGeom)

                            if boundary:
                                selValue = iGeom.length()
                            else:
                                # we need a common boundary
                                if 0 < iGeom.length():
                                    selValue = selGeom.area()
                                else:
                                    selValue = 0

                            if selValue > max:
                                max = selValue
                                mergeWithFid = selFeat.id()
                                mergeWithGeom = QgsGeometry(
                                    selGeom)  # deep copy of the geometry

                    if mergeWithFid is not None:  # a successful candidate
                        newGeom = mergeWithGeom.combine(geom2Eliminate)

                        if outLayer.changeGeometry(mergeWithFid, newGeom):
                            # write change back to disc
                            if self.saveChanges(outLayer):
                                outLayer.startEditing()
                            else:
                                return None

                            # mark feature as eliminated in inLayer
                            fidsToDeselect.append(fid2Eliminate)
                        else:
                            QMessageBox.warning(
                                self, self.tr("Eliminate"),
                                self.
                                tr("Could not replace geometry of feature with id %s"
                                   ) % (mergeWithFid))
                            return None

                        start = start + add
                        progressBar.setValue(start)
            # end for fid2Eliminate

            # deselect features that are already eliminated in inLayer
            inLayer.deselect(fidsToDeselect)

        #end while

        if inLayer.selectedFeatureCount() > 0:
            # copy all features that could not be eliminated to outLayer
            if outLayer.addFeatures(inLayer.selectedFeatures()):
                # inform user
                fidList = ""

                for fid in inLayer.selectedFeaturesIds():
                    if not fidList == "":
                        fidList += ", "

                    fidList += unicode(fid)

                QErrorMessage(self).showMessage(
                    self.tr("Could not eliminate features with these ids:\n%s")
                    % (fidList))
            else:
                QMessageBox.warning(self, self.tr("Eliminate"),
                                    self.tr("Could not add features"))

        # stop editing outLayer and commit any pending changes
        if not self.saveChanges(outLayer):
            return None

        if outFileName:
            if self.addToCanvasCheck.isChecked():
                ftools_utils.addShapeToCanvas(outFileName)
            else:
                QMessageBox.information(
                    self, self.tr("Eliminate"),
                    self.tr("Created output shapefile:\n%s") % (outFileName))

        self.iface.mapCanvas().refresh()