Vectorlagen gebruiken

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

Informatie over attributen ophalen

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

# "layer" is a QgsVectorLayer instance
for field in layer.pendingFields():
    print field.name(), field.typeName()

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. Soms kan het nuttig zijn om programmatisch objecten te selecteren of om de standaard kleur te wijzigen.

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 setSelectedFeatures() aanroepen om objecten toe te voegen aan de lijst met geselecteerde objecten voor ene bepaalde laag, die de lijst met ID’s voor de objecten doorgeeft aan de lijst:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
# Get the first feature from the layer
feature = layer.getFeatures().next()
# Add this features to the selected list
layer.setSelectedFeatures([feature.id()])

Geef eenvoudigweg een lege lijst door om de selectie leeg te maken:

layer.setSelectedFeatures([])

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

iter = layer.getFeatures()
for feature in iter:
    # retrieve every feature with its geometry and attributes
    # fetch geometry
    geom = feature.geometry()
    print "Feature ID %d: " % feature.id()

    # show some information about the feature
    if geom.type() == QGis.Point:
        x = geom.asPoint()
        print "Point: " + str(x)
    elif geom.type() == QGis.Line:
        x = geom.asPolyline()
        print "Line: %d points" % len(x)
    elif geom.type() == QGis.Polygon:
        x = geom.asPolygon()
        numPts = 0
        for ring in x:
        numPts += len(ring)
        print "Polygon: %d rings with %d points" % (len(x), numPts)
    else:
        print "Unknown"

    # fetch attributes
    attrs = feature.attributes()

    # attrs is a list. It contains all the attribute values of this feature
    print attrs

Toegang tot attributen

Naar attributen kan worden verwezen door middel van hun naam.

print feature['name']

Als alternatief kan naar attributen worden verwezen door middel van een index. Dit zal iets sneller zijn dan het gebruiken van de naam. Bijvoorbeeld; het eerste attribuut verkrijgen:

print feature[0]

Itereren over geselecteerde objecten

als u alleen objecten moet selecteren, kunt u de methode selectedFeatures() gebruiken uit de vectorlaag:

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

Een andere optie is de methode features() van Processing:

import processing
features = processing.features(layer)
for feature in features:
    # do whatever you need with the feature

Standaard worden hiermee alle objecten in een laag doorlopen indien er geen selectie actief is, of anders over de geselecteerde objecten. Onthoud dat dit gedrag kan worden gewijzigd in de opties voor Processing om selecties te negeren.

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 gegeven gebied, dan dient een object QgsFeatureRequest te worden toegevoegd aan de aanroep getFeatures(). Hier is een voorbeeld:

request = QgsFeatureRequest()
request.setFilterRect(areaOfInterest)
for feature in layer.getFeatures(request):
    # do whatever you need with the feature

Als u in plaats daarvan een op attributen gebaseerd filter nodig heeft (of als aanvulling) van een ruimtelijke zoals weergegeven in het voorbeeld 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)

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.

# Only return selected fields
request.setSubsetOfAttributes([0,2])
# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.pendingFields())
# Don't return geometry objects
request.setFlags(QgsFeatureRequest.NoGeometry)

Tip

Als u slechts een subset van de attributen nodig heeft of u heeft de informatie over de geometrie niet nodig, kunt u de snelheid van het verzoek voor de objecten significant verhogen door de vlag QgsFeatureRequest.NoGeometry te gebruiken of een subset van attributen te specificeren (mogelijk leeg) zoals weergegeven in het bovenstaande voorbeeld.

Vectorlagen bewerken

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

caps = layer.dataProvider().capabilities()

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:

# 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.setCacheImage(None)
else:
    iface.mapCanvas().refresh()

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)

if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature()
    feat.addAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

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

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

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.fromPoint(QgsPoint(111,222))
    layer.dataProvider().changeGeometryValues({ fid : geom })

