Viktigt

Översättning är en gemenskapsinsats du kan gå med i. Den här sidan är för närvarande översatt till 100.00%.

6. Använda vektorlager

Råd

Kodsnuttarna på den här sidan behöver följande import om du befinner dig utanför pyqgis-konsolen:

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

I detta avsnitt sammanfattas olika åtgärder som kan göras med vektorlager.

Det mesta arbetet här baseras på metoderna i klassen QgsVectorLayer.

6.1. Hämta information om attribut

Du kan hämta information om de fält som associeras med ett vektorskikt genom att anropa fields() på ett QgsVectorLayer-objekt:

vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
for field in vlayer.fields():
    print(field.name(), field.typeName())
 1fid Integer64
 2id Integer64
 3scalerank Integer64
 4featurecla String
 5type String
 6name String
 7abbrev String
 8location String
 9gps_code String
10iata_code String
11wikipedia String
12natlscale Real

Metoderna displayField() och mapTipTemplate() ger information om det fält och den mall som används i fliken Egenskaper för display.

När du laddar ett vektorlager väljs alltid ett fält av QGIS som Display Name, medan HTML Map Tip är tomt som standard. Med dessa metoder kan du enkelt få båda:

vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
print(vlayer.displayField())
name

Observera

Om du ändrar Display Name från ett fält till ett uttryck måste du använda displayExpression() istället för displayField().

6.2. Iteration över vektorlager

Att iterera över funktionerna i ett vektorlager är en av de vanligaste uppgifterna. Nedan följer ett exempel på en enkel grundläggande kod för att utföra denna uppgift och visa lite information om varje funktion. Variabeln layer antas ha ett objekt av QgsVectorLayer.

 1# "layer" is a QgsVectorLayer instance
 2layer = iface.activeLayer()
 3features = layer.getFeatures()
 4
 5for feature in features:
 6    # retrieve every feature with its geometry and attributes
 7    print("Feature ID: ", feature.id())
 8    # fetch geometry
 9    # show some information about the feature geometry
10    geom = feature.geometry()
11    geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
12    if geom.type() == QgsWkbTypes.PointGeometry:
13        # the geometry type can be of single or multi type
14        if geomSingleType:
15            x = geom.asPoint()
16            print("Point: ", x)
17        else:
18            x = geom.asMultiPoint()
19            print("MultiPoint: ", x)
20    elif geom.type() == QgsWkbTypes.LineGeometry:
21        if geomSingleType:
22            x = geom.asPolyline()
23            print("Line: ", x, "length: ", geom.length())
24        else:
25            x = geom.asMultiPolyline()
26            print("MultiLine: ", x, "length: ", geom.length())
27    elif geom.type() == QgsWkbTypes.PolygonGeometry:
28        if geomSingleType:
29            x = geom.asPolygon()
30            print("Polygon: ", x, "Area: ", geom.area())
31        else:
32            x = geom.asMultiPolygon()
33            print("MultiPolygon: ", x, "Area: ", geom.area())
34    else:
35        print("Unknown or invalid geometry")
36    # fetch attributes
37    attrs = feature.attributes()
38    # attrs is a list. It contains all the attribute values of this feature
39    print(attrs)
40    # for this test only print the first feature
41    break
Feature ID:  1
Point:  <QgsPointXY: POINT(7 45)>
[1, 'First feature']

6.3. Välja funktioner

I QGIS desktop kan funktioner väljas på olika sätt: användaren kan klicka på en funktion, rita en rektangel på kartbilden eller använda ett uttrycksfilter. Markerade funktioner är normalt markerade i en annan färg (standard är gul) för att dra användarens uppmärksamhet till markeringen.

Ibland kan det vara användbart att programmatiskt välja funktioner eller att ändra standardfärgen.

För att välja alla funktioner kan metoden selectAll() användas:

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

Om du vill välja med hjälp av ett uttryck använder du metoden selectByExpression():

# 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)

Om du vill ändra markeringsfärgen kan du använda metoden setSelectionColor() i QgsMapCanvas enligt följande exempel:

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

Om du vill lägga till funktioner i listan över valda funktioner för ett visst lager kan du anropa select() och skicka listan över funktionernas ID:n till den:

1selected_fid = []
2
3# Get the first feature id from the layer
4feature = next(layer.getFeatures())
5if feature:
6    selected_fid.append(feature.id())
7
8# Add that features to the selected list
9layer.select(selected_fid)

För att rensa valet:

layer.removeSelection()

6.3.1. Tillgång till attribut

Attribut kan refereras till med sitt namn:

print(feature['name'])
First feature

Alternativt kan attribut refereras till med index. Detta är lite snabbare än att använda namnet. Till exempel, för att få det andra attributet:

print(feature[1])
First feature

6.3.2. Iteration över utvalda funktioner

Om du bara behöver utvalda funktioner kan du använda metoden selectedFeatures() från vektorlagret:

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

6.3.3. Iteration över en delmängd av funktioner

Om du vill iterera över en given delmängd av funktioner i ett skikt, t.ex. de som finns inom ett givet område, måste du lägga till ett QgsFeatureRequest-objekt till getFeatures()-anropet. Här är ett exempel:

1areaOfInterest = QgsRectangle(450290,400520, 450750,400780)
2
3request = QgsFeatureRequest().setFilterRect(areaOfInterest)
4
5for feature in layer.getFeatures(request):
6    # do whatever you need with the feature
7    pass

För snabbhetens skull görs skärningen ofta bara med hjälp av funktionens avgränsningsbox. Det finns dock en flagga ExactIntersect som ser till att endast korsande funktioner returneras:

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

