De codesnippers op deze pagina hebben de volgende import nodig als u buiten de console van PyQGIS bent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from qgis.core import (
  QgsApplication,
  QgsDataSourceUri,
  QgsCategorizedSymbolRenderer,
  QgsClassificationRange,
  QgsPointXY,
  QgsProject,
  QgsExpression,
  QgsField,
  QgsFields,
  QgsFeature,
  QgsFeatureRequest,
  QgsFeatureRenderer,
  QgsGeometry,
  QgsGraduatedSymbolRenderer,
  QgsMarkerSymbol,
  QgsMessageLog,
  QgsRectangle,
  QgsRendererCategory,
  QgsRendererRange,
  QgsSymbol,
  QgsVectorDataProvider,
  QgsVectorLayer,
  QgsVectorFileWriter,
  QgsWkbTypes,
  QgsSpatialIndex,
)

from qgis.core.additions.edit import edit

from qgis.PyQt.QtGui import (
    QColor,
)

6. Vectorlagen gebruiken

Dit gedeelte beschrijft verschillende acties die kunnen worden uitgevoerd met vectorlagen.

Het meeste werk hier is gebaseerd op de methoden van de klasse QgsVectorLayer.

6.1. Informatie over attributen ophalen

U kunt informatie ophalen over de velden die zijn geassocieerd met een vectorlaag door fields() aan te roepen op een object QgsVectorLayer:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
for field in vlayer.fields():
    print(field.name(), field.typeName())
1
2
3
4
5
ID Integer64
fk_region Integer64
ELEV Real
NAME String
USE String

De methoden displayField() en mapTipTemplate() van de klasse QgsVectorLayer verschaffen informatie over het veld en de gebruikte sjabloon op de tab Tonen.

Wanneer u een vectorlaag laadt, wordt altijd een veld gekozen door QGIS als de Weergavenaam, terwijl de HTML kaarttip standaard leeg is. Met deze methoden kunt u gemakkelijk beide verkrijgen:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
print(vlayer.displayField())
NAME

Notitie

Als u de Weergavenaam wijzigt van een veld naar een expressie, moet u displayExpression() gebruiken in plaats van displayField().

6.2. Itereren over vectorlagen

Het doorlopen van de objecten in een vectorlaag is één van de meest voorkomende taken. Hieronder staat een voorbeeld van eenvoudige basiscode om deze taak uit te voeren en enige informatie weer te geven over elk object. Voor de variabele layer wordt aangenomen dat die een object QgsVectorLayer heeft

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# "layer" is a QgsVectorLayer instance
layer = iface.activeLayer()
features = layer.getFeatures()

for feature in features:
    # retrieve every feature with its geometry and attributes
    print("Feature ID: ", feature.id())
    # fetch geometry
    # show some information about the feature geometry
    geom = feature.geometry()
    geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
    if geom.type() == QgsWkbTypes.PointGeometry:
        # the geometry type can be of single or multi type
        if geomSingleType:
            x = geom.asPoint()
            print("Point: ", x)
        else:
            x = geom.asMultiPoint()
            print("MultiPoint: ", x)
    elif geom.type() == QgsWkbTypes.LineGeometry:
        if geomSingleType:
            x = geom.asPolyline()
            print("Line: ", x, "length: ", geom.length())
        else:
            x = geom.asMultiPolyline()
            print("MultiLine: ", x, "length: ", geom.length())
    elif geom.type() == QgsWkbTypes.PolygonGeometry:
        if geomSingleType:
            x = geom.asPolygon()
            print("Polygon: ", x, "Area: ", geom.area())
        else:
            x = geom.asMultiPolygon()
            print("MultiPolygon: ", x, "Area: ", geom.area())
    else:
        print("Unknown or invalid geometry")
    # fetch attributes
    attrs = feature.attributes()
    # attrs is a list. It contains all the attribute values of this feature
    print(attrs)
    # for this test only print the first feature
    break
Feature ID:  1
Point:  <QgsPointXY: POINT(7 45)>
[1, 'First feature']

6.3. Objecten selecteren

In QGIS desktop kunnen objecten op verschillende manieren worden geselecteerd, de gebruiker kan klikken op een object, een rechthoek in het kaartvenster tekenen of een expressie-filter gebruiken. Geselecteerde objecten worden normaal gesproken geaccentueerd in een andere kleur (standaard is geel) om de aandacht van de gebruiker naar de selectie te trekken.

Sometimes it can be useful to programmatically select features or to change the default color.

De methode selectAll() kan worden gebruikt om alle objecten te selecteren:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
layer.selectAll()

Gebruik de methode selectByExpression() om te selecteren met een expressie:

# Assumes that the active layer is points.shp file from the QGIS test suite
# (Class (string) and Heading (number) are attributes in points.shp)
layer = iface.activeLayer()
layer.selectByExpression('"Class"=\'B52\' and "Heading" > 10 and "Heading" <70', QgsVectorLayer.SetSelection)

U kunt, om de kleur van de selectie te wijzigen, de methode setSelectionColor() van QgsMapCanvas gebruiken, zoals weergegeven in het volgende voorbeeld:

iface.mapCanvas().setSelectionColor( QColor("red") )

U kunt, om objecten toe te voegen aan de lijst met geselecteerde objecten voor een bepaalde laag, select() aanroepen, die de lijst met ID’s voor de objecten doorgeeft aan de lijst:

1
2
3
4
5
6
7
8
9
selected_fid = []

# Get the first feature id from the layer
for feature in layer.getFeatures():
    selected_fid.append(feature.id())
    break

# Add these features to the selected list
layer.select(selected_fid)

De selectie opheffen:

layer.removeSelection()

6.3.1. Toegang tot attributen

Naar attributen kan worden verwezen door middel van hun naam:

print(feature['name'])
First feature

Als alternatief kan naar attributen worden verwezen door middel van een index. Dit is iets sneller dan het gebruiken van de naam. Bijvoorbeeld om het tweede attribuut te krijgen:

print(feature[1])
First feature

6.3.2. Itereren over geselecteerde objecten

Als u alleen geselecteerde objecten nodig hebt, kunt u de methode selectedFeatures() gebruiken van de vectorlaag:

selection = layer.selectedFeatures()
for feature in selection:
    # do whatever you need with the feature
    pass

6.3.3. Itereren over een deel van de objecten