Tip

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

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.

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

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

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.

Gebruik isEditing() om te zien of een laag in de modus Bewerken staat — de functies voor bewerken werken alleen als de modus Bewerken is ingeschakeld. Gebruiken van functies voor bewerken

# 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
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to given value (QVariant)
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.) Hoe de functionaliteit Ongedaan maken te gebruiken

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
    layer.destroyEditCommand()
   return

# ... more editing ...

layer.endEditCommand()

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.

Er is de methode startEditing() om de modus Bewerken te starten, om te stoppen met bewerken zijn er commitChanges() en rollback() — echter, normaal gesproken zou u deze methoden niet nodig hebben en laat deze functionaliteit te worden geactiveerd door de gebruiker.

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.

  1. ruimtelijke index maken — de volgende code maakt een lege index

    index = QgsSpatialIndex()
    
  2. objecten aan index toevoegen — 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 nextFeature() van de provider

    index.insertFeature(feat)
    
  3. als de ruimtelijke index eenmaal is gevuld met enkele waarden, kunt u enkele query’s uitvoeren

    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPoint(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))
    

Vectorlagen schrijven

U kunt bestanden voor vectorlagen schrijven met behulp van de klasse QgsVectorFileWriter. Het ondersteunt elke andere soort vectorbestand dat wordt ondersteunt door OGR (shapefiles, GeoJSON, KML en andere).