Med setLimit() kan du begränsa antalet begärda funktioner. Här är ett exempel:

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

Om du behöver ett attributbaserat filter i stället för (eller utöver) ett rumsligt filter som visas i exemplen ovan kan du bygga ett QgsExpression-objekt och skicka det till QgsFeatureRequest-konstruktören. Här är ett exempel:

# 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)

Se Uttryck, filtrering och beräkning av värden för detaljer om den syntax som stöds av QgsExpression.

Begäran kan användas för att definiera de data som hämtas för varje funktion, så att iteratorn returnerar alla funktioner, men returnerar partiella data för var och en av dem.

 1# Only return selected fields to increase the "speed" of the request
 2request.setSubsetOfAttributes([0,2])
 3
 4# More user friendly version
 5request.setSubsetOfAttributes(['name','id'],layer.fields())
 6
 7# Don't return geometry objects to increase the "speed" of the request
 8request.setFlags(QgsFeatureRequest.NoGeometry)
 9
10# Fetch only the feature with id 45
11request.setFilterFid(45)
12
13# The options may be chained
14request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])

6.4. Modifiera vektorlager

De flesta vektordataleverantörer stöder redigering av lagerdata. Ibland stöder de bara en delmängd av möjliga redigeringsåtgärder. Använd funktionen capabilities() för att ta reda på vilken uppsättning funktioner som stöds.

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

För en lista över alla tillgängliga funktioner, se API Documentation of QgsVectorDataProvider.

Om du vill skriva ut en textbeskrivning av skiktets kapacitet i en kommaseparerad lista kan du använda capabilitiesString() som i följande exempel:

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

Genom att använda någon av följande metoder för redigering av vektorlager överförs ändringarna direkt till det underliggande datalagret (en fil, databas etc.). Om du bara vill göra tillfälliga ändringar kan du hoppa till nästa avsnitt som förklarar hur du gör modifieringar med redigeringsbuffert.

Observera

Om du arbetar i QGIS (antingen från konsolen eller från ett insticksprogram) kan det vara nödvändigt att tvinga fram en ny ritning av kartbilden för att se de ändringar du har gjort i geometrin, stilen eller attributen:

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

6.4.1. Lägg till funktioner

Skapa några QgsFeature-instanser och skicka en lista över dem till providern QgsVectorDataProvider addFeatures()-metoden. Den returnerar två värden: resultat (True eller False) och en lista över tillagda funktioner (deras ID anges av datalagret).

För att ställa in attributen för funktionen kan du antingen initiera funktionen genom att skicka ett QgsFields-objekt (du kan få det från metoden fields() i vektorlagret) eller anropa initAttributes() genom att skicka det antal fält du vill lägga till.

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

6.4.2. Ta bort funktioner

För att ta bort vissa funktioner behöver du bara ange en lista med deras funktions-ID.

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

6.4.3. Modifiera funktioner

Det är möjligt att antingen ändra funktionens geometri eller att ändra vissa attribut. I följande exempel ändras först värdena för attribut med index 0 och 1 och därefter ändras objektets geometri.

1fid = 100   # ID of the feature we will modify
2
3if caps & QgsVectorDataProvider.ChangeAttributeValues:
4    attrs = { 0 : "hello", 1 : 123 }
5    layer.dataProvider().changeAttributeValues({ fid : attrs })
6
7if caps & QgsVectorDataProvider.ChangeGeometries:
8    geom = QgsGeometry.fromPointXY(QgsPointXY(111,222))
9    layer.dataProvider().changeGeometryValues({ fid : geom })

Tips

Fördela klassen QgsVectorLayerEditUtils för redigering av enbart geometri

Om du bara behöver ändra geometrier kan du överväga att använda QgsVectorLayerEditUtils som tillhandahåller några användbara metoder för att redigera geometrier (översätta, infoga eller flytta vertex etc.).

6.4.4. Modifiera vektorlager med en redigeringsbuffert

När du redigerar vektorer i QGIS-programmet måste du först starta redigeringsläget för ett visst lager, sedan göra några ändringar och slutligen överföra (eller återställa) ändringarna. Alla ändringar som du gör skrivs inte förrän du överför dem - de ligger kvar i skiktets redigeringsbuffert i minnet. Det är möjligt att använda den här funktionen även programmatiskt — det är bara en annan metod för redigering av vektorlager som kompletterar den direkta användningen av dataleverantörer. Använd detta alternativ när du tillhandahåller några GUI-verktyg för redigering av vektorlager, eftersom det gör det möjligt för användaren att bestämma om han/hon vill göra en commit/rollback och tillåter användning av undo/redo. När ändringar bekräftas sparas alla ändringar från redigeringsbufferten till dataleverantören.

Metoderna liknar dem som vi har sett i providern, men de anropas på QgsVectorLayer-objektet istället.

För att dessa metoder ska fungera måste lagret vara i redigeringsläge. För att starta redigeringsläget, använd metoden startEditing(). För att stoppa redigeringen använder du metoderna commitChanges() eller rollBack(). Den första metoden överför alla dina ändringar till datakällan, medan den andra metoden kasserar dem och inte ändrar datakällan alls.

Om du vill ta reda på om ett lager är i redigeringsläge använder du metoden isEditable().

Här har du några exempel som visar hur du kan använda dessa redigeringsmetoder.

 1from qgis.PyQt.QtCore import QMetaType
 2
 3feat1 = feat2 = QgsFeature(layer.fields())
 4fid = 99
 5feat1.setId(fid)
 6
 7# add two features (QgsFeature instances)
 8layer.addFeatures([feat1,feat2])
 9# delete a feature with specified ID