Wanneer u een deel van de objecten in een laag wilt doorlopen, zoals bijvoorbeeld alleen de objecten in een opgegeven gebied, dan dient een object QgsFeatureRequest te worden toegevoegd aan de aanroep getFeatures(). Hier is een voorbeeld:

1
2
3
4
5
6
7
areaOfInterest = QgsRectangle(450290,400520, 450750,400780)

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

for feature in layer.getFeatures(request):
    # do whatever you need with the feature
    pass

Omwille van de snelheid wordt het kruisen vaak gedaan door alleen het begrenzingsvak van het object te gebruiken. Er is echter een vlag ExactIntersect dat er voor zorgt dat alleen kruisende objecten zullen worden teruggegeven:

request = QgsFeatureRequest().setFilterRect(areaOfInterest) \
                             .setFlags(QgsFeatureRequest.ExactIntersect)

Met setLimit() kunt u het aantal gezochte objecten beperken. Hier is een voorbeeld:

request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
    print(feature)
<qgis._core.QgsFeature object at 0x7f9b78590948>

Als u in plaats daarvan een op attributen gebaseerd filter nodig heeft (of als aanvulling) van een ruimtelijke zoals weergegeven in de voorbeelden hierboven, kunt u een object QgsExpression bouwen en dat doorgeven aan de constructor QgsFeatureRequest. Hier is een voorbeeld:

# The expression will filter the features where the field "location_name"
# contains the word "Lake" (case insensitive)
exp = QgsExpression('location_name ILIKE \'%Lake%\'')
request = QgsFeatureRequest(exp)

Bekijk Expressies, filteren en waarden berekenen voor de details over de door QgsExpression ondersteunde syntaxis.

Het verzoek kan worden gebruikt om de gegevens per opgehaald object te definiëren, zodat de doorloop alle objecten retourneert, maar slechts een deel van de gegevens van elk daarvan teruggeeft.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Only return selected fields to increase the "speed" of the request
request.setSubsetOfAttributes([0,2])

# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.fields())

# Don't return geometry objects to increase the "speed" of the request
request.setFlags(QgsFeatureRequest.NoGeometry)

# Fetch only the feature with id 45
request.setFilterFid(45)

# The options may be chained
request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])

6.4. Vectorlagen bewerken

De meeste vector gegevensproviders ondersteunen het bewerken van gegevens van de laag. Soms ondersteunen zij slechts een subset van mogelijke acties voor bewerken. Gebruik de functie capabilities() om uit te zoeken welke set voor functionaliteiten wordt ondersteund.

caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
if caps & QgsVectorDataProvider.DeleteFeatures:
    print('The layer supports DeleteFeatures')
The layer supports DeleteFeatures

Bekijk, voor een lijst van alle beschikbare capabilities, de API Documentation of QgsVectorDataProvider.

U kunt, om de tekstuele beschrijving van de capabilities van de laag af te drukken naar een kommagescheiden lijst, capabilitiesString() gebruiken, zoals in het volgende voorbeeld:

1
2
3
4
5
6
caps_string = layer.dataProvider().capabilitiesString()
# Print:
# 'Add Features, Delete Features, Change Attribute Values, Add Attributes,
# Delete Attributes, Rename Attributes, Fast Access to Features at ID,
# Presimplify Geometries, Presimplify Geometries with Validity Check,
# Transactions, Curved Geometries'

Bij het gebruiken van de volgende methodes voor het bewerken van vectorlagen worden de wijzigingen direct opgeslagen in de onderliggende gegevensbron (een bestand, database etc.). Voor het geval u slechts tijdelijke wijzigingen wilt uitvoeren, ga dan naar het volgende gedeelte waarin uitgelegd wordt hoe aanpassingen kunnen worden uitgevoerd met een bewerkingsbuffer.

Notitie

Als u werkt binnen QGIS (ofwel vanuit de console of vanuit een plug-in), zou het nodig kunnen zijn het opnieuw tekenen van het kaartvenster te forceren om de wijzigingen te kunnen zien die u heeft gemaakt aan de geometrie, aan de stijl of aan de attributen:

1
2
3
4
5
6
# If caching is enabled, a simple canvas refresh might not be sufficient
# to trigger a redraw and you must clear the cached image for the layer
if iface.mapCanvas().isCachingEnabled():
    layer.triggerRepaint()
else:
    iface.mapCanvas().refresh()

6.4.1. Objecten toevoegen

Maak enkele instances QgsFeature en geef daar een lijst van door aan de methode addFeatures() van de provider. Het zal twee waarden teruggeven: resultaat (true/false) en een lijst van toegevoegde objecten (hun ID wordt ingesteld door de opslag van de gegevens).

U kunt, om de attributen in te stellen, ofwel het object initialiseren door een object QgsFields door te geven (u kunt dat verkrijgen vanuit de methode fields() van de vectorlaag) of initAttributes() aan te roepen en het aantal velden op te geven die wilt hebben toegevoegd.

1
2
3
4
5
6
7
8
if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature(layer.fields())
    feat.setAttributes([0, 'hello'])
    # Or set a single attribute by key or by index:
    feat.setAttribute('name', 'hello')
    feat.setAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

6.4.2. Objecten verwijderen

Geef eenvoudigweg een lijst van hun object-ID’s op om enkele objecten te verwijderen.

if caps & QgsVectorDataProvider.DeleteFeatures:
    res = layer.dataProvider().deleteFeatures([5, 10])

6.4.3. Objecten bewerken

Het is mogelijk om de geometrie van objecten te wijzigen of enkele attributen. Het volgende voorbeeld wijzigt eerst waarden van attributen met de index 0 en 1, en wijzigt dan de geometrie van het object.

1
2
3
4
5
6
7
8
9
fid = 100   # ID of the feature we will modify

if caps & QgsVectorDataProvider.ChangeAttributeValues:
    attrs = { 0 : "hello", 1 : 123 }
    layer.dataProvider().changeAttributeValues({ fid : attrs })

if caps & QgsVectorDataProvider.ChangeGeometries:
    geom = QgsGeometry.fromPointXY(QgsPointXY(111,222))
    layer.dataProvider().changeGeometryValues({ fid : geom })

Tip

Voorkeur voor klasse QgsVectorLayerEditUtils voor bewerken van alleen de geometrie

Als u alleen geometrieën wilt wijzigen, kunt u overwegen QgsVectorLayerEditUtils te gebruiken wat enkele nuttige methoden verschaft om geometrieën te bewerken (vertalen, invoegen of punten verplaatsen etc.)

