6. Usare Layer Vettoriali

Suggerimento

I frammenti di codice in questa pagina hanno bisogno delle seguenti importazioni se sei al di fuori della console di pyqgis:

 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)

Questa sezione riassume le varie azioni che si possono eseguire con i vettori.

La maggior parte del materiale presente in questo documento si basa sui metodi della classe QgsVectorLayer.

6.1. Recuperare informazioni sugli attributi

Puoi recuperare informazioni sui campi associati a un layer vettoriale chiamando fields() su un oggetto QgsVectorLayer:

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

I metodi displayField() e mapTipTemplate() della classe QgsVectorLayer forniscono informazioni sul campo e sul modello usati nella scheda Proprietà Suggerimenti.

Quando carichi un layer vettoriale, un campo viene sempre scelto da QGIS come Display Name, mentre Suggerimenti Mappa in HTML è vuoto per default. Con questi metodi puoi facilmente ottenere entrambi:

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

Nota

Se cambi il Display Name da un campo a un’espressione, devi usare displayExpression() invece di displayField().

6.2. Iterare un Vettore.

L’iterazione sugli elementi di un layer vettoriale è una delle operazioni più comuni. Di seguito è riportato un esempio di semplice codice di base per eseguire questo processo e per mostrare alcune informazioni su ciascun elemento. Si presume che la variabile layer abbia un oggetto 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. Selezionare geometrie

In QGIS desktop, gli elementi possono essere selezionati in diversi modi: l’utente può fare clic su un elemento, disegnare un rettangolo sull” area della mappa o utilizzare un filtro di espressione. Gli elementi selezionati sono normalmente evidenziati con un colore diverso (il colore predefinito è il giallo) per attirare l’attenzione dell’utente sulla selezione.

A volte può essere utile selezionare automaticamente gli elementi o cambiare il colore predefinito.

Per selezionare tutti gli elementi, può essere usato il metodo selectAll():

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

Per effettuare una selezione usando un’espressione, usa il metodo 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)

Per cambiare il colore delle selezioni puoi usare il metodo setSelectionColor() di QgsMapCanvas come mostrato nel seguente esempio:

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

Per aggiungere gli elementi all’elenco degli elementi selezionati per un dato layer, puoi chiamare select() passandogli l’elenco degli ID degli elementi:

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)

Per annullare la selezione:

layer.removeSelection()

6.3.1. Accedere agli attributi

Gli attributi possono essere indicati con il loro nome:

print(feature['name'])
First feature

In alternativa, si può fare riferimento agli attributi tramite l’indice. Questo è un po” più veloce rispetto all’uso del nome. Per esempio, per ottenere il secondo attributo:

print(feature[1])
First feature

6.3.2. Iterare gli elementi selezionati

Se ti servono solo gli elementi selezionati, puoi usare il metodo selectedFeatures() per il layer vettoriale:

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

6.3.3. Iterare un sottoinsieme di caratteristiche

Se vuoi iterare su un determinato sottoinsieme di elementi in un layer, come quelli all’interno di una determinata area, devi aggiungere un oggetto QgsFeatureRequest alla chiamata getFeatures(). Ecco un esempio:

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

Per motivi di velocità, l’intersezione viene spesso eseguita solo utilizzando il rettangolo di selezione degli elementi. Esiste tuttavia un flag ExactIntersect che assicura che vengano restituite solo gli elementi che si intersecano:

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

Con setLimit() puoi limitare il numero di elementi che vengono richiesti. Ecco un esempio:

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>

Se hai bisogno di un filtro basato su attributi, invece (o in aggiunta) di uno spaziale come mostrato negli esempi precedenti, puoi costruire un oggetto QgsExpression e passarlo al costruttore QgsFeatureRequest. Ecco un esempio:

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

Vedi Espressioni, Filtraggio e Calcolo di Valori per i dettagli sulla sintassi supportata da QgsExpression.

La richiesta può essere usata per definire i dati recuperati per ogni elemento, in modo che l’iteratore restituisca tutti gli elementi, restituendo però dati parziali per ciascuno di essi.

 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. Modificare i Vettori