10layer.deleteFeature(fid)
11
12# set new geometry (QgsGeometry instance) for a feature
13geometry = QgsGeometry.fromWkt("POINT(7 45)")
14layer.changeGeometry(fid, geometry)
15# update an attribute with given field index (int) to a given value
16fieldIndex =1
17value ='My new name'
18layer.changeAttributeValue(fid, fieldIndex, value)
19
20# add new field
21layer.addAttribute(QgsField("mytext", QMetaType.Type.QString))
22# remove a field
23layer.deleteAttribute(fieldIndex)

För att undo/redo ska fungera korrekt måste de ovan nämnda anropen paketeras in i undokommandon. (Om du inte bryr dig om undo/redo och vill att ändringarna ska lagras omedelbart är det enklare att :ref:redigera med dataleverantör <editing>.)

Så här kan du använda funktionen för att ångra:

 1layer.beginEditCommand("Feature triangulation")
 2
 3# ... call layer's editing methods ...
 4
 5if problem_occurred:
 6  layer.destroyEditCommand()
 7  # ... tell the user that there was a problem
 8  # and return
 9
10# ... more editing ...
11
12layer.endEditCommand()

Metoden beginEditCommand() skapar ett internt ”aktivt” kommando och registrerar efterföljande ändringar i vektorlagret. Med anropet till endEditCommand() flyttas kommandot till ångra-stacken och användaren kommer att kunna ångra/återställa det från GUI. Om något gick fel när ändringarna gjordes kommer metoden destroyEditCommand() att ta bort kommandot och återställa alla ändringar som gjorts medan kommandot var aktivt.

Du kan också använda with edit(layer)-satsen för att packa in commit och rollback i ett mer semantiskt kodblock, som i exemplet nedan:

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

Detta kommer automatiskt att anropa commitChanges() i slutet. Om något undantag inträffar kommer det att rollBack() alla ändringar. Om ett problem uppstår inom commitChanges() (när metoden returnerar False) kommer ett QgsEditError undantag att tas upp.

6.4.5. Lägga till och ta bort fält

Om du vill lägga till fält (attribut) måste du ange en lista med fältdefinitioner. För borttagning av fält behöver du bara ange en lista med fältindex.

1from qgis.PyQt.QtCore import QMetaType
2
3if caps & QgsVectorDataProvider.AddAttributes:
4    res = layer.dataProvider().addAttributes(
5        [QgsField("mytext", QMetaType.Type.QString),
6        QgsField("myint", QMetaType.Type.Int)])
7
8if caps & QgsVectorDataProvider.DeleteAttributes:
9    res = layer.dataProvider().deleteAttributes([0])
 1# Alternate methods for removing fields
 2# first create temporary fields to be removed (f1-3)
 3layer.dataProvider().addAttributes([QgsField("f1", QMetaType.Type.Int),
 4                                    QgsField("f2", QMetaType.Type.Int),
 5                                    QgsField("f3", QMetaType.Type.Int)])
 6layer.updateFields()
 7count=layer.fields().count() # count of layer fields
 8ind_list=list((count-3, count-2)) # create list
 9
10# remove a single field with an index
11layer.dataProvider().deleteAttributes([count-1])
12
13# remove multiple fields with a list of indices
14layer.dataProvider().deleteAttributes(ind_list)

När du har lagt till eller tagit bort fält i dataleverantören måste skiktets fält uppdateras eftersom ändringarna inte sprids automatiskt.

layer.updateFields()

Tips

Spara ändringar direkt med hjälp av med baserat kommando

Genom att använda with edit(layer): kommer ändringarna att överföras automatiskt genom att anropa commitChanges() i slutet. Om något undantag inträffar kommer det att rollBack() alla ändringar. Se :ref:redigeringsbuffert.

6.5. Använda rumsligt index

Spatiala index kan dramatiskt förbättra prestandan i din kod om du behöver göra frekventa förfrågningar till ett vektorskikt. Tänk dig till exempel att du skriver en interpoleringsalgoritm och att du för en given plats behöver veta de 10 närmaste punkterna från ett punktlager för att kunna använda dessa punkter för att beräkna det interpolerade värdet. Utan ett rumsligt index är det enda sättet för QGIS att hitta dessa 10 punkter att beräkna avståndet från varje punkt till den angivna platsen och sedan jämföra dessa avstånd. Detta kan vara en mycket tidskrävande uppgift, särskilt om den måste upprepas för flera platser. Om det finns ett rumsligt index för skiktet blir operationen mycket mer effektiv.

Tänk på ett lager utan rumsligt index som en telefonbok där telefonnumren inte är ordnade eller indexerade. Det enda sättet att hitta telefonnumret till en viss person är att läsa från början tills du hittar det.

Rumsliga index skapas inte som standard för ett QGIS-vektorlager, men du kan skapa dem enkelt. Det här är vad du behöver göra:

  • skapa ett spatialt index med hjälp av QgsSpatialIndex:

    index = QgsSpatialIndex()
    
  • add features to index — index tar QgsFeature objekt och lägger till det i den interna datastrukturen. Du kan skapa objektet manuellt eller använda ett från ett tidigare anrop till providerns getFeatures()-metod.

    index.addFeature(feat)
    
  • alternativt kan du ladda alla funktioner i ett lager på en gång med hjälp av bulkbelastning

    index = QgsSpatialIndex(layer.getFeatures())
    
  • när det spatiala indexet har fyllts med vissa värden kan du göra några frågor

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