6.4.4. Vectorlagen bewerken met een bewerkingsbuffer

Bij het bewerken van vectoren binnen de toepassing QGIS, moet u eerst de modus Bewerken starten voor een bepaalde laag, dan enige aanpassingen te doen en tenslotte de wijzigingen vastleggen (of terugdraaien). Alle aanpassingen die u doet worden niet weggeschreven totdat u ze vastlegt — zij blijven in de bewerkingsbuffer van het geheugen van de laag. Het is mogelijk om deze functionaliteit ook programmatisch te gebruiken — het is simpelweg een andere methode voor het bewerken van vectorlagen die het direct gebruik van providers van gegevens aanvult. Gebruik deze optie bij het verschaffen van enkele gereedschappen voor de GUI voor het bewerken van vectorlagen, omdat dit de gebruiker in staat zal stellen te bepalen om vast te leggen/terug te draaien en maakt het gebruiken van Ongedaan maken/Opnieuw mogelijk. Bij het vastleggen van wijzigingen worden alle aanpassingen in de bewerkingsbuffer opgeslagen in de provider van de gegevens.

De methoden zijn soortgelijk aan die welke we hebben gezien in de provider, maar zij worden in plaats daarvan aangeroepen op het object QgsVectorLayer.

De laag met in de modus Bewerken staan om deze methoden te kunnen laten werken. Gebruikt de methode startEditing() om de modus Bewerken te starten. Gebruik de methoden commitChanges() of rollBack() om het bewerken te stoppen. De eerste zal al uw wijzigingen vastleggen in de gegevensbron, terwijl de tweede ze zal negeren en de gegevensbron in het geheel niet zal wijzigen.

Gebruik de methode isEditable() om te weten te komen of een laag in de modus Bewerken staat.

Hier zijn enkele voorbeelden die demonstreren hoe deze methoden voor bewerken te gebruiken.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from qgis.PyQt.QtCore import QVariant

feat1 = feat2 = QgsFeature(layer.fields())
fid = 99
feat1.setId(fid)

# add two features (QgsFeature instances)
layer.addFeatures([feat1,feat2])
# delete a feature with specified ID
layer.deleteFeature(fid)

# set new geometry (QgsGeometry instance) for a feature
geometry = QgsGeometry.fromWkt("POINT(7 45)")
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to a given value
fieldIndex =1
value ='My new name'
layer.changeAttributeValue(fid, fieldIndex, value)

# add new field
layer.addAttribute(QgsField("mytext", QVariant.String))
# remove a field
layer.deleteAttribute(fieldIndex)

De hierboven vermelde aanroepen moeten zijn opgenomen in opdrachten Ongedaan maken om er voor te zorgen dat Ongedaan maken/Opnieuw juist werkt. (Als Ongedaan maken/Opnieuw voor u niet van belang is en u wilt dat de wijzigingen onmiddellijk worden opgeslagen, dan zult u gemakkelijker werken met bewerken met gegevensprovider.)

Hier staat hoe u de functionaliteit Ongedaan maken kunt gebruiken:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
layer.beginEditCommand("Feature triangulation")

# ... call layer's editing methods ...

if problem_occurred:
  layer.destroyEditCommand()
  # ... tell the user that there was a problem
  # and return

# ... more editing ...

layer.endEditCommand()

De methode beginEditCommand() zal een interne “actieve” opdracht maken en zal opvolgende wijzigingen in de vectorlaag opnemen. Met de aanroep naar endEditCommand() wordt de opdracht doorgegeven aan de stapel Ongedaan maken en de gebruiker zal in staat zijn om Ongedaan maken/Opnieuw uit te voeren vanuit de GUI. Voor het geval er iets verkeerd gaat bij het maken van de wijzigingen, zal de methode destroyEditCommand() de opdracht verwijderen en de wijzigingen terugdraaien die al werden gemaakt toen deze opdracht actief was.

U kunt ook het argument with edit(layer)-gebruiken om commit en rollback in een meer semantisch codeblok op te nemen zoals weergegeven in het voorbeeld hieronder:

with edit(layer):
  feat = next(layer.getFeatures())
  feat[0] = 5
  layer.updateFeature(feat)

Dit zal aan het einde automatisch commitChanges() aanroepen. Indien er een uitzondering optreedt, zal het rollBack() alle wijzigingen. In het geval dat een probleem wordt tegengekomen binnen commitChanges() (als de methode False teruggeeft) zal een uitzondering QgsEditError optreden.

6.4.5. Velden toevoegen en verwijderen

U moet een lijst met definities voor velden opgeven om velden toe te voegen (attributen). Geef een lijst met indexen van velden op om velden te verwijderen.

1
2
3
4
5
6
7
8
9
from qgis.PyQt.QtCore import QVariant

if caps & QgsVectorDataProvider.AddAttributes:
    res = layer.dataProvider().addAttributes(
        [QgsField("mytext", QVariant.String),
        QgsField("myint", QVariant.Int)])

if caps & QgsVectorDataProvider.DeleteAttributes:
    res = layer.dataProvider().deleteAttributes([0])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Alternate methods for removing fields
# first create temporary fields to be removed (f1-3)
layer.dataProvider().addAttributes([QgsField("f1",QVariant.Int),QgsField("f2",QVariant.Int),QgsField("f3",QVariant.Int)])
layer.updateFields()
count=layer.fields().count() # count of layer fields
ind_list=list((count-3, count-2)) # create list

# remove a single field with an index
layer.dataProvider().deleteAttributes([count-1])

# remove multiple fields with a list of indices
layer.dataProvider().deleteAttributes(ind_list)

Na het verwijderen of toevoegen van velden in de gegevensprovider moeten de velden van de laag worden bijgewerkt omdat de wijzigingen niet automatisch worden doorgevoerd.

layer.updateFields()

Tip

Wijzigingen direct opslaan met op with gebaseerde opdracht

Gebruiken van with edit(layer): de wijzigingen zullen automatisch worden vastgelegd door het aanroepen van commitChanges() aan het einde. Indien er een uitzondering optreedt, zal het rollBack() alle wijzigingen. Bekijk Vectorlagen bewerken met een bewerkingsbuffer.

6.5. Ruimtelijke index gebruiken