La maggior parte dei fornitori di dati vettoriali supporta la modifica dei dati dei layer. A volte supportano solo un sottoinsieme di possibili azioni di modifica. Usa la funzione capabilities() per scoprire quale insieme di funzionalità è supportato.

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

Per un elenco di tutte le capabilities disponibili, consulta la Documentazione API di QgsVectorDataProvider.

Per stampare la descrizione testuale delle capabilities del layer in un elenco separato da virgole, puoi usare capabilitiesString() come nell’esempio seguente:

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'

Utilizzando uno dei seguenti metodi per la modifica dei layer vettoriali, le modifiche vengono direttamente apportate all’archivio dati sottostante (un file, un database, ecc.). Se vuoi apportare solo modifiche temporanee, passa alla sezione successiva che spiega come effettuare modifications with editing buffer.

Nota

Se stai lavorando all’interno di QGIS (sia dalla console che da un plugin), potrebbe essere necessario forzare un ridisegno della mappa per vedere le modifiche che hai fatto alla geometria, allo stile o agli attributi:

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. Aggiungi Elementi

Creare alcune istanze QgsFeature e passarne un elenco al metodo addFeatures() del provider. Esso restituirà due valori: il risultato (True o False) e l’elenco degli elementi aggiunti (il loro ID è impostato dal data store).

Per impostare gli attributi dell” elemento, si può inizializzare l’elemento passando un oggetto QgsFields (si può ottenere dal metodo fields() del layer vettoriale) o chiamare initAttributes() passando il numero di campi che vuoi aggiungere.

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. Eliminare Elementi

Per eliminare alcuni elementi, è sufficiente fornire un elenco degli ID degli elementi stessi.

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

6.4.3. Modificare Elementi

È possibile modificare la geometria dell” elemento o modificare alcuni attributi. L’esempio seguente modifica prima i valori degli attributi con indice 0 e 1 e poi la geometria dell” elemento.

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

Suggerimento

Preferibile la classe QgsVectorLayerEditUtils per modifiche solo geometriche

Se hai solo bisogno di modificare le geometrie, potresti considerare di usare la QgsVectorLayerEditUtils che fornisce alcuni metodi utili per modificare le geometrie (traslazione, inserimento o spostamento di vertici, ecc.).

6.4.4. Modificare i Vettori con un Buffer di Modifica

Quando modifichi i vettori all’interno dell’applicazione QGIS, devi prima avviare la modalità di modifica per un particolare layer, poi apportare alcune modifiche e infine eseguire il commit (o il rollback) delle modifiche. Tutte le modifiche apportate non vengono scritte finché non esegui il commit, ma rimangono nel buffer di modifica in memoria del layer. È possibile usare questa funzionalità anche in modo programmatico: è solo un altro metodo per la modifica dei layer vettoriali che integra l’uso diretto dei fornitori di dati. Utilizzare questa opzione quando si forniscono strumenti dell’interfaccia grafica per la modifica dei layer vettoriali, in quanto consente all’utente di decidere se eseguire il commit/rollback e permette l’uso di annullo/ripristina. Quando le modifiche vengono impegnate, tutte le modifiche del buffer di modifica vengono salvate nel fornitore di dati.

I metodi sono simili a quelli visti nel provider, ma vengono richiamati sull’oggetto QgsVectorLayer.

Affinché questi metodi funzionino, il layer deve essere in modalità di modifica. Per avviare la modalità di modifica, utilizza il metodo startEditing(). Per interrompere la modifica, utilizza i metodi commitChanges() o rollBack(). Il primo impegna tutte le modifiche apportate all’origine dati, mentre il secondo le scarta e non modifica affatto l’origine dati.

Per scoprire se un layer è in stato di modifica, usa il metodo isEditable().

Di seguito hai alcuni esempi che dimostrano come utilizzare questi metodi di editing.

 1from qgis.PyQt.QtCore import QVariant
 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", QVariant.String))
22# remove a field
23layer.deleteAttribute(fieldIndex)