Du kan också använda det spatiala indexet QgsSpatialIndexKDBush. Detta index liknar det standardiserade QgsSpatialIndex men:

  • stöder endast funktioner med en enda punkt

  • är statisk (inga ytterligare funktioner kan läggas till i indexet efter konstruktionen)

  • är mycket snabbare!

  • möjliggör direkt hämtning av den ursprungliga funktionens punkter, utan att ytterligare funktionsförfrågningar krävs

  • stöder verkliga avståndsbaserade sökningar, dvs. returnerar alla punkter inom en radie från en sökpunkt

6.6. Klassen QgsVectorLayerUtils

Klassen QgsVectorLayerUtils innehåller några mycket användbara metoder som du kan använda med vektorlager.

Till exempel förbereder metoden createFeature() en QgsFeature som ska läggas till i ett vektorskikt med alla eventuella begränsningar och standardvärden för varje fält:

vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)

Med metoden getValues() kan du snabbt få fram värdena för ett fält eller uttryck:

1vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
2# select only the first feature to make the output shorter
3vlayer.selectByIds([1])
4val = QgsVectorLayerUtils.getValues(vlayer, "NAME", selectedOnly=True)
5print(val)
(['Sahnewal'], True)

6.7. Skapa vektorlager

Det finns flera sätt att generera en vektorskiktsdatauppsättning:

  • klassen QgsVectorFileWriter: En praktisk klass för att skriva vektorfiler till disk, med antingen ett statiskt anrop till writeAsVectorFormatV3() som sparar hela vektorlagret eller skapa en instans av klassen och utfärda anrop till ärvda addFeature(). Den här klassen stöder alla vektorformat som GDAL stöder (GeoPackage, Shapefile, GeoJSON, KML och andra).

  • klassen QgsVectorLayer: instansierar en dataleverantör som tolkar den angivna sökvägen (url) till datakällan för att ansluta till och komma åt data. Den kan användas för att skapa tillfälliga, minnesbaserade lager (memory) och ansluta till GDAL-vektordataset (ogr), databaser (postgres, spatialite, mysql, mssql) och mer (wfs, gpx, delimitedtext…).

6.7.1. Från en instans av QgsVectorFileWriter

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

Du kan också konvertera fält för att göra dem kompatibla med olika format genom att använda FieldValueConverter. Om du t.ex. vill konvertera arrayvariabeltyper (t.ex. i Postgres) till en texttyp kan du göra följande:

 1LIST_FIELD_NAME = 'xxxx'
 2
 3class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):
 4
 5  def __init__(self, layer, list_field):
 6    QgsVectorFileWriter.FieldValueConverter.__init__(self)
 7    self.layer = layer
 8    self.list_field_idx = self.layer.fields().indexFromName(list_field)
 9
10  def convert(self, fieldIdxInLayer, value):
11    if fieldIdxInLayer == self.list_field_idx:
12      return QgsListFieldFormatter().representValue(layer=vlayer,
13                                                    fieldIndex=self.list_field_idx,
14                                                    config={},
15                                                    cache=None,
16                                                    value=value)
17    else:
18      return value
19
20  def fieldDefinition(self, field):
21    idx = self.layer.fields().indexFromName(field.name())
22    if idx == self.list_field_idx:
23      return QgsField(LIST_FIELD_NAME, QMetaType.Type.QString)
24    else:
25      return self.layer.fields()[idx]
26
27converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
28opts = QgsVectorFileWriter.SaveVectorOptions()
29opts.fieldValueConverter = converter

Ett destinations-CRS kan också anges — om en giltig instans av QgsCoordinateReferenceSystem skickas som den fjärde parametern, transformeras lagret till det CRS:et.

För giltiga drivrutinsnamn, anropa metoden supportedFiltersAndFormats() eller konsultera supported formats by OGR — du bör skicka värdet i kolumnen ”Code” som drivrutinsnamn.

Alternativt kan du ange om du bara vill exportera utvalda funktioner, skicka ytterligare drivrutinsspecifika alternativ för skapande eller tala om för skrivaren att inte skapa attribut… Det finns ett antal andra (valfria) parametrar; se QgsVectorFileWriter-dokumentationen för detaljer.

6.7.2. Direkt från funktioner

 1from qgis.PyQt.QtCore import QMetaType
 2
 3# define fields for feature attributes. A QgsFields object is needed
 4fields = QgsFields()
 5fields.append(QgsField("first", QMetaType.Type.Int))
 6fields.append(QgsField("second", QMetaType.Type.QString))
 7
 8""" create an instance of vector file writer, which will create the vector file.
 9Arguments:
101. path to new file (will fail if exists already)
112. field map
123. geometry type - from WKBTYPE enum
134. layer's spatial reference (instance of
14   QgsCoordinateReferenceSystem)
155. coordinate transform context
166. save options (driver name for the output file, encoding etc.)
17"""
18
19crs = QgsProject.instance().crs()
20transform_context = QgsProject.instance().transformContext()
21save_options = QgsVectorFileWriter.SaveVectorOptions()
22save_options.driverName = "ESRI Shapefile"
23save_options.fileEncoding = "UTF-8"
24
25writer = QgsVectorFileWriter.create(
26  "testdata/my_new_shapefile.shp",
27  fields,
28  QgsWkbTypes.Point,
29  crs,
30  transform_context,
31  save_options
32)
33
34if writer.hasError() != QgsVectorFileWriter.NoError:
35    print("Error when creating shapefile: ",  writer.errorMessage())
36
37# add a feature
38fet = QgsFeature()
39
40fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
41fet.setAttributes([1, "text"])
42writer.addFeature(fet)
43
44# delete the writer to flush features to disk
45del writer

6.7.3. Från en instans av QgsVectorLayer