Ruimtelijke indexen kunnen de uitvoering van uw code enorm verbeteren als u frequent query’s moet uitvoeren op een vectorlaag. Stel u bijvoorbeeld voor dat u een algoritme voor interpolatie schrijft, en dat voor een bepaalde locatie u de 10 dichtstbijzijnde punten van een puntenlaag wilt weten om die punten te gebruiken voor het berekenen van de waarde voor de interpolatie. Zonder een ruimtelijke index is de enige manier waarop QGIS die 10 punten kan vinden is door de afstand vanaf elk punt tot de gespecificeerde locatie te berekenen en dan die afstanden te vergelijken. Dit kan een zeer tijdrovende taak zijn, speciaal als het moet worden herhaald voor verschillende locaties. Als er een ruimtelijke index bestaat voor de laag, is de bewerking veel effectiever.

Denk aan een laag zonder ruimtelijke index als aan een telefoonboek waarin telefoonnummers niet zijn gesorteerd of geïndexeerd. De enige manier om het telefoonnummer van een bepaald persoon te vinden is door vanaf het begin te lezen totdat u het vindt.

Ruimtelijke indexen worden niet standaard gemaakt voor een vectorlaag in QGIS, maar u kunt ze eenvoudig maken. Dit is wat u dan moet doen:

  • ruimtelijke index maken met de klasse QgsSpatialIndex():

    index = QgsSpatialIndex()
    
  • voeg objecten aan de index toe — index neemt object QgsFeature en voegt dat toe aan de interne gegevensstructuur. U kunt het object handmatig maken of er een gebruiken uit een eerdere aanroep naar getFeatures() van de provider.

    index.addFeature(feat)
    
  • als alternatief kunt u alle objecten van een laag in één keer laden met behulp van bulk laden

    index = QgsSpatialIndex(layer.getFeatures())
    
  • als de ruimtelijke index eenmaal is gevuld met enkele waarden, kunt u enkele query’s uitvoeren

    1
    2
    3
    4
    5
    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPointXY(25.4, 12.7), 5)
    
    # returns array of IDs of features which intersect the rectangle
    intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
    

6.6. Vectorlagen maken

Er zijn verschillende manieren om een gegevensset uit een vectorlaag te maken:

  • de klasse QgsVectorFileWriter: Een handige klasse voor het schrijven van vectorbestanden naar schijf, ofwel met een statische aanroep naar writeAsVectorFormat() die de gehele vectorlaag opslaat of een instance van de klasse maken en aanroepen uitvoeren naar addFeature(). Deze klasse ondersteunt alle indelingen voor vector die OGR ondersteunt (GeoPackage, Shapefile, GeoJSON, KML en andere).

  • de klasse QgsVectorLayer: instantieert een gegevensprovider die het opgegeven pad (URL) van de gegevensbron interpreteert om te verbinden met en toegang te verschaffen tot de gegevens. Het kan worden gebruikt om tijdelijke, op geheugen gebaseerde lagen (memory), te maken en te verbinden met gegevenssets van OGR (ogr), databases (postgres, spatialite, mysql, mssql) en meer (wfs, gpx, delimitedtext…).

6.6.1. Vanuit een instance van QgsVectorFileWriter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# SaveVectorOptions contains many settings for the writer process
save_options = QgsVectorFileWriter.SaveVectorOptions()
transform_context = QgsProject.instance().transformContext()
# Write to a GeoPackage (default)
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                  "testdata/my_new_file.gpkg",
                                                  transform_context,
                                                  save_options)
if error[0] == QgsVectorFileWriter.NoError:
    print("success!")
else:
  print(error)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "ESRI Shapefile"
save_options.fileEncoding = "UTF-8"
transform_context = QgsProject.instance().transformContext()
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                  "testdata/my_new_shapefile",
                                                  transform_context,
                                                  save_options)
if error[0] == QgsVectorFileWriter.NoError:
    print("success again!")
else:
  print(error)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Write to an ESRI GDB file
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "FileGDB"
# if no geometry
save_options.overrideGeometryType = QgsWkbTypes.Unknown
save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
save_options.layerName = 'my_new_layer_name'
transform_context = QgsProject.instance().transformContext()
gdb_path = "testdata/my_example.gdb"
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                gdb_path,
                                                transform_context,
                                                save_options)
if error[0] == QgsVectorFileWriter.NoError:
  print("success!")
else:
  print(error)

U kunt ook velden converteren om ze uitwisselbaar te maken met verschillende indelingen door de klasse FieldValueConverter te gebruiken. Bijvoorbeeld om typen arryvariabelen (bijv. in Postgres) te converteren naar een type tekst, kunt u het volgende doen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LIST_FIELD_NAME = 'xxxx'

class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):

  def __init__(self, layer, list_field):
    QgsVectorFileWriter.FieldValueConverter.__init__(self)
    self.layer = layer
    self.list_field_idx = self.layer.fields().indexFromName(list_field)

  def convert(self, fieldIdxInLayer, value):
    if fieldIdxInLayer == self.list_field_idx:
      return QgsListFieldFormatter().representValue(layer=vlayer,
                                                    fieldIndex=self.list_field_idx,
                                                    config={},
                                                    cache=None,
                                                    value=value)
    else:
      return value

  def fieldDefinition(self, field):
    idx = self.layer.fields().indexFromName(field.name())
    if idx == self.list_field_idx:
      return QgsField(LIST_FIELD_NAME, QVariant.String)
    else:
      return self.layer.fields()[idx]

converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.fieldValueConverter = converter

Een doel-CRS mag ook worden gespecificeerd — als een geldige instance van QgsCoordinateReferenceSystem wordt doorgegeven als de vierde parameter, wordt de laag naar dat CRS getransformeerd.

Roep voor geldige namen van stuurprogramma’s de methode supportedFiltersAndFormats aan of raadpleeg de door OGR ondersteunde indelingen — u zou de waarde in de kolom “Code” moeten doorgeven als de naam van het stuurprogramma.

Optioneel kunt u instellen of of u alleen geselecteerde objecten wilt exporteren, meer driver-specifieke opties voor maken wilt doorgeven of de schrijven wilt vertellen om geen attributen aan te maken… Er zijn een aantal andere (optionele) parameters; bekijk de documentatie voor QgsVectorFileWriter voor details.

6.6.2. Direct uit objecten

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from qgis.PyQt.QtCore import QVariant

# define fields for feature attributes. A QgsFields object is needed
fields = QgsFields()
fields.append(QgsField("first", QVariant.Int))
fields.append(QgsField("second", QVariant.String))