Per far funzionare correttamente annulla/ripristina, le chiamate di cui sopra devono essere racchiuse in comandi di annullamento. (Se non ci si preoccupa dell’annulla/ripristina e si vuole che le modifiche vengano memorizzate immediatamente, si potrà lavorare più facilmente con editing with data provider).

Ecco come utilizzare la funzionalità di annullamento:

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

Il metodo beginEditCommand() crea un comando interno «attivo» e registra le modifiche successive nel layer vettoriale. Con la chiamata a endEditCommand() il comando viene spinto nella lista di annullamento e l’utente potrà annullarlo/ripristinarlo dalla GUI. Nel caso in cui qualcosa sia andato storto durante le modifiche, il metodo destroyEditCommand() rimuoverà il comando e annullerà tutte le modifiche effettuate mentre il comando era attivo.

Puoi anche usare la dichiarazione with edit(layer) per racchiudere commit e rollback in un blocco di codice più semantico, come mostrato nell’esempio seguente:

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

Questo chiamerà automaticamente commitChanges() alla fine. Se si verifica un’eccezione, rollBack() tutte le modifiche. Nel caso in cui si verifichi un problema all’interno di commitChanges() (quando il metodo restituisce False) verrà sollevata un’eccezione QgsEditError.

6.4.5. Aggiungere e Rimuovere Campi

Per aggiungere campi (attributi), è necessario specificare un elenco di definizioni di campi. Per eliminare i campi è sufficiente fornire un elenco di indici di campo.