Bland alla dataleverantörer som stöds av QgsVectorLayer-klassen, låt oss fokusera på de minnesbaserade lagren. Memory provider är avsedd att användas främst av plugin- eller tredjepartsapputvecklare. Den lagrar inte data på disk, vilket gör att utvecklare kan använda den som en snabb backend för vissa tillfälliga lager.

Providern stöder string-, int- och double-fält.

Minnesprovidern stöder även spatial indexering, som aktiveras genom att anropa providerns createSpatialIndex()-funktion. När det spatiala indexet har skapats kommer du att kunna iterera över funktioner inom mindre regioner snabbare (eftersom det inte är nödvändigt att traversera alla funktioner, bara de som finns i den angivna rektangeln).

En minnesprovider skapas genom att skicka "memory" som providersträng till QgsVectorLayer-konstruktören.

Konstruktorn tar också en URI som definierar geometritypen för lagret, en av följande: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" eller "None".

URI:n kan också ange koordinatreferenssystem, fält och indexering för minnesprovidern i URI:n. Syntaxen är:

crs=definition

Anger koordinatreferenssystemet, där definitionen kan vara någon av de former som accepteras av QgsCoordinateReferenceSystem.createFromString()

index=yes

Anger att leverantören kommer att använda ett rumsligt index

field=name:type(length,precision)

Anger ett attribut för lagret. Attributet har ett namn och eventuellt en typ (heltal, dubbel eller sträng), längd och precision. Det kan finnas flera fältdefinitioner.

Följande exempel på en URI innehåller alla dessa alternativ

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

Följande exempel på kod illustrerar hur man skapar och fyller i en minnesprovider

 1from qgis.PyQt.QtCore import QDateTime, QMetaType, Qt
 2
 3# create layer
 4vl = QgsVectorLayer("Point", "temporary_points", "memory")
 5pr = vl.dataProvider()
 6
 7# add fields
 8pr.addAttributes([QgsField("name", QMetaType.Type.QString),
 9                    QgsField("age",  QMetaType.Type.Int),
10                    QgsField("size", QMetaType.Type.Double),
11                    QgsField("birthday", QMetaType.Type.QDateTime)])
12vl.updateFields() # tell the vector layer to fetch changes from the provider
13
14# add a feature
15fet = QgsFeature()
16fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
17fmt = Qt.DateFormat.ISODate
18t = QDateTime.fromString("2000-01-01T12:00:00", fmt)
19fet.setAttributes(["Johnny", 2, 0.3, t])
20pr.addFeatures([fet])
21
22# update layer's extent when new features have been added
23# because change of extent in provider is not propagated to the layer
24vl.updateExtents()

Låt oss slutligen kontrollera om allt gick bra

 1# show some stats
 2print("fields:", len(pr.fields()))
 3print("features:", pr.featureCount())
 4e = vl.extent()
 5print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
 6
 7# iterate over features
 8features = vl.getFeatures()
 9for fet in features:
10    print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
fields: 4
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johnny', 2, 0.3, PyQt5.QtCore.QDateTime(2000, 1, 1, 12, 0)] <QgsPointXY: POINT(10 10)>

6.8. Utseende (symbologi) för vektorskikt

När ett vektorlager renderas ges datans utseende av renderer och symboler som är associerade med lagret. Symboler är klasser som tar hand om ritning av visuell representation av funktioner, medan renderare bestämmer vilken symbol som ska användas för en viss funktion.

Renderingsprogrammet för ett visst lager kan erhållas enligt nedan:

renderer = layer.renderer()

Och med den referensen, låt oss utforska den lite

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

Det finns flera kända renderingstyper tillgängliga i QGIS kärnbibliotek:

Typ

Klass

Beskrivning

singleSymbol

QgsSingleSymbolRenderer

Renderar alla funktioner med samma symbol

categorizedSymbol

QgsCategorizedSymbolRenderer

Rendering av funktioner med olika symboler för varje kategori

graduatedSymbol

QgsGraduatedSymbolRenderer

Renderar funktioner med en annan symbol för varje intervall av värden

Det kan också finnas några anpassade renderingstyper, så gör aldrig ett antagande att det bara finns dessa typer. Du kan fråga applikationens QgsRendererRegistry för att ta reda på vilka renderare som finns tillgängliga för närvarande:

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

Det är möjligt att få en dumpning av en renderingsenhets innehåll i textform — kan vara användbart för felsökning

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

6.8.1. Renderare för en enda symbol

Du kan få symbolen som används för rendering genom att anropa symbol()-metoden och ändra den med setSymbol()-metoden (notering för C++-utvecklare: renderingsprogrammet tar äganderätten till symbolen.)

Du kan ändra symbolen som används av ett visst vektorlager genom att anropa setSymbol() och skicka en instans av den lämpliga symbolinstansen. Symboler för punkt-, linje- och polygon-lager kan skapas genom att anropa createSimple()-funktionen i motsvarande klasser QgsMarkerSymbol, QgsLineSymbol och QgsFillSymbol.

Den ordbok som skickas till createSimple() anger symbolens stilegenskaper.

Du kan t.ex. ersätta den symbol som används av ett visst punkt-lager genom att anropa setSymbol() och skicka en instans av en QgsMarkerSymbol, som i följande kodexempel:

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

namn anger markörens form och kan vara något av följande:

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

För att få en fullständig lista över egenskaper för det första symbollagret i en symbolinstans kan du följa exempelkoden:

print(layer.renderer().symbol().symbolLayers()[0].properties())
{'angle': '0', 'cap_style': 'square', 'color': '255,0,0,255,rgb:1,0,0,1', '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,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1', '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'}