""" create an instance of vector file writer, which will create the vector file.
Arguments:
1. path to new file (will fail if exists already)
2. field map
3. geometry type - from WKBTYPE enum
4. layer's spatial reference (instance of
   QgsCoordinateReferenceSystem)
5. coordinate transform context
6. save options (driver name for the output file, encoding etc.)
"""

crs = QgsProject.instance().crs()
transform_context = QgsProject.instance().transformContext()
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "ESRI Shapefile"
save_options.fileEncoding = "UTF-8"

writer = QgsVectorFileWriter.create(
  "testdata/my_new_shapefile.shp",
  fields,
  QgsWkbTypes.Point,
  crs,
  transform_context,
  save_options
)

if writer.hasError() != QgsVectorFileWriter.NoError:
    print("Error when creating shapefile: ",  writer.errorMessage())

# add a feature
fet = QgsFeature()

fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes([1, "text"])
writer.addFeature(fet)

# delete the writer to flush features to disk
del writer

6.6.3. Vanuit een instance van QgsVectorLayer

Laten we, naast alle gegevensproviders die worden ondersteund door de klasse QgsVectorLayer, ons focussen op de op geheugen gebaseerde lagen. Memory-provider is bedoeld om hoofdzakelijk te worden gebruikt door plug-ins of ontwikkelaars voor 3e partijen. Het slaat geen gegevens op de schijf op, wat ontwikkelaars in staat stelt het te gebruiken als snel backend voor enkele tijdelijke lagen.

De provider ondersteunt velden string, int en double.

De memory-provider ondersteunt ook ruimtelijke indexen, wat wordt ingeschakeld door de functie van de provider createSpatialIndex() aan te roepen. Als de ruimtelijke index eenmaal is gemaakt zult u in staat zijn objecten in kleinere regio’s sneller te doorlopen (omdat het niet nodig is door alle objecten te gaan, alleen die in de gespecificeerde rechthoek).

Een memory-provider wordt gemaakt door "memory" door te geven als de string voor de provider string aan de constructor QgsVectorLayer.

De constructor accepteert ook een URI die het type geometrie van de laag definieert, één van: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" of "None".

De URI mag ook het coördinaten referentiesysteem specificeren, velden, en indexeren van de memory-provider in de URI. De syntaxis is:

crs=definition

Specificeert het coördinaten referentiesysteem, waar definition een van de vormen kan zijn die worden geaccepteerd door QgsCoordinateReferenceSystem.createFromString

index=yes

Specificeert dat de provider een ruimtelijke index zal gebruiken

field=name:type(length,precision)

Specificeert een attribuut van de laag. Het attribuut heeft een naam en, optioneel, een type (integer, double of string), lengte en precisie. Er kunnen meerdere definities voor velden zijn.

Het volgende voorbeeld van een URI bevat al deze opties

"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"

De volgende voorbeeldcode illustreert het maken en vullen van een memory-provider

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from qgis.PyQt.QtCore import QVariant

# create layer
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()

# add fields
pr.addAttributes([QgsField("name", QVariant.String),
                    QgsField("age",  QVariant.Int),
                    QgsField("size", QVariant.Double)])
vl.updateFields() # tell the vector layer to fetch changes from the provider

# add a feature
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes(["Johny", 2, 0.3])
pr.addFeatures([fet])

# update layer's extent when new features have been added
# because change of extent in provider is not propagated to the layer
vl.updateExtents()

Laten we tenslotte controleren of alles goed ging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# show some stats
print("fields:", len(pr.fields()))
print("features:", pr.featureCount())
e = vl.extent()
print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())

# iterate over features
features = vl.getFeatures()
for fet in features:
    print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
fields: 3
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johny', 2, 0.3] <QgsPointXY: POINT(10 10)>

6.7. Uiterlijk (symbologie) van vectorlagen

Wanneer een vectorlaag wordt gerenderd wordt het uiterlijk van de gegevens verschaft door de renderer en symbolen geassocieerd met de laag. Symbolen zijn klassen die zorg dragen voor het tekenen van visuele weergaven van objecten, terwijl renderers bepalen welk symbool zal worden gebruikt voor een bepaald object.

De renderer voor een bepaalde laag kan worden verkregen zoals hieronder is weergegeven:

renderer = layer.renderer()

En met die verwijzing, laten we het een beetje verkennen

print("Type:", renderer.type())
Type: singleSymbol

Er zijn verschillende bekende typen renderer beschikbaar in de bron-bibliotheek van QGIS:

Type

Klasse

Omschrijving

singleSymbol

QgsSingleSymbolRenderer

Rendert alle objecten met hetzelfde symbool

categorizedSymbol

QgsCategorizedSymbolRenderer

Rendert objecten door een ander symbool voor elke categorie te gebruiken

graduatedSymbol

QgsGraduatedSymbolRenderer

Rendert objecten door een ander symbool voor elke bereik van waarden te gebruiken

Er kunnen ook enkele aangepaste typen renderer zijn, dus doe nooit de aanname dat alleen deze typen beschikbaar zijn. U kunt het QgsRendererRegistry van de toepassing bevragen om de huidige beschikbare renderers te achterhalen:

print(QgsApplication.rendererRegistry().renderersList())
['nullSymbol', 'singleSymbol', 'categorizedSymbol', 'graduatedSymbol', 'RuleRenderer', 'pointDisplacement', 'pointCluster', 'invertedPolygonRenderer', 'heatmapRenderer', '25dRenderer']

Het is mogelijk om een dump te verkrijgen van de inhoud van een renderer in de vorm van tekst — kan handig zijn bij debuggen

renderer.dump()
SINGLE: MARKER SYMBOL (1 layers) color 190,207,80,255

6.7.1. Renderer Enkel symbool

U kunt het voor de rendering gebruikte symbool verkrijgen door de methode symbol() aan te roepen en die te wijzigen met de methode setSymbol() (opmerking voor ontwikkelaars in C++: de renderer wordt eigenaar van het symbool.)

U kunt het symbool dat wordt gebruikt door een bepaalde vectorlaag wijzigen door setSymbol() aan te roepen die een instance doorgeeft van de toepasselijke symbool instance. Symbolen voor lagen punt, lijn en polygoon kunnen worden gemaakt door het aanroepen van de functie createSimple() van de overeenkomende klassen QgsMarkerSymbol, QgsLineSymbol en QgsFillSymbol.