1from qgis.PyQt.QtCore import QVariant
2
3if caps & QgsVectorDataProvider.AddAttributes:
4    res = layer.dataProvider().addAttributes(
5        [QgsField("mytext", QVariant.String),
6        QgsField("myint", QVariant.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",QVariant.Int),QgsField("f2",QVariant.Int),QgsField("f3",QVariant.Int)])
 4layer.updateFields()
 5count=layer.fields().count() # count of layer fields
 6ind_list=list((count-3, count-2)) # create list
 7
 8# remove a single field with an index
 9layer.dataProvider().deleteAttributes([count-1])
10
11# remove multiple fields with a list of indices
12layer.dataProvider().deleteAttributes(ind_list)

Dopo aver aggiunto o rimosso campi nel fornitore di dati, è necessario aggiornare i campi del layer, perché le modifiche non vengono propagate automaticamente.

layer.updateFields()

Suggerimento

Salva direttamente le modifiche usando con il comando basato su

Utilizzando with edit(layer): le modifiche saranno apportate automaticamente chiamando commitChanges() alla fine. Se si verifica un’eccezione, si attiva rollBack() per tutte le modifiche. Vedi Modificare i Vettori con un Buffer di Modifica.

6.5. Usare l’Indice Spaziale

Gli indici spaziali possono migliorare notevolmente le prestazioni del codice se è necessario eseguire interrogazioni frequenti su un layer vettoriale. Immaginiamo, ad esempio, di scrivere un algoritmo di interpolazione e che per una determinata posizione sia necessario conoscere i 10 punti più vicini di un layer di punti, per poterli utilizzare per calcolare il valore interpolato. Senza un indice spaziale, l’unico modo che QGIS ha per trovare questi 10 punti è calcolare la distanza di ogni singolo punto dalla posizione specificata e poi confrontare le distanze. Questa operazione può richiedere molto tempo, soprattutto se deve essere ripetuta per diverse posizioni. Se esiste un indice spaziale per il layer, l’operazione è molto più efficace.

Pensa a un layer senza indice spaziale come a un elenco telefonico in cui i numeri di telefono non sono ordinati o indicizzati. L’unico modo per trovare il numero di telefono di una determinata persona è leggere dall’inizio fino a trovarlo.

Gli indici spaziali non vengono creati di default per un layer vettoriale di QGIS, ma puoi crearli facilmente. Ecco cosa devi fare:

  • crea un indice spaziale utilizzando la classe QgsSpatialIndex:

    index = QgsSpatialIndex()
    
  • aggiungi elementi all’indice — L’indice prende l’oggetto QgsFeature e lo aggiunge alla struttura dati interna. Puoi creare l’oggetto manualmente o utilizzarne uno da una precedente chiamata al metodo getFeatures() del provider.

    index.addFeature(feat)
    
  • In alternativa, puoi caricare tutti gli elementi di un layer in una volta sola, utilizzando il caricamento in blocco

    index = QgsSpatialIndex(layer.getFeatures())
    
  • una volta che l’indice spaziale è stato riempito con alcuni valori, puoi eseguire delle query

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

Puoi utilizzare anche l’indice spaziale QgsSpatialIndexKDBush. Questo indice è simile a quello standard QgsSpatialIndex ma:

  • supporta solo elementi a punto singolo

  • è statico (nessuna elemento aggiuntivo può essere aggiunto all’indice dopo la costruzione)

  • è molto più veloce!

  • permette di recuperare direttamente i punti degli elementi di partenza, senza richiedere ulteriori richieste di elementi

  • supporta le ricerche basate sulla distanza, cioè restituisce tutti i punti entro un raggio da un punto di ricerca.

6.6. La classe QgsVectorLayerUtils

La classe QgsVectorLayerUtils contiene alcuni metodi molto utili che si possono usare con i layer vettoriali.

Ad esempio, il metodo createFeature() prepara una QgsFeature da aggiungere a un layer vettoriale, mantenendo tutti gli eventuali vincoli e valori predefiniti di ogni campo:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)

Il metodo getValues() consente di ottenere rapidamente i valori di un campo o di un’espressione:

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

6.7. Creare Layer vettoriali

Ci sono diversi modi per generare un dataset di layer vettoriali:

  • la classe QgsVectorFileWriter: Una classe adatta per scrivere file vettoriali su disco, utilizzando una chiamata statica a writeAsVectorFormatV3() che salva l’intero layer vettoriale, oppure creando un’istanza della classe ed effettuando chiamate a addFeature(). Questa classe supporta tutti i formati vettoriali supportati da GDAL (GeoPackage, Shapefile, GeoJSON, KML e altri).

  • la classe QgsVectorLayer: istanzia un fornitore di dati che interpreta il percorso fornito (url) dell’origine dati per connettersi e accedere ai dati. Può essere usata per creare layer temporanei, basati sulla memoria (memory) e connettersi a insiemi di dati vettoriali GDAL (ogr), database (postgres, spatialite, mysql, mssql) e altro (wfs, gpx, delimitedtext…).

6.7.1. Da un’istanza di 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)

Puoi convertire i campi per renderli compatibili con formati diversi, utilizzando la FieldValueConverter. Ad esempio, per convertire i tipi di variabili array (ad esempio in Postgres) in un tipo di testo, puoi procedere come segue:

 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, QVariant.String)
24    else:
25      return self.layer.fields()[idx]
26
27converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
28opts = QgsVectorFileWriter.SaveVectorOptions()
29opts.fieldValueConverter = converter

Si può anche specificare un SR di destinazione — se un’istanza valida di QgsCoordinateReferenceSystem viene passata come quarto parametro, il layer viene trasformato in tale SR.

Per i nomi dei driver validi, chiama il metodo supportedFiltersAndFormats() o consulta i formati supportati da OGR — devi passare il valore nella colonna «Code» come nome del driver.

Opzionalmente puoi impostare se esportare solo gli elementi selezionati, se passare altre opzioni specifiche del driver per la creazione o se dire allo scrittore di non creare attributi… Esiste una serie di altri parametri (opzionali); vedere la documentazione di QgsVectorFileWriter per i dettagli.

6.7.2. Direttamente dagli elementi

 1from qgis.PyQt.QtCore import QVariant
 2
 3# define fields for feature attributes. A QgsFields object is needed
 4fields = QgsFields()
 5fields.append(QgsField("first", QVariant.Int))
 6fields.append(QgsField("second", QVariant.String))
 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. Da un’istanza di QgsVectorLayer