Detta kan vara användbart om du vill ändra vissa egenskaper:

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

6.8.2. Kategoriserad symbolrendering

När du använder en kategoriserad renderare kan du fråga efter och ställa in attributet som används för klassificering: använd metoderna classAttribute() och setClassAttribute().

För att få en lista över kategorier

1categorized_renderer = QgsCategorizedSymbolRenderer()
2# Add a few categories
3cat1 = QgsRendererCategory('1', QgsMarkerSymbol(), 'category 1')
4cat2 = QgsRendererCategory('2', QgsMarkerSymbol(), 'category 2')
5categorized_renderer.addCategory(cat1)
6categorized_renderer.addCategory(cat2)
7
8for cat in categorized_renderer.categories():
9    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>

Där value() är värdet som används för diskriminering mellan kategorier, label() är en text som används för kategoribeskrivning och symbol()-metoden returnerar den tilldelade symbolen.

Renderaren lagrar vanligtvis även originalsymbolen och färgrampen som användes för klassificeringen: sourceColorRamp() och sourceSymbol() metoder.

6.8.3. Rendering av graderad symbol

Denna rendering är mycket lik den kategoriserade symbolrendering som beskrivs ovan, men i stället för ett attributvärde per klass arbetar den med värdeintervall och kan därför endast användas med numeriska attribut.

Om du vill veta mer om intervall som används i renderingsprogrammet

 1graduated_renderer = QgsGraduatedSymbolRenderer()
 2# Add a few categories
 3graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 0-100', 0, 100), QgsMarkerSymbol()))
 4graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 101-200', 101, 200), QgsMarkerSymbol()))
 5
 6for ran in graduated_renderer.ranges():
 7    print("{} - {}: {} {}".format(
 8        ran.lowerValue(),
 9        ran.upperValue(),
10        ran.label(),
11        ran.symbol()
12      ))
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>

kan du återigen använda classAttribute() (för att hitta klassificeringsattributets namn), sourceSymbol() och sourceColorRamp() metoder. Dessutom finns metoden mode() som bestämmer hur intervallen skapades: med hjälp av lika intervall, kvantiler eller någon annan metod.

Om du vill skapa din egen graderade symbolrendering kan du göra det enligt exemplet nedan (som skapar ett enkelt tvåklassigt arrangemang)

 1from qgis.PyQt import QtGui
 2
 3myVectorLayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
 4myTargetField = 'scalerank'
 5myRangeList = []
 6myOpacity = 1
 7# Make our first symbol and range...
 8myMin = 0.0
 9myMax = 50.0
10myLabel = 'Group 1'
11myColour = QtGui.QColor('#ffee00')
12mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
13mySymbol1.setColor(myColour)
14mySymbol1.setOpacity(myOpacity)
15myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, myLabel)
16myRangeList.append(myRange1)
17#now make another symbol and range...
18myMin = 50.1
19myMax = 100
20myLabel = 'Group 2'
21myColour = QtGui.QColor('#00eeff')
22mySymbol2 = QgsSymbol.defaultSymbol(
23     myVectorLayer.geometryType())
24mySymbol2.setColor(myColour)
25mySymbol2.setOpacity(myOpacity)
26myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
27myRangeList.append(myRange2)
28myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
29myClassificationMethod = QgsApplication.classificationMethodRegistry().method("EqualInterval")
30myRenderer.setClassificationMethod(myClassificationMethod)
31myRenderer.setClassAttribute(myTargetField)
32
33myVectorLayer.setRenderer(myRenderer)

6.8.4. Arbeta med symboler

För representation av symboler finns QgsSymbol basklass med tre härledda klasser:

Varje symbol består av ett eller flera symbolskikt (klasser som härrör från QgsSymbolLayer). Symbolskikten gör den faktiska renderingen, symbolklassen i sig fungerar bara som en behållare för symbolskikten.

Om du har en instans av en symbol (t.ex. från en renderare) är det möjligt att utforska den: metoden type() anger om det är en markör-, linje- eller fyllningssymbol. Det finns en dump()-metod som returnerar en kort beskrivning av symbolen. För att få en lista över symbollager:

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

För att ta reda på symbolens färg använd color() metod och setColor() för att ändra dess färg. Med markörsymboler kan du dessutom fråga efter symbolens storlek och rotation med metoderna size() och angle(). För linjesymboler returnerar metoden width() linjebredden.

Storlek och bredd är i millimeter som standard, vinklar är i grader.

6.8.4.1. Arbeta med symbolskikt

Som tidigare nämnts är det symbolskikten (underklasser till QgsSymbolLayer) som bestämmer hur funktionerna ska se ut. Det finns flera grundläggande symbolskiktsklasser för allmänt bruk. Det är möjligt att implementera nya symbolskiktstyper och därmed godtyckligt anpassa hur funktioner ska återges. Metoden layerType() identifierar symbolskiktsklassen på ett unikt sätt — de grundläggande och standardiserade är symbolskiktstyperna SimpleMarker, SimpleLine och SimpleFill.

Du kan få en fullständig lista över de typer av symbollager som du kan skapa för en viss symbollagerklass med följande kod:

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

Klassen QgsSymbolLayerRegistry hanterar en databas med alla tillgängliga symbolskiktstyper.

För att komma åt symbolskiktsdata använder du dess properties()-metod som returnerar en nyckelvärdesordbok med egenskaper som bestämmer utseendet. Varje symbolskiktstyp har en specifik uppsättning egenskaper som den använder. Dessutom finns det de generiska metoderna color(), size(), angle() och width(), med deras motsvarigheter i setter. Naturligtvis är storlek och vinkel endast tillgängliga för markörsymbolskikt och bredd för linjesymbolskikt.