Er zijn twee mogelijkheden voor het exporteren van een vectorlaag:

  • vanuit een instance van QgsVectorLayer

    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_shapes.shp", "CP1250", None, "ESRI Shapefile")
    
    if error == QgsVectorFileWriter.NoError:
        print "success!"
    
    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_json.json", "utf-8", None, "GeoJSON")
    if error == QgsVectorFileWriter.NoError:
        print "success again!"
    

    De derde parameter specificeert de codering voor uit te voeren tekst. Alleen enkele stuurprogramma’s hebben dit nodig voor een juiste verwerking - shapefiles zijn er daar één van — in het geval u echter geen internationale tekens gebruikt, hoeft u zich niet echt druk te maken over de codering. De vierde parameter die we hebben gelaten als None kan het doel-CRS specificeren — als een geldige instance van QgsCoordinateReferenceSystem wordt doorgegeven, wordt de laag naar dat CRS getransformeerd.

    Voor geldige namen van stuurprogramma’s, bekijk supported formats by OGR — u zou de waarde in de kolom “Code” door moeten geven als de naam van het stuurprogramma. Optioneel kunt u instellen om alleen geselecteerde objecten te exporteren, nadere specifieke opties voor het stuurprogramma voor het maken door te geven of de schrijver vertellen geen attributen te maken — bekijk de documentatie voor de volledige syntaxis.

  • direct uit objecten

    # define fields for feature attributes. A list of QgsField objects is needed
    fields = [QgsField("first", QVariant.Int),
              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. encoding of the attributes
    # 3. field map
    # 4. geometry type - from WKBTYPE enum
    # 5. layer's spatial reference (instance of
    #    QgsCoordinateReferenceSystem) - optional
    # 6. driver name for the output file
    writer = QgsVectorFileWriter("my_shapes.shp", "CP1250", fields, QGis.WKBPoint, None, "ESRI Shapefile")
    
    if writer.hasError() != QgsVectorFileWriter.NoError:
        print "Error when creating shapefile: ", writer.hasError()
    
    # add a feature
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
    fet.setAttributes([1, "text"])
    writer.addFeature(fet)
    
    # delete the writer to flush features to disk (optional)
    del writer
    

Memory-provider

Memory-provider is bedoeld om te worden gebruikt door voornamelijk plug-in of ontwikkelaars voor 3e partijen. Het slaat geen gegevens op op de schijf, 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", of "MultiPolygon".

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

# 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.fromPoint(QgsPoint(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

# show some stats
print "fields:", len(pr.fields())
print "features:", pr.featureCount()
e = layer.extent()
print "extent:", e.xMiniminum(), e.yMinimum(), e.xMaximum(), e.yMaximum()

# iterate over features
f = QgsFeature()
features = vl.getFeatures()
for f in features:
    print "F:", f.id(), f.attributes(), f.geometry().asPoint()

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.rendererV2()

En met die verwijzing, laten we het een beetje verkennen

print "Type:", rendererV2.type()

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

Type

Klasse

Omschrijving

singleSymbol QgsSingleSymbolRendererV2

Rendert alle objecten met hetzelfde symbool

categorizedSymbol QgsCategorizedSymbolRendererV2

Rendert objecten door een ander symbool voor elke categorie te gebruiken

graduatedSymbol QgsGraduatedSymbolRendererV2

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 slechts deze typen beschikbaar zijn. U kunt singleton QgsRendererV2Registry bevragen om de huidige beschikbare renderers te achterhalen:

QgsRendererV2Registry.instance().renderersList()
# Prints:
[u'singleSymbol',
u'categorizedSymbol',
u'graduatedSymbol',
u'RuleRenderer',
u'pointDisplacement',
u'invertedPolygonRenderer',
u'heatmapRenderer']

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

print rendererV2.dump()

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 QgsMarkerSymbolV2, QgsLineSymbolV2 en QgsFillSymbolV2.

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 QgsMarkerSymbolV2 zoals in het volgende voorbeeld van code:

symbol = QgsMarkerSymbolV2.createSimple({'name': 'square', 'color': 'red'})
layer.rendererV2().setSymbol(symbol)

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

  • circle
  • square
  • rectangle
  • diamond
  • pentagon
  • triangle
  • equilateral_triangle
  • star
  • regular_star
  • arrow
  • filled_arrowhead

Renderer symbool Categoriën

U kunt de naam van het attribuut, dat gebruikt wordt voor de classificatie, bevragen en instellen: gebruik de methoden: classAttribute() en setClassAttribute().

Een lijst categorieën verkrijgen

for cat in rendererV2.categories():
    print "%s: %s :: %s" % (cat.value().toString(), cat.label(), str(cat.symbol()))

Waar value() de waarde is die wordt gebruikt voor de verdeling in 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().

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

for ran in rendererV2.ranges():
    print "%f - %f: %s %s" % (
        ran.lowerValue(),
        ran.upperValue(),
        ran.label(),
        str(ran.symbol())
      )

U kunt opnieuw classAttribute() gebruiken om de naam van het attribuut voor classificatie te zoeken, methoden sourceSymbol() en sourceColorRamp(). 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)

from qgis.core import *