Tra tutti i fornitori di dati supportati dalla classe QgsVectorLayer, concentriamoci sui layer in memoria. Il fornitore di memoria è destinato a essere usato principalmente dagli sviluppatori di plugin o di applicazioni di terze parti. Non memorizza i dati su disco, consentendo agli sviluppatori di utilizzarlo come backend veloce per alcuni layer temporanei.

Il provider supporta campi di tipo string, int e double.

Il provider di memoria supporta anche l’indicizzazione spaziale, che si attiva richiamando la funzione createSpatialIndex() del provider. Una volta creato l’indice spaziale, sarà possibile iterare sugli elementi all’interno di regioni più piccole in modo più rapido (poiché non è necessario analizzare tutti gli elementi, ma solo quelli nel rettangolo specificato).

Un fornitore di memoria viene creato passando "memory" come stringa del fornitore al costruttore QgsVectorLayer.

Il costruttore accetta anche un URI che definisce il tipo di geometria del layer, uno dei seguenti: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" o "None".

L’URI può anche specificare il sistema di riferimento delle coordinate, i campi e l’indicizzazione del provider in memoria nell’URI. La sintassi è:

crs=definizione

Specifica il sistema di riferimento delle coordinate, dove la definizione può essere una qualsiasi delle forme accettate da QgsCoordinateReferenceSystem.createFromString().

index=yes

Specifica che il provider userà un indice spaziale

field=nome:tipo(lunghezza,precisione)

Specifica un attributo del vettore. L’attributo ha un nome, e facoltativamente un tipo (intero, double, o string), lunghezza, e precisione. Ci sono possono essere definizioni di campo multiple

Il seguente esempio di URI incorpora tutte queste opzioni

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

Il seguente esempio di codice illustra la creazione e il popolamento di un fornitore di memoria

 1from qgis.PyQt.QtCore import QVariant
 2
 3# create layer
 4vl = QgsVectorLayer("Point", "temporary_points", "memory")
 5pr = vl.dataProvider()
 6
 7# add fields
 8pr.addAttributes([QgsField("name", QVariant.String),
 9                    QgsField("age",  QVariant.Int),
10                    QgsField("size", QVariant.Double)])
11vl.updateFields() # tell the vector layer to fetch changes from the provider
12
13# add a feature
14fet = QgsFeature()
15fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
16fet.setAttributes(["Johny", 2, 0.3])
17pr.addFeatures([fet])
18
19# update layer's extent when new features have been added
20# because change of extent in provider is not propagated to the layer
21vl.updateExtents()

Infine, verifichiamo se tutto è andato a buon fine

 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: 3
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johny', 2, 0.3] <QgsPointXY: POINT(10 10)>

6.8. Apparenza (Simbologia) dei Vettori

Quando un vettore deve essere visualizzato, l’aspetto dei dati è dato dal visualizzatore e dai simboli associati al vettore. I simboli sono classi che si occupano del disegno di rappresentazione visiva delle geometrie, mentre i visualizzatori determinano quale simbolo sarà usato per una particolare geometria.

Il visualizzatore per un determinato layer può essere ottenuto come mostrato di seguito:

renderer = layer.renderer()

E con questo riferimento, esploriamolo un po”.

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

Nella libreria principale di QGIS sono disponibili diversi tipi di visualizzatori:

Tipo

Classe

Descrizione

singleSymbol

QgsSingleSymbolRenderer

Visualizza tutte le geometria con lo stesso simbolo

categorizedSymbol

QgsCategorizedSymbolRenderer

Visualizza le geometria usando un simbolo diverso per ogni categoria

graduatedSymbol

QgsGraduatedSymbolRenderer

Visualizza le geometrie usando un simbolo diverso per ogni intervallo di valori

Potrebbero esserci anche tipi di visualizzatori personalizzati, quindi non bisogna mai dare per scontato che esistano solo questi tipi. Puoi interrogare la QgsRendererRegistry dell’applicazione per trovare i visualizzatori attualmente disponibili:

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

È possibile ottenere un dump dei contenuti di un visualizzatore in forma di testo — può essere utile per il debug.

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