6.8.4.2. Skapa egna typer av symbolskikt

Tänk dig att du skulle vilja anpassa hur data återges. Du kan skapa din egen symbollagerklass som ritar funktionerna exakt som du vill. Här är ett exempel på en markör som ritar röda cirklar med angiven radie

 1from qgis.core import QgsMarkerSymbolLayer
 2from qgis.PyQt.QtGui import QColor
 3
 4class FooSymbolLayer(QgsMarkerSymbolLayer):
 5
 6  def __init__(self, radius=4.0):
 7      QgsMarkerSymbolLayer.__init__(self)
 8      self.radius = radius
 9      self.color = QColor(255,0,0)
10
11  def layerType(self):
12     return "FooMarker"
13
14  def properties(self):
15      return { "radius" : str(self.radius) }
16
17  def startRender(self, context):
18    pass
19
20  def stopRender(self, context):
21      pass
22
23  def renderPoint(self, point, context):
24      # Rendering depends on whether the symbol is selected (QGIS >= 1.5)
25      color = context.selectionColor() if context.selected() else self.color
26      p = context.renderContext().painter()
27      p.setPen(color)
28      p.drawEllipse(point, self.radius, self.radius)
29
30  def clone(self):
31      return FooSymbolLayer(self.radius)

Metoden layerType() bestämmer namnet på symbolskiktet; det måste vara unikt bland alla symbolskikt. Metoden properties() används för att bevara attribut. Metoden clone() måste returnera en kopia av symbolskiktet med alla attribut exakt lika. Slutligen finns det renderingsmetoder: startRender() anropas innan den första funktionen renderas, stopRender() när renderingen är klar, och renderPoint() anropas för att göra renderingen. Koordinaterna för punkten/punkterna är redan transformerade till utdatakoordinaterna.

För polylinjer och polygoner skulle den enda skillnaden vara i renderingsmetoden: du skulle använda renderPolyline() som tar emot en lista med linjer, medan renderPolygon() tar emot en lista med punkter på den yttre ringen som första parameter och en lista med inre ringar (eller Ingen) som andra parameter.

Vanligtvis är det bekvämt att lägga till ett GUI för att ställa in attribut för symbollagertypen så att användarna kan anpassa utseendet: i vårt exempel ovan kan vi låta användaren ställa in cirkelradie. Följande kod implementerar en sådan widget

 1from qgis.gui import QgsSymbolLayerWidget
 2
 3class FooSymbolLayerWidget(QgsSymbolLayerWidget):
 4    def __init__(self, parent=None):
 5        QgsSymbolLayerWidget.__init__(self, parent)
 6
 7        self.layer = None
 8
 9        # setup a simple UI
10        self.label = QLabel("Radius:")
11        self.spinRadius = QDoubleSpinBox()
12        self.hbox = QHBoxLayout()
13        self.hbox.addWidget(self.label)
14        self.hbox.addWidget(self.spinRadius)
15        self.setLayout(self.hbox)
16        self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
17            self.radiusChanged)
18
19    def setSymbolLayer(self, layer):
20        if layer.layerType() != "FooMarker":
21            return
22        self.layer = layer
23        self.spinRadius.setValue(layer.radius)
24
25    def symbolLayer(self):
26        return self.layer
27
28    def radiusChanged(self, value):
29        self.layer.radius = value
30        self.emit(SIGNAL("changed()"))

Denna widget kan bäddas in i dialogrutan för symbolegenskaper. När symbollagertypen väljs i dialogrutan för symbolegenskaper skapas en instans av symbollagret och en instans av widgeten för symbollager. Sedan anropas metoden setSymbolLayer() för att tilldela symbollagret till widgeten. I den metoden bör widgeten uppdatera användargränssnittet för att återspegla symbolskiktets attribut. Metoden symbolLayer() används för att hämta symbolskiktet igen via dialogrutan för egenskaper för att använda det för symbolen.

Vid varje ändring av attribut bör widgeten sända ut signalen changed() för att låta dialogrutan för egenskaper uppdatera förhandsgranskningen av symbolen.

Nu saknas bara det sista limmet: att göra QGIS medvetet om dessa nya klasser. Detta görs genom att lägga till symbolskiktet i registret. Det är möjligt att använda symbolskiktet även utan att lägga till det i registret, men vissa funktioner kommer inte att fungera: t.ex. laddning av projektfiler med de anpassade symbolskikten eller oförmåga att redigera skiktets attribut i GUI.

Vi kommer att behöva skapa metadata för symbolskiktet

 1from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry
 2
 3class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):
 4
 5  def __init__(self):
 6    super().__init__("FooMarker", "My new Foo marker", QgsSymbol.Marker)
 7
 8  def createSymbolLayer(self, props):
 9    radius = float(props["radius"]) if "radius" in props else 4.0
10    return FooSymbolLayer(radius)
11
12fslmetadata = FooSymbolLayerMetadata()
QgsApplication.symbolLayerRegistry().addSymbolLayerType(fslmetadata)

Du bör skicka lagertyp (samma som returneras av lagret) och symboltyp (markör/linje/fyllning) till konstruktören för den överordnade klassen. Metoden createSymbolLayer() tar hand om att skapa en instans av symbolskiktet med attribut som anges i ordlistan props. Och det finns metoden createSymbolLayerWidget() som returnerar inställningswidgeten för denna symbolskiktstyp.

Det sista steget är att lägga till detta symbolskikt i registret — och vi är klara.