myVectorLayer = QgsVectorLayer(myVectorPath, myName, 'ogr')
myTargetField = 'target_field'
myRangeList = []
myOpacity = 1
# Make our first symbol and range...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbolV2.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setAlpha(myOpacity)
myRange1 = QgsRendererRangeV2(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 = QgsSymbolV2.defaultSymbol(
     myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setAlpha(myOpacity)
myRange2 = QgsRendererRangeV2(myMin, myMax, mySymbol2 myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRendererV2('', myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRendererV2.EqualInterval)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRendererV2(myRenderer)
QgsMapLayerRegistry.instance().addMapLayer(myVectorLayer)

Werken met symbolen

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

  • QgsMarkerSymbolV2 — voor objecten punt

  • QgsLineSymbolV2 — voor objecten lijn

  • QgsFillSymbolV2 — voor objecten polygoon

Elk symbool bestaat uit één of meer symboollagen (klassen afgeleid van QgsSymbolLayerV2). 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

for i in xrange(symbol.symbolLayerCount()):
    lyr = symbol.symbolLayer(i)
    print "%d: %s" % (i, lyr.layerType())

Gebruik de methode color() om de kleur van het symbool vast te stellen en setColor() om die kleur te wijzigen. Met aanvullende markeringssymbolen kunt u vragen naar de grootte en rotatie van het symbool met de methoden size() en angle(), voor lijnsymbolen is er de methode width() die de dikte van de lijn teruggeeft.

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

Werken met symboollagen

Zoals eerder gezegd bepalen symboollagen (subklassen van QgsSymbolLayerV2) 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 kunt maken voor een bepaalde klasse van een symboollaag, verkrijgen op deze manier

from qgis.core import QgsSymbolLayerV2Registry
myRegistry = QgsSymbolLayerV2Registry.instance()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbolV2.Marker):
    print item

Uitvoer

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

Klasse QgsSymbolLayerV2Registry beheert een database van alle beschikbare typen symboollagen.

Gebruik zijn methode properties() om toegang te verkrijgen tot de gegevens van de symbollaag, 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(), 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.

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

class FooSymbolLayer(QgsMarkerSymbolLayerV2):

  def __init__(self, radius=4.0):
      QgsMarkerSymbolLayerV2.__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. Eigenschappen worden 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() die het renderen uitvoert. 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, resp. renderPolygon() welke een lijst van punten op de buitenste ring als een 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

class FooSymbolLayerWidget(QgsSymbolLayerV2Widget):
    def __init__(self, parent=None):
        QgsSymbolLayerV2Widget.__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 functie 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 de voorvertoning 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

class FooSymbolLayerMetadata(QgsSymbolLayerV2AbstractMetadata):

  def __init__(self):
    QgsSymbolLayerV2AbstractMetadata.__init__(self, "FooMarker", QgsSymbolV2.Marker)

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

  def createSymbolLayerWidget(self):
    return FooSymbolLayerWidget()

QgsSymbolLayerV2Registry.instance().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. createSymbolLayer() zorgt voor het maken van een instance van de symboollaag met attributen die zijn gespecificeerd in het woordenboek props. (Let op: de sleutels zijn QString instances, geen “str”-objecten). En er is de methode createSymbolLayerWidget() die 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.

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

import random

class RandomRenderer(QgsFeatureRendererV2):
  def __init__(self, syms=None):
    QgsFeatureRendererV2.__init__(self, "RandomRenderer")
    self.syms = syms if syms else [QgsSymbolV2.defaultSymbol(QGis.Point), QgsSymbolV2.defaultSymbol(QGis.Point)]

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

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

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

  def usedAttributes(self):
    return []

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

De constructor van de bovenliggende klasse QgsFeatureRendererV2 heeft de naam van de renderer nodig (moet uniek 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 ene lijst met veldnamen teruggeven waarvan de renderer verwacht dat die aanwezig is. Tenslotte zou de functie 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 QgsRendererV2Widget. De volgende voorbeeldcode maakt een knop die de gebruiker in staat stelt het symbool in te stellen van het eerste symbool

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

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

  def renderer(self):
    return self.r

De constructor ontvangt instances van de actieve laag (QgsVectorLayer), de globale opmaak (QgsStyleV2) 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 — het 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

class RandomRendererMetadata(QgsRendererV2AbstractMetadata):
  def __init__(self):
    QgsRendererV2AbstractMetadata.__init__(self, "RandomRenderer", "Random renderer")

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

QgsRendererV2Registry.instance().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 QgsRendererV2AbstractMetadata als een derde (optioneel) argument — de basis klassse-constructor in de functie __init__() van de RandomRendererMetadata wordt

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

Het pictogram kan ook op een later tijdstip worden geassocieerd met behulp van 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 (PyQt4 bevat .qrc compiler voor Python).

Meer onderwerpen

TODO:

maken/aanpassen van symbolen die werken met stijl (QgsStyleV2) werken met kleurbalken (QgsVectorColorRampV2) op regels gebaseerde-renderer (zie deze blogpost) die de symboollaag en registreren van de renderer verkent