6.8.1. Visualizzatore Simbolo Singolo

Puoi ottenere il simbolo usato per il visualizzatore chiamando il metodo symbol() e cambiarlo con il metodo setSymbol() (nota per gli sviluppatori C++: il visualizzatore assume la proprietà del simbolo).

Puoi modificare il simbolo utilizzato da un particolare layer vettoriale chiamando setSymbol() passando un’istanza del simbolo appropriato. I simboli per i layer punto, linea e poligono possono essere creati chiamando la funzione createSimple() delle classi corrispondenti QgsMarkerSymbol, QgsLineSymbol e QgsFillSymbol.

Il dizionario passato a createSimple() imposta le proprietà di stile del simbolo.

Ad esempio, puoi sostituire il simbolo utilizzato da un particolare layer punto chiamando setSymbol() passando un’istanza di QgsMarkerSymbol, come nel seguente esempio di codice:

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

Il ``nome”” indica la forma del marcatore e può essere uno dei seguenti:

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

Per ottenere l’elenco completo delle proprietà del primo livello di un’istanza di simbolo, si può seguire il codice di esempio:

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

Può essere utile se si desidera modificare alcune proprietà:

 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. Visualizzatore Simbolo Categorizzato

Quando utilizzi un visualizzatore categorizzato, puoi interrogare e impostare l’attributo usato per la classificazione: usa i metodi classAttribute() e setClassAttribute().

Per ottenere un elenco delle categorie

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>

Dove value() è il valore usato per la discriminazione tra le categorie, label() è un testo usato per la descrizione della categoria e symbol() restituisce il simbolo assegnato.

Il visualizzatore di solito memorizza anche il simbolo originale e la scala di colori utilizzati per la classificazione: metodi sourceColorRamp() e sourceSymbol().

6.8.3. Visualizzatore Simbolo Graduato

Questo visualizzatore è molto simile al visualizzatore simbolo categorizzato descritto sopra, ma invece di un valore di attributo per classe esso lavora con intervalli di valori e quindi può essere usato solo con attributi di tipo numerico.

Per saperne di più sugli intervalli utilizzati nel visualizzatore

 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>

puoi utilizzare nuovamente i metodi classAttribute() (per trovare il nome dell’attributo di classificazione), sourceSymbol() e sourceColorRamp(). Inoltre, esiste il metodo mode() che determina come sono stati creati gli intervalli: utilizzando intervalli uguali, quantili o qualche altro metodo.

Se vuoi creare un tuo renderer di simboli graduati, puoi farlo come illustrato nello frammento di codice di esempio seguente (che crea una semplice rappresentazione a due classi)

 1from qgis.PyQt import QtGui
 2
 3myVectorLayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
 4myTargetField = 'ELEV'
 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. Lavorare con i simboli

Per la rappresentazione dei simboli, esiste la classe base QgsSymbol con tre classi derivate:

Ogni simbolo consiste in uno o più livelli di simboli (classi derivate da QgsSymbolLayer). I livelli di simboli eseguono il rendering vero e proprio, mentre la classe simbolo serve solo come contenitore per i livelli di simboli.

Avendo un’istanza di un simbolo (ad esempio da un visualizzatore), è possibile esplorarlo: il metodo type() dice se si tratta di un marcatore, di una linea o di un simbolo di riempimento. Esiste un metodo dump() che restituisce una breve descrizione del simbolo. Per ottenere un elenco di livelli di simboli:

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

Per conoscere il colore del simbolo, utilizza il metodo color() e setColor() per modificarne il colore. Per i simboli marker, inoltre, puoi interrogare la dimensione e la rotazione del simbolo con i metodi size() e angle(). Per i simboli di linea, il metodo width() restituisce la larghezza della linea.

La dimensione e la larghezza sono in millimetri per impostazione predefinita, gli angoli sono in gradi.

6.8.4.1. Lavorare con i Livelli di Simboli