Het aan createSimple() doorgegeven woordenboek stelt de eigenschappen voor de stijl van het symbool in.

U kunt bijvoorbeeld het gebruikte symbool voor een bepaalde punt-laag wijzigen door setSymbol() aan te roepen die een instance doorgeeft van een :class:`QgsMarkerSymbol <qgis.core.QgsMarkerSymbol> zoals in het volgende voorbeeld van code:

symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
layer.triggerRepaint()

name geeft de vorm van de markering aan, en kan één van de volgende zijn:

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

U kunt de voorbeeldcode volgen om een volledige lijst met eigenschappen te verkrijgen van de eerste symboollaag van een instance symbool :

print(layer.renderer().symbol().symbolLayers()[0].properties())
{'angle': '0', 'color': '255,0,0,255', 'horizontal_anchor_point': '1', 'joinstyle': 'bevel', 'name': 'square', 'offset': '0,0', 'offset_map_unit_scale': '3x:0,0,0,0,0,0', 'offset_unit': 'MM', 'outline_color': '35,35,35,255', 'outline_style': 'solid', 'outline_width': '0', 'outline_width_map_unit_scale': '3x:0,0,0,0,0,0', 'outline_width_unit': 'MM', 'scale_method': 'diameter', 'size': '2', 'size_map_unit_scale': '3x:0,0,0,0,0,0', 'size_unit': 'MM', 'vertical_anchor_point': '1'}

Dit kan nuttig zijn als u enkele eigenschappen wilt wijzigen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# You can alter a single property...
layer.renderer().symbol().symbolLayer(0).setSize(3)
# ... but not all properties are accessible from methods,
# you can also replace the symbol completely:
props = layer.renderer().symbol().symbolLayer(0).properties()
props['color'] = 'yellow'
props['name'] = 'square'
layer.renderer().setSymbol(QgsMarkerSymbol.createSimple(props))
# show the changes
layer.triggerRepaint()

6.7.2. Renderer symbool Categoriën

Bij het gebruiken van een renderer Categorieën kunt u het attribuut dat is gebruikt voor de classificatie bevragen en instellen: gebruik de methoden classAttribute() en setClassAttribute().

Een lijst categorieën verkrijgen

1
2
3
4
5
6
7
8
9
categorized_renderer = QgsCategorizedSymbolRenderer()
# Add a few categories
cat1 = QgsRendererCategory('1', QgsMarkerSymbol(), 'category 1')
cat2 = QgsRendererCategory('2', QgsMarkerSymbol(), 'category 2')
categorized_renderer.addCategory(cat1)
categorized_renderer.addCategory(cat2)

for cat in categorized_renderer.categories():
    print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))
1: category 1 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>
2: category 2 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>

Waar value() de waarde is die wordt gebruikt voor het onderscheiden van de categorieën, label() is een tekst die gebruikt wordt voor de omschrijving van de categorie en de methode symbol() geeft het toegewezen symbool terug.

De renderer slaat gewoonlijk ook het originele symbool en de kleurenbalk op die voor de classificatie werden gebruikt: methoden sourceColorRamp() en sourceSymbol().

6.7.3. Renderer symbool Gradueel

Deze renderer lijkt erg veel op de renderer voor het symbool van de categorieën, hierboven beschreven, maar in plaats van één attribuutwaarde per klasse, werkt het met bereiken van waarden en kan dus alleen gebruikt worden met numerieke attributen.

Meer te weten komen over gebruikte bereiken in de renderer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
graduated_renderer = QgsGraduatedSymbolRenderer()
# Add a few categories
graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 0-100', 0, 100), QgsMarkerSymbol()))
graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 101-200', 101, 200), QgsMarkerSymbol()))

for ran in graduated_renderer.ranges():
    print("{} - {}: {} {}".format(
        ran.lowerValue(),
        ran.upperValue(),
        ran.label(),
        ran.symbol()
      ))
0.0 - 100.0: class 0-100 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>
101.0 - 200.0: class 101-200 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>

U kunt opnieuw de methoden classAttribute (om de naam van het attribuut voor classificatie te zoeken), sourceSymbol en sourceColorRamp gebruiken. Aanvullend is er de methode mode die bepaalt hoe de bereiken werden gemaakt: met behulp van gelijke intervallen, kwantielen of een andere methode.

Als u uw eigen renderer voor symbolen Gradueel wilt maken, kunt u dat doen zoals is geïllustreerd in het voorbeeldsnippet hieronder (wat een eenvoudige schikking in twee klassen maakt)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from qgis.PyQt import QtGui

myVectorLayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
myTargetField = 'ELEV'
myRangeList = []
myOpacity = 1
# Make our first symbol and range...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setOpacity(myOpacity)
myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, myLabel)
myRangeList.append(myRange1)
#now make another symbol and range...
myMin = 50.1
myMax = 100
myLabel = 'Group 2'
myColour = QtGui.QColor('#00eeff')
mySymbol2 = QgsSymbol.defaultSymbol(
     myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setOpacity(myOpacity)
myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
myClassificationMethod = QgsApplication.classificationMethodRegistry().method("EqualInterval")
myRenderer.setClassificationMethod(myClassificationMethod)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRenderer(myRenderer)

6.7.4. Werken met symbolen

Voor het weergeven van symbolen is er de basisklasse QgsSymbol met drie afgeleide klassen:

Elk symbool bestaat uit één of meer symboollagen (klassen afgeleid van QgsSymbolLayer). De symboollagen doen de actuele rendering, de symboolklasse zelf dient alleen als een container voor de symboollagen.

Met een instance van een symbool (bijv. van een renderer), is het mogelijk om het te verkennen: de methode type zegt of het een symbool markering, lijn of vulling is. Er is de methode dump wat een korte omschrijving van het symbool teruggeeft. Een lijst van symboollagen verkrijgen:

marker_symbol = QgsMarkerSymbol()
for i in range(marker_symbol.symbolLayerCount()):
    lyr = marker_symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))
0: SimpleMarker

Gebruik de methode color om de kleur van het symbool vast te stellen en setColor en angle, voor lijnsymbolen geeft de methode width de dikte van de lijn terug.

Grootte en breedte zijn standaard in millimeters, hoeken zijn in graden.

6.7.4.1. Werken met symboollagen

Zoals eerder gezegd bepalen symboollagen (subklassen van QgsSymbolLayer) het uiterlijk van de objecten. Er zijn verscheidene basisklassen voor symboollagen voor algemeen gebruik. Het is mogelijk om nieuwe typen symboollagen te implementeren en dus willekeurig aan te passen hoe objecten zullen worden gerenderd. De methode layerType() identificeert uniek de klasse van de symboollaag — de basis en standaard zijn de typen symboollagen SimpleMarker, SimpleLine en SimpleFill.

U kunt een volledige lijst van de typen symboollagen, die u voor een bepaalde klasse van een symboollaag kunt maken, verkrijgen met de volgende code:

1
2
3
4
5
from qgis.core import QgsSymbolLayerRegistry
myRegistry = QgsApplication.symbolLayerRegistry()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
    print(item)
1
2
3
4
5
6
7
8
EllipseMarker
FilledMarker
FontMarker
GeometryGenerator
RasterMarker
SimpleMarker
SvgMarker
VectorField

Klasse QgsSymbolLayerRegistry beheert een database van alle beschikbare typen symboollagen.

Gebruik zijn methode properties() om toegang te verkrijgen tot de gegevens van de symboollaag, die een woordenboek met paren van sleutels-waarden teruggeeft van eigenschappen die het uiterlijk bepalen. Elke type symboollaag heeft een specifieke set eigenschappen die het gebruikt. Aanvullend zijn er de generieke methoden color, size, angle en width met hun tegenhangers om ze in te stellen. Natuurlijk zijn grootte en hoek alleen beschikbaar voor symboollagen voor markeringen en breedte voor lijn-symboollagen.

6.7.4.2. Aangepaste typen voor symboollagen maken

Veronderstel dat u de manier waarop gegevens worden gerenderd wilt aanpassen. U kunt uw eigen klasse voor de symboollaag maken dat de objecten op exact de wijze die u wilt tekent. Hier is een voorbeeld van een markering die rode cirkels met een gespecificeerde straal tekent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from qgis.core import QgsMarkerSymbolLayer
from qgis.PyQt.QtGui import QColor

class FooSymbolLayer(QgsMarkerSymbolLayer):

  def __init__(self, radius=4.0):
      QgsMarkerSymbolLayer.__init__(self)
      self.radius = radius
      self.color = QColor(255,0,0)

  def layerType(self):
     return "FooMarker"

  def properties(self):
      return { "radius" : str(self.radius) }

  def startRender(self, context):
    pass

  def stopRender(self, context):
      pass

  def renderPoint(self, point, context):
      # Rendering depends on whether the symbol is selected (QGIS >= 1.5)
      color = context.selectionColor() if context.selected() else self.color
      p = context.renderContext().painter()
      p.setPen(color)
      p.drawEllipse(point, self.radius, self.radius)

  def clone(self):
      return FooSymbolLayer(self.radius)

De methode layerType bepaalt de naam van de symboollaag, die moet uniek zijn voor alle symboollagen. De methode properties wordt gebruikt voor het behouden van attributen. De methode clone moet een kopie teruggeven van de symboollaag met exact dezelfde attributen. Tenslotte zijn er methoden voor renderen: startRender wordt aangeroepen vóór het renderen van het eerste object, stopRender als het renderen is voltooid en de methode renderPoint wordt aangeroepen om het renderen uit te voeren. De coördinaten van de punt(en) zijn al getransformeerd naar de coördinaten voor uitvoer.

Voor polylijnen en polygonen zou het enige verschil liggen in de methode van renderen: u zou renderPolyline gebruiken, welke een lijst met lijnen zou ontvangen, terwijl renderPolygon een lijst van punten op de buitenste ring als de eerste parameter ontvangt en een lijst van binnenringen (of None) als een tweede parameter.

Gewoonlijk is het handig om een GUI toe te voegen voor het instellen van attributen voor het type symboollaag om het voor gebruikers mogelijk te maken het uiterlijk aan te passen: in het geval van ons voorbeeld hierboven kunnen we de gebruiker de straal van de cirkel laten instellen. De volgende code implementeert een dergelijk widget

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from qgis.gui import QgsSymbolLayerWidget

class FooSymbolLayerWidget(QgsSymbolLayerWidget):
    def __init__(self, parent=None):
        QgsSymbolLayerWidget.__init__(self, parent)

        self.layer = None

        # setup a simple UI
        self.label = QLabel("Radius:")
        self.spinRadius = QDoubleSpinBox()
        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.label)
        self.hbox.addWidget(self.spinRadius)
        self.setLayout(self.hbox)
        self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
            self.radiusChanged)

    def setSymbolLayer(self, layer):
        if layer.layerType() != "FooMarker":
            return
        self.layer = layer
        self.spinRadius.setValue(layer.radius)

    def symbolLayer(self):
        return self.layer

    def radiusChanged(self, value):
        self.layer.radius = value
        self.emit(SIGNAL("changed()"))

Deze widget kan worden ingebed in het dialoogvenster van de eigenschappen voor het symbool. Wanneer het type symboollaag wordt geselecteerd in het dialoogvenster van de eigenschappen voor het symbool, maakt het een instance van de symboollaag en een instance van de widget van de symboollaag. Dan roept het de methode setSymbolLayer aan om de symboollaag toe te wijzen aan de widget. In die methode zou de widget de UI moeten bijwerken om de attributen van de symboollaag weer te geven. De methode symbolLayer wordt gebruikt om de symboollaag opnieuw op te halen bij het dialoogvenster Eigenschappen om het voor het symbool te gebruiken.

Bij elke wijziging van attributen zou de widget een signaal changed() moeten uitzenden om het dialoogvenster Eigenschappen het voorbeeld van het symbool bij te laten werken.

Nu missen we alleen nog de uiteindelijke lijm: om QGIS zich bewust te laten worden van deze nieuwe klassen. Dit wordt gedaan door de symboollaag toe te voegen aan het register. Het is mogelijk om de symboollaag ook te gebruiken zonder die toe te voegen aan het register, maar sommige functionaliteit zal niet werken: bijv. het laden van projectbestanden met de aangepaste symboollagen of de mogelijkheid om de attributen van de laag te bewerken in de GUI.

We zullen metadata moeten maken voor de symboollaag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry

class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):

  def __init__(self):
    super().__init__("FooMarker", "My new Foo marker", QgsSymbol.Marker)

  def createSymbolLayer(self, props):
    radius = float(props["radius"]) if "radius" in props else 4.0
    return FooSymbolLayer(radius)

QgsApplication.symbolLayerRegistry().addSymbolLayerType(FooSymbolLayerMetadata())

U zou het type laag (hetzelfde als welke wordt teruggegeven door de laag) en type symbool (markering/lijn/vulling) moeten doorgeven aan de constructor van de bovenliggende klasse. De methode createSymbolLayer() zorgt voor het maken van een instance van de symboollaag met attributen die zijn gespecificeerd in het woordenboek props. En er is de methode createSymbolLayerWidget() die de instellingen voor de widget teruggeeft voor dit type symboollaag.

De laatste stap is om deze symboollaag toe te voegen aan het register — en we zijn klaar.

6.7.5. Aangepaste renderers maken

Het zou handig kunnen zijn om een nieuwe implementatie voor de renderer te maken als u de regels voor het selecteren van symbolen voor het renderen van objecten zou willen aanpassen. Sommige gebruiken gevallen waarin u dit zou willen doen: symbool wordt bepaald uit een combinatie van velden, grootte van symbolen wijzigt, afhankelijk van hun huidige schaal etc.

De volgende code geeft een eenvoudige aangepaste renderer weer die twee markeringssymbolen maakt en er, willekeurig, één kiest voor elk object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import random
from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer


class RandomRenderer(QgsFeatureRenderer):
  def __init__(self, syms=None):
    super().__init__("RandomRenderer")
    self.syms = syms if syms else [
      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point)),
      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
    ]

  def symbolForFeature(self, feature, context):
    return random.choice(self.syms)

  def startRender(self, context, fields):
    super().startRender(context, fields)
    for s in self.syms:
      s.startRender(context, fields)

  def stopRender(self, context):
    super().stopRender(context)
    for s in self.syms:
      s.stopRender(context)

  def usedAttributes(self, context):
    return []

  def clone(self):
    return RandomRenderer(self.syms)

De constructor van de bovenliggende klasse QgsFeatureRenderer heeft de naam van de renderer nodig (die uniek moet zijn voor alle renderers). De methode symbolForFeature is die welke bepaalt welk symbool zal worden gebruikt voor een bepaald object. startRender en stopRender zorgen voor initialisatie/finalisatie van het renderen van het symbool. De methode usedAttributes kan een lijst met veldnamen teruggeven waarvan de renderer verwacht dat die aanwezig is. Tenslotte zou de methode clone een kopie van de renderer moeten teruggeven.

Net als met symboollagen is het mogelijk een GUI toe te voegen voor de configuratie van de renderer. Die moet worden afgeleid uit QgsRendererWidget. De volgende voorbeeldcode maakt een knop die de gebruiker in staat stelt het eerste symbool in te stellen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from qgis.gui import QgsRendererWidget, QgsColorButton


class RandomRendererWidget(QgsRendererWidget):
  def __init__(self, layer, style, renderer):
    super().__init__(layer, style)
    if renderer is None or renderer.type() != "RandomRenderer":
      self.r = RandomRenderer()
    else:
      self.r = renderer
    # setup UI
    self.btn1 = QgsColorButton()
    self.btn1.setColor(self.r.syms[0].color())
    self.vbox = QVBoxLayout()
    self.vbox.addWidget(self.btn1)
    self.setLayout(self.vbox)
    self.btn1.colorChanged.connect(self.setColor1)

  def setColor1(self):
    color = self.btn1.color()
    if not color.isValid(): return
    self.r.syms[0].setColor(color)

  def renderer(self):
    return self.r

De constructor ontvangt instances van de actieve laag (QgsVectorLayer), de globale opmaak (QgsStyle) en huidige renderer. Indien er geen renderer is of de renderer heeft een andere type, zal die worden vervangen door onze nieuwe renderer, anders zullen we de huidige renderer gebruiken (die al het type heeft dat we nodig hebben). De inhoud van de widget zou moeten worden bijgewerkt om de huidige staat van de renderer weer te geven. Wanneer het dialoogvenster van de renderer wordt geaccepteerd, wordt de methode voor de widget renderer aangeroepen om de huidige renderer te verkrijgen — die zal worden toegewezen aan de laag.

Het laatste ontbrekende gedeelte zijn de metadata voor de renderer en het registreren in het register, anders zal het laden van de lagen met de renderer niet werken en zal de gebruiker niet in staat zijn die te selecteren uit de lijst met renderers. Laten we ons voorbeeld RandomRenderer voltooien

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from qgis.core import (
  QgsRendererAbstractMetadata,
  QgsRendererRegistry,
  QgsApplication
)

class RandomRendererMetadata(QgsRendererAbstractMetadata):

  def __init__(self):
    super().__init__("RandomRenderer", "Random renderer")

  def createRenderer(self, element):
    return RandomRenderer()

  def createRendererWidget(self, layer, style, renderer):
    return RandomRendererWidget(layer, style, renderer)

QgsApplication.rendererRegistry().addRenderer(RandomRendererMetadata())

Soortgelijk als met de symboollagen, verwacht de constructor voor abstracte metadata de naam van de renderer, de zichtbare naam voor de gebruikers en optioneel de naam van het pictogram voor de renderer. De methode createRenderer geeft de instance QDomElement door die kan worden gebruikt om de status van de renderer opnieuw op te slaan in de boom van de DOM. De methode createRendererWidget maakt het widget voor de configuratie. Die hoeft niet aanwezig te zijn of mag None teruggeven als de renderer geen GUI heeft.

U kunt, om een pictogram te associëren met de renderer, die toewijzen in de constructor QgsRendererAbstractMetadata als een derde (optioneel) argument — de basis klassse-constructor in de functie __init__() van de RandomRendererMetadata wordt

QgsRendererAbstractMetadata.__init__(self,
       "RandomRenderer",
       "Random renderer",
       QIcon(QPixmap("RandomRendererIcon.png", "png")))

Het pictogram kan ook op een later tijdstip worden geassocieerd met de methode setIcon van de klasse van de metadata. Het pictogram kan worden geladen vanuit een bestand (zoals hierboven weergegeven) of kan worden geladen vanuit een Qt resource (PyQt5 bevat .qrc compiler voor Python).

6.8. Meer onderwerpen

TODO:

  • symbolen maken/aanpassen

  • werken met stijl (QgsStyle)

  • werken met kleurverlopen (QgsColorRamp)

  • symboollaag en registraties van renderer verkennen