6.8.5. Skapa anpassade renderingar

Det kan vara bra att skapa en ny renderingsimplementering om du vill anpassa reglerna för hur symboler ska väljas för rendering av funktioner. Några användningsfall där du skulle vilja göra det: symbolen bestäms utifrån en kombination av fält, storleken på symbolerna ändras beroende på aktuell skala etc.

Följande kod visar en enkel anpassad renderare som skapar två markörsymboler och slumpmässigt väljer en av dem för varje objekt

 1import random
 2from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer
 3
 4
 5class RandomRenderer(QgsFeatureRenderer):
 6  def __init__(self, syms=None):
 7    super().__init__("RandomRenderer")
 8    self.syms = syms if syms else [
 9      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point)),
10      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
11    ]
12
13  def symbolForFeature(self, feature, context):
14    return random.choice(self.syms)
15
16  def startRender(self, context, fields):
17    super().startRender(context, fields)
18    for s in self.syms:
19      s.startRender(context, fields)
20
21  def stopRender(self, context):
22    super().stopRender(context)
23    for s in self.syms:
24      s.stopRender(context)
25
26  def usedAttributes(self, context):
27    return []
28
29  def clone(self):
30    return RandomRenderer(self.syms)

Konstruktören för den överordnade klassen QgsFeatureRenderer behöver ett namn på renderingsprogrammet (som måste vara unikt bland renderingsprogrammen). Metoden symbolForFeature() är den som bestämmer vilken symbol som ska användas för en viss funktion. startRender() och stopRender() tar hand om initialisering/avslutning av symbolrendering. Metoden usedAttributes() kan returnera en lista med fältnamn som renderingsprogrammet förväntar sig ska finnas. Slutligen bör metoden clone() returnera en kopia av renderingsprogrammet.

Precis som med symbolskikt är det möjligt att bifoga ett GUI för konfiguration av renderingsprogrammet. Det måste härledas från QgsRendererWidget. Följande exempelkod skapar en knapp som gör det möjligt för användaren att ställa in den första symbolen

 1from qgis.gui import QgsRendererWidget, QgsColorButton
 2
 3
 4class RandomRendererWidget(QgsRendererWidget):
 5  def __init__(self, layer, style, renderer):
 6    super().__init__(layer, style)
 7    if renderer is None or renderer.type() != "RandomRenderer":
 8      self.r = RandomRenderer()
 9    else:
10      self.r = renderer
11    # setup UI
12    self.btn1 = QgsColorButton()
13    self.btn1.setColor(self.r.syms[0].color())
14    self.vbox = QVBoxLayout()
15    self.vbox.addWidget(self.btn1)
16    self.setLayout(self.vbox)
17    self.btn1.colorChanged.connect(self.setColor1)
18
19  def setColor1(self):
20    color = self.btn1.color()
21    if not color.isValid(): return
22    self.r.syms[0].setColor(color)
23
24  def renderer(self):
25    return self.r

Konstruktören tar emot instanser av det aktiva lagret (QgsVectorLayer), den globala stilen (QgsStyle) och den aktuella renderingen. Om det inte finns någon renderare eller om renderaren har en annan typ kommer den att ersättas med vår nya renderare, annars kommer vi att använda den aktuella renderaren (som redan har den typ vi behöver). Widgetens innehåll bör uppdateras för att visa det aktuella läget för renderingsprogrammet. När dialogrutan för rendering accepteras anropas widgetens renderer()-metod för att hämta den aktuella renderingen — den kommer att tilldelas lagret.

Det sista som saknas är metadata för renderingsprogrammet och registrering i registret, annars fungerar inte laddningen av lager med renderingsprogrammet och användaren kan inte välja det från listan över renderingsprogram. Låt oss avsluta vårt RandomRenderer-exempel

 1from qgis.core import (
 2  QgsRendererAbstractMetadata,
 3  QgsRendererRegistry,
 4  QgsApplication
 5)
 6
 7class RandomRendererMetadata(QgsRendererAbstractMetadata):
 8
 9  def __init__(self):
10    super().__init__("RandomRenderer", "Random renderer")
11
12  def createRenderer(self, element):
13    return RandomRenderer()
14
15  def createRendererWidget(self, layer, style, renderer):
16    return RandomRendererWidget(layer, style, renderer)
17
18rrmetadata = RandomRendererMetadata()
QgsApplication.rendererRegistry().addRenderer(rrmetadata)

På samma sätt som med symbolskikt väntar konstruktören för abstrakta metadata på namn på renderingsprogrammet, namn som är synligt för användare och eventuellt namn på renderingsprogrammets ikon. Metoden createRenderer() skickar en instans av QDomElement som kan användas för att återställa renderarens tillstånd från DOM-trädet. Metoden createRendererWidget() skapar konfigurationswidgeten. Den behöver inte vara närvarande eller kan returnera None om renderaren inte har något GUI.

För att associera en ikon med renderingsprogrammet kan du tilldela den i QgsRendererAbstractMetadata-konstruktören som ett tredje (valfritt) argument — basklassens konstruktör i RandomRendererMetadata __init__()-funktionen blir

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

Ikonen kan också associeras vid ett senare tillfälle med hjälp av setIcon()-metoden i metadataklassen. Ikonen kan laddas från en fil (som visas ovan) eller laddas från en Qt-resurs (PyQt5 innehåller .qrc-kompilatorn för Python).

6.9. Ytterligare ämnen

TODO:

  • skapa/ändra symboler

  • arbeta med stil (QgsStyle)

  • arbeta med färgramper (QgsColorRamp)

  • utforska symbolskikt och renderingsregister