Come già detto, i livelli di simboli (sottoclassi di QgsSymbolLayer) determinano l’aspetto degli elementi. Esistono diverse classi di livelli di simboli di base per uso generale. È possibile implementare nuovi tipi di livelli di simboli e quindi personalizzare arbitrariamente il modo in cui gli elementi vengono rappresentati. Il metodo layerType() identifica in modo univoco la classe del livello di simboli: quelli di base e predefiniti sono i tipi di livelli di simboli SimpleMarker, SimpleLine e SimpleFill.

Puoi ottenere un elenco completo dei tipi di livelli di simboli che è possibile creare per una determinata classe di livelli di simboli con il seguente codice:

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

La classe QgsSymbolLayerRegistry gestisce un database di tutti i tipi di livelli di simboli disponibili.

Per accedere ai dati del livello di simboli, utilizza il metodo properties() che restituisce un dizionario chiave-valore di proprietà che determinano l’aspetto. Ogni tipo di livello di simboli ha un insieme specifico di proprietà che utilizza. Inoltre, ci sono i metodi generici color(), size(), angle() e width(), con le loro controparti di impostazione. Naturalmente, le dimensioni e l’angolo sono disponibili solo per i livelli dei simboli marcatore e la larghezza per i livelli dei simboli linea.

6.8.4.2. Creazione di tipi di livelli di simboli personalizzati

Immagina di voler personalizzare il modo in cui i dati vengono rappresentati. Puoi creare la tua classe di livello simbolo che disegnerà gli elementi esattamente come desideri. Ecco un esempio di un marcatore che disegna cerchi rossi con raggio specificato

 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)

Il metodo layerType() determina il nome del livello di simboli; deve essere unico tra tutti i livelli di simboli. Il metodo properties() è utilizzato per la persistenza degli attributi. Il metodo clone() deve restituire una copia del livello di simboli con tutti gli attributi esattamente uguali. Infine ci sono i metodi di visualizzazione: startRender() viene chiamato prima di eseguire la visualizzazione del primo elemento, stopRender() quando la visualizzazione è terminata e renderPoint() viene chiamato per eseguire la visualizzazione. Le coordinate dei punti sono già trasformate in coordinate di output.

Per le polilinee e i poligoni l’unica differenza è nel metodo di visualizzazione: si usa renderPolyline() che riceve un elenco di linee, mentre renderPolygon() riceve un elenco di punti sull’anello esterno come primo parametro e un elenco di anelli interni (o Nessuno) come secondo parametro.

Di solito è conveniente aggiungere un’interfaccia grafica per l’impostazione degli attributi del tipo di livello del simbolo, per consentire agli utenti di personalizzarne l’aspetto: nel caso dell’esempio precedente, possiamo consentire all’utente di impostare il raggio del cerchio. Il codice seguente implementa tale 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()"))

Questo widget può essere incorporato nella finestra di dialogo delle proprietà del simbolo. Quando si seleziona il tipo di livello del simbolo nella finestra di dialogo delle proprietà del simbolo, viene creata un’istanza del livello del simbolo e un’istanza del widget del livello del simbolo. Quindi chiama il metodo setSymbolLayer() per assegnare il livello di simboli al widget. In questo metodo il widget deve aggiornare l’interfaccia utente per riflettere gli attributi del livello di simboli. Il metodo symbolLayer() viene utilizzato per recuperare il livello di simboli dalla finestra di dialogo delle proprietà e utilizzarlo per il simbolo.

A ogni modifica degli attributi, il widget deve emettere il messaggio changed() per consentire alla finestra di dialogo delle proprietà di aggiornare l’anteprima del simbolo.

Ora manca solo il collante finale: rendere QGIS consapevole di queste nuove classi. Questo viene fatto aggiungendo il livello dei simboli al registro. È possibile utilizzare il livello di simboli anche senza aggiungerlo al registro, ma alcune funzionalità non funzioneranno: ad esempio, il caricamento dei file di progetto con i livelli di simboli personalizzati o l’impossibilità di modificare gli attributi del layer nella GUI.

Dovremo creare i metadati per il livello dei simboli

 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)

Devi passare il tipo di livello (lo stesso restituito dal livello) e il tipo di simbolo (marcatore/linea/riempimento) al costruttore della classe padre. Il metodo createSymbolLayer() si occupa di creare un’istanza di livello di simboli con gli attributi specificati nel dizionario props. E c’è il metodo createSymbolLayerWidget() che restituisce il widget delle impostazioni per questo tipo di livello di simboli.

L’ultimo passo consiste nell’aggiungere questo livello di simboli al registro — e abbiamo finito.

6.8.5. Creazione di visualizzatori personalizzati

Potrebbe essere utile creare una nuova implementazione del visualizzatore se vuoi personalizzare le regole di selezione dei simboli per la visualizzazione degli elementi. Alcuni casi d’uso in cui si vorrebbe farlo: il simbolo è determinato da una combinazione di campi, la dimensione dei simboli cambia a seconda della scala corrente, ecc.

Il codice seguente mostra un semplice visualizzatore personalizzato che crea due simboli di marcatori e ne sceglie a caso uno per ogni elemento

 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)

Il costruttore della classe genitore QgsFeatureRenderer ha bisogno di un nome di visualizzatore (che deve essere unico tra i visualizzatori). Il metodo symbolForFeature() è quello che decide quale simbolo sarà usato per un particolare elemento. startRender() e stopRender() si occupano dell’inizializzazione/finalizzazione della rappresentazione dei simboli. Il metodo usedAttributes() può restituire un elenco di nomi di campi che il visualizzatore si aspetta siano presenti. Infine, il metodo clone() dovrebbe restituire una copia del visualizzatore.

Come per i livelli di simboli, è possibile collegare un’interfaccia grafica per la configurazione del visualizzatore. Deve essere derivata da QgsRendererWidget. Il codice di esempio seguente crea un pulsante che consente all’utente di impostare il primo simbolo

 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

Il costruttore riceve istanze del livello attivo (QgsVectorLayer), dello stile globale (QgsStyle) e del visualizzatore corrente. Se non c’è un visualizzatore o se il visualizzatore ha un tipo diverso, verrà sostituito con il nostro nuovo visualizzatore, altrimenti verrà utilizzato il visualizzatore corrente (che ha già il tipo di cui abbiamo bisogno). Il contenuto del widget deve essere aggiornato per mostrare lo stato attuale del visualizzatore. Quando la finestra di dialogo del visualizzatore viene accettata, viene richiamato il metodo renderer() del widget per ottenere il visualizzatore corrente, che verrà assegnato al livello.

L’ultima parte mancante sono i metadati del visualizzatore e la sua registrazione nel registro, altrimenti il caricamento dei livelli con il visualizzatore non funzionerà e l’utente non sarà in grado di selezionarlo dall’elenco dei visualizzatori. Terminiamo l’esempio di RandomRenderer

 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)

Come per i livelli di simboli, il costruttore dell’abstract dei metadati si aspetta il nome del visualizzatore, il nome visibile per gli utenti e, facoltativamente, il nome dell’icona del visualizzatore. Il metodo createRenderer() passa un’istanza QDomElement che può essere usata per ripristinare lo stato del visualizzatore dall’albero DOM. Il metodo createRendererWidget() crea il widget di configurazione. Non è necessario che sia presente o può restituire None se il visualizzatore non è dotato di interfaccia grafica.

Per associare un’icona al visualizzatore, puoi assegnarla nel costruttore QgsRendererAbstractMetadata come terzo parametro (opzionale) — il costruttore della classe base nella funzione RandomRendererMetadata __init__() diventa

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

L’icona può anche essere associata in qualsiasi momento successivo usando il metodo setIcon() della classe dei metadati. L’icona può essere caricata da un file (come mostrato sopra) o da una risorsa Qt (PyQt5 include il compilatore .qrc per Python).

6.9. Ulteriori argomenti

**DA FARE: **

  • creazione/modifica di simboli

  • lavorare con gli stili (QgsStyle)

  • lavorare con la scala di colori (QgsColorRamp)

  • esplorare i registri dei livelli di simboli e dei visualizzatori