Os trechos de código desta página precisam das seguintes importações se você estiver fora do console do pyqgis:

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

from qgis.core.additions.edit import edit

from qgis.PyQt.QtGui import (
    QColor,
)

6. Usando Camadas Vetoriais

Esta seção lista várias operações que podem ser realizadas com camadas vetoriais.

A maioria dos trabalhos aqui é baseada nos métodos da classe QgsVectorLayer.

6.1. Recuperando informações sobre atributos

Você pode recuperar informações sobre os campos associados a uma camada vetorial chamando fields() em um objeto QgsVectorLayer:

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

6.2. Iterando sobre Camada Vetorial

A iteração sobre as feições em uma camada vetorial é uma das tarefas mais comuns. Abaixo está um exemplo do código básico simples para executar esta tarefa e mostrando algumas informações sobre cada feição. Supõe-se que a variável layer tenha um objeto QgsVectorLayer.

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

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

6.3. Selecionando características

Na área de trabalho do QGIS, as feições podem ser selecionadas de diferentes maneiras: o usuário pode clicar em uma feição, desenhar um retângulo na tela do mapa ou usar um filtro de expressão. As feições selecionadas normalmente são destacadas em uma cor diferente (o padrão é amarelo) para chamar a atenção do usuário na seleção.

Às vezes, pode ser útil selecionar recursos programaticamente ou alterar a cor padrão.

Para selecionar todas as feições, o método selectAll() pode ser usado:

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

Para selecionar usando uma expressão, use o método 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)

Para alterar a cor da seleção, você pode usar o método setSelectionColor() de QgsMapCanvas, como mostrado no exemplo a seguir:

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

Para adicionar recursos à lista de recursos selecionados para uma determinada camada, você pode chamar select() passando para ele a lista de IDs de recursos:

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

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

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

Para limpar a seleção:

layer.removeSelection()

6.3.1. Acessando atributos

Os atributos podem ser referidos pelo seu nome:

print(feature['name'])
First feature

Como alternativa, os atributos podem ser referidos pelo índice. Isso é um pouco mais rápido do que usar o nome. Por exemplo, para obter o segundo atributo:

print(feature[1])
First feature

6.3.2. Iteração sobre os feições selecionadas

Se você precisar apenas de feições selecionadas, poderá usar o método selectedFeatures() da camada vetorial:

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

6.3.3. Iterando sobre um subconjunto de feições

Se você deseja iterar sobre um determinado subconjunto de feições em uma camada, como aquelas dentro de uma determinada área, você deve adicionar um objeto QgsFeatureRequest ao getFeatures() chamado. Aqui está um exemplo:

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

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

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

Por uma questão de velocidade, a interseção geralmente é feita apenas usando a caixa delimitadora da feição. No entanto, existe um sinalizador ExactIntersect que garante que apenas as feições que se intersectam serão retornadas:

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

Com setLimit() você pode limitar o número de feições solicitadas. Aqui está um exemplo:

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

Se você precisar de um filtro baseado em atributo em vez (ou adicionalmente) de um filtro espacial, como mostrado nos exemplos acima, poderá criar um objeto QgsExpression e passá-lo para o construtor :classe:`QgsFeatureRequest <qgis.core.QgsFeatureRequest>`. Aqui está um exemplo:

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

Veja expression para obter detalhes sobre a sintaxe suportada por QgsExpression.

A solicitação pode ser usada para definir os dados recuperados para cada feição, portanto, o iterador retorna todos as feições, mas retorna dados parciais para cada uma delas.

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

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

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

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

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

6.4. Modificando Camadas Vetoriais

A maioria dos provedores de dados vetoriais suporta a edição de dados da camada. Às vezes, eles suportam apenas um subconjunto de possíveis ações de edição. Use a função capacidades() para descobrir qual conjunto de funcionalidades é suportado.

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

Para obter uma lista de todos os recursos disponíveis, consulte a API Documentation of QgsVectorDataProvider.

Para imprimir a descrição textual dos recursos da camada em uma lista separada por vírgulas, você pode usar capabilitiesString() como no exemplo a seguir:

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

Ao usar qualquer um dos métodos a seguir para edição da camada vetorial, as alterações são confirmadas diretamente no armazenamento de dados subjacente (um arquivo, banco de dados etc.). Caso você deseje fazer apenas alterações temporárias, pule para a próxima seção que explica como fazer modifications with editing buffer.

Nota

Se você estiver trabalhando no QGIS (no console ou em um complemento), pode ser necessário forçar um redesenho da tela do mapa para ver as alterações feitas na geometria, no estilo ou nos atributos:

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

6.4.1. Adicionar feições

Crie algumas instâncias QgsFeature e passe uma lista delas para o método do provedor addFeatures() do provedor. Ele retornará dois valores: resultado (verdadeiro/falso) e lista de recursos adicionados (seu ID é definido pelo armazenamento de dados).

Para configurar os atributos da feição, você pode inicializar a feição passando um objeto QgsFields (você pode obtê-lo no método fields() da camada vetorial) ou chame initAttributes() passando o número de campos que você deseja adicionar.

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

6.4.2. Excluir feições

Para excluir algumas feições, basta fornecer uma lista de seus IDs de feições.

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

6.4.3. Modificar Feições

É possível alterar a geometria da feição ou alterar alguns atributos. O exemplo a seguir altera primeiro os valores dos atributos com os índices 0 e 1, depois altera a geometria da feição.

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

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

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

Dica

Preferindo a classe QgsVectorLayerEditUtils para edições somente de geometria

Se você precisar alterar apenas geometrias, considere usar QgsVectorLayerEditUtils, que fornece alguns métodos úteis para editar geometrias (traduzir, inserir ou mover vértices, etc.).

6.4.4. Modificando Camadas Vetoriais com um Buffer

Ao editar vetores no aplicativo QGIS, você deve primeiro iniciar o modo de edição para uma camada específica, fazer algumas modificações e finalmente confirmar (ou retroceder) as alterações. Todas as alterações que você faz não são gravadas até que você as confirme - elas permanecem no buffer de edição na memória da camada. É possível usar essa funcionalidade também usando programação - é apenas outro método para edição de camada vetorial que complementa o uso direto de provedores de dados. Use esta opção ao fornecer algumas ferramentas da GUI para a edição da camada vetorial, pois isso permitirá ao usuário decidir se deve confirmar/retroceder e permite o uso de desfazer/refazer. Quando as alterações são confirmadas, todas as alterações do buffer de edição são salvas no provedor de dados.

Os métodos são semelhantes aos que vimos no provedor, mas são chamados no objeto QgsVectorLayer.

Para que esses métodos funcionem, a camada deve estar no modo de edição. Para iniciar o modo de edição, use o método startEditing(). Para parar a edição, use os métodos commitChanges() ou rollBack(). O primeiro confirmará todas as suas alterações na fonte de dados, enquanto o segundo as descartará e não modificará a fonte de dados.

Para descobrir se uma camada está no modo de edição, use o método isEditable().

Aqui você tem alguns exemplos que demonstram como usar esses métodos de edição.

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

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

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

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

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

Para desfazer/refazer o trabalho corretamente, os chamados acima mencionados têm de ser envoltos em comandos de desfazer. (Se você não se importa com desfazer/refazer e quer ter as mudanças armazenadas imediatamente, então você vai ter o trabalho mais fácil editing with data provider.)

Aqui está como você pode usar a funcionalidade desfazer:

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

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

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

# ... more editing ...

layer.endEditCommand()

O método beginEditCommand() cria um comando interno “ativo” e registra as alterações subsequentes na camada vetorial. Com o chamado para endEditCommand() o comando é enviado para a pilha de desfazer e o usuário poderá desfazê-lo/refazê-lo da GUI. Caso algo dê errado ao fazer as alterações, o método destroyEditCommand() removerá o comando e reverterá todas as alterações feitas enquanto este comando estava ativo.

Você também pode usar o with edit(layer) -statement para agrupar commit e rollback em um bloco de código mais semântico, como mostrado no exemplo abaixo:

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

Isso chamará automaticamente commitChanges() no final. Se ocorrer alguma exceção, ele irá rollBack() todas as alterações. Caso seja encontrado um problema em commitChanges() (quando o método retornar Falso) a exceção a QgsEditError será gerada.

6.4.5. Adicionando e Removendo Campos

Para adicionar campos (atributos), você precisa especificar uma lista de definições de campos. Para exclusão de campos, forneça apenas uma lista de índices de campos.

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

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

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

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

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

Após adicionar ou remover campos no provedor de dados, os campos da camada precisam ser atualizados porque as alterações não são propagadas automaticamente.

layer.updateFields()

Dica

Salvando as alterações diretamente usando o comando baseado em **``with`` **

Usando with edit(layer): as alterações serão confirmadas automaticamente chamando commitChanges() no final. Se ocorrer alguma exceção, ele irá rollBack () todas as alterações. Veja Modificando Camadas Vetoriais com um Buffer.

6.5. Utilizando Índices Espaciais

Os índices espaciais podem melhorar drasticamente o desempenho do seu código, se você precisar fazer consultas frequentes a uma camada vetorial. Imagine, por exemplo, que você esteja escrevendo um algoritmo de interpolação e que, para um determinado local, precise conhecer os 10 pontos mais próximos de uma camada de pontos, a fim de usá-los para calcular o valor interpolado. Sem um índice espacial, a única maneira de o QGIS encontrar esses 10 pontos é calcular a distância de cada ponto até o local especificado e comparar essas distâncias. Isso pode ser uma tarefa que consome muito tempo, especialmente se precisar ser repetida em vários locais. Se existir um índice espacial para a camada, a operação será muito mais eficaz.

Pense em uma camada sem um índice espacial como uma lista telefônica na qual os números de telefone não são ordenados ou indexados. A única maneira de encontrar o número de telefone de uma determinada pessoa é ler desde o início até encontrá-lo.

Os índices espaciais não são criados por padrão para uma camada vetorial QGIS, mas você pode criá-los facilmente. Isto é o que você precisa fazer:

  • crie índice espacial usando a classe QgsSpatialIndex():

    index = QgsSpatialIndex()
    
  • adicione feições ao índice — o indíce pega o objeto QgsFeature e o adiciona à estrutura de dados interna. Você pode criar o objeto manualmente ou usar um de uma chamada anterior para o método getFeatures().

    index.addFeature(feat)
    
  • Como alternativa, você pode carregar todas as feições de uma camada ao mesmo tempo usando o carregamento em massa

    index = QgsSpatialIndex(layer.getFeatures())
    
  • uma vez que o índice espacial é preenchido com alguns valores, você pode fazer algumas consultas

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

6.6. Criando Camadas Vetoriais

Existem várias maneiras de gerar um conjunto de dados de camada vetorial:

  • a classe QgsVectorFileWriter: Uma classe conveniente para gravar arquivos vetoriais em disco, usando uma chamada estática para writeAsVectorFormat() salva a camada vetorial inteira ou cria uma instância da classe e emite chamadas para addFeature(). Esta classe suporta todos os formatos vetoriais suportados pelo OGR (GeoPackage, Shapefile, GeoJSON, KML e outros).

  • a classe QgsVectorLayer: instancia um provedor de dados que interpreta o caminho fornecido (url) da fonte de dados para conectar e acessar os dados. Ele pode ser usado para criar camadas temporárias baseadas em memória (memory) e conectar-se a conjuntos de dados OGR (ogr), bancos de dados (postgres, spatialite, mysql, mssql) e mais (wfs, gpx, delimitedtext…).

6.6.1. De uma instância de QgsVectorFileWriter

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

Você também pode converter campos para torná-los compatíveis com diferentes formatos usando FieldValueConverter. Por exemplo, para converter tipos de variáveis ​​de matriz (por exemplo, em Postgres) em um tipo de texto, você pode fazer o seguinte:

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

class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):

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

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

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

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

Um SRC de destino também pode ser especificado — se uma instância válida de QgsCoordinateReferenceSystem for passada como o quarto parâmetro, a camada será transformada nesse SRC.

Para nomes de driver válidos, chame o método supportedFiltersAndFormats ou consulte formatos suportados pelo OGR — você deve passar o valor na coluna “Code” como o nome do driver.

Opcionalmente, você pode definir se deseja exportar apenas os recursos selecionados, passar outras opções específicas do driver para criação ou dizer ao gravador para não criar atributos… Há vários outros parâmetros (opcionais); veja a documentação de QgsVectorFileWriter para obter detalhes.

6.6.2. Diretamente das feições

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

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

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

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

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

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

# add a feature
fet = QgsFeature()

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

# delete the writer to flush features to disk
del writer

6.6.3. De uma instância de QgsVectorLayer

Entre todos os provedores de dados suportados pela classe QgsVectorLayer, vamos nos concentrar nas camadas baseadas em memória. O provedor de memória deve ser usado principalmente por desenvolvedores de complementos ou de 3os. Ele não armazena dados no disco, permitindo que os desenvolvedores os usem como um backend rápido para algumas camadas temporárias.

O provedor suporta campos string, int e double.

O provedor de memória também suporta a indexação espacial, que é ativada chamando a função createSpatialIndex(). Depois que o índice espacial for criado, você poderá iterar as feições em regiões menores mais rapidamente (já que não é necessário percorrer todos as feições, apenas aquelas no retângulo especificado).

Um provedor de memória é criado passando "memory" como a string do provedor para o construtor QgsVectorLayer.

O construtor também usa um URI que define o tipo de geometria da camada, um dos seguintes: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString",``”MultiPolygon”`` ou "None".

O URI também pode especificar o sistema de referência de coordenadas, campos e indexação do provedor de memória no URI. A sintaxe é:

crs=definição

Especifica o sistema de referência de coordenadas, onde a definição pode ser qualquer uma das formas aceitas pelo QgsCoordinateReferenceSystem.createFromString

index=yes

Especifica que o provedor irá usar o index espacial

field=name:type(length,precision)

Especifica um atributo da camada. O atributo tem um nome e, opcionalmente, um tipo (número inteiro, duplo ou sequência), comprimento e precisão. Pode haver múltiplas definições de campo.

O exemplo seguinte de URL incorpora todas estas opções

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

O código de exemplo a seguir ilustra a criação e o preenchimento de um provedor de memória

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

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

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

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

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

Finalmente, vamos verificar se tudo correu bem

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

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

6.7. Aparencia (Simbologia) de Camadas de Vetor

Quando uma camada vetorial está sendo renderizada, a aparência dos dados é dada pelos renderizador e símbolos associados à camada. Símbolos são classes que cuidam do desenho da representação visual de recursos, enquanto os renderizadores determinam qual símbolo será usado para uma feição específica.

O renderizador para uma determinada camada pode ser obtido como mostrado abaixo:

renderer = layer.renderer()

E com essa referência, vamos explorar um pouco

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

Existem vários tipos conhecidos de renderizadores disponíveis na biblioteca principal do QGIS:

Tipo

Classes

Descrição

singleSymbol

QgsSingleSymbolRenderer

Renderiza todas as características com o mesmo símbolo

categorizedSymbol

QgsCategorizedSymbolRenderer

Renderiza características usando um símbolo diferente para cada categoria

graduatedSymbol

QgsGraduatedSymbolRenderer

Renderiza caracter´sticas usando diferents símbolos para cada limite de valores

Também pode haver alguns tipos de renderizador personalizados, portanto, nunca assuma que existem apenas esses tipos. Você pode consultar QgsRendererRegistry do aplicativo para descobrir os renderizadores disponíveis no momento:

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

É possível obter um dump do conteúdo do renderizador em forma de texto - pode ser útil para depuração

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

6.7.1. Renderizador de símbolo único

Você pode obter o símbolo usado para renderização chamando o método meth:symbol() <qgis.core.QgsSingleSymbolRenderer.symbol> e alterá-lo com o método setSymbol() (observação para desenvolvedores do C++: o renderizador assume a propriedade do símbolo.)

Você pode alterar o símbolo usado por uma camada vetorial específica chamando setSymbol() passando uma instância da instância de símbolo apropriada. Símbolos para as camadas de ponto, linha e polígono podem ser criados chamando a função createSimple() das classes correspondentes QgsMarkerSymbol, QgsLineSymbol e QgsFillSymbol.

O dicionário passado para createSimple() define as propriedades de estilo do símbolo.

Por exemplo, você pode substituir o símbolo usado por uma determinada camada de ponto chamando setSymbol() passando uma instância de QgsMarkerSymbol, como no seguinte exemplo de código:

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

name indica a forma do marcador e pode ser um dos seguintes:

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

Para obter a lista completa de propriedades da primeira camada de símbolo de uma instância de símbolo, você pode seguir o código de exemplo:

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

Isso pode ser útil se você quiser alterar algumas propriedades:

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

6.7.2. Renderizador de Símbolo Categorizado

Ao usar um renderizador categorizado, é possível consultar e definir o atributo usado para classificação usando classAttribute() e setClassAttribute().

Para obter uma lista de categorias

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

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

Em que value() é o valor usado para discriminação entre categorias, label() é um texto usado para a descrição da categoria e symbol() retorna o símbolo atribuído.

Geralmente, o renderizador também armazena símbolo e rampa de cores originais que foram usados ​​para a classificação: métodos sourceColorRamp() e sourceSymbol().

6.7.3. Renderizador de Símbolo Graduado

Esse renderizador é muito semelhante ao renderizador de símbolo categorizado descrito acima, mas, em vez de um valor de atributo por classe, ele trabalha com intervalos de valores e, portanto, pode ser usado apenas com atributos numéricos.

Para saber mais sobre os intervalos usados ​​no renderizador

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

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

você pode novamente usar os métodos classAttribute (para encontrar o nome do atributo de classificação), sourceSymbol e sourceColorRamp. Além disso, existe o método mode que determina como os intervalos foram criados: usando intervalos iguais, quantis ou algum outro método.

Se você deseja criar seu próprio renderizador de símbolo graduado, pode fazê-lo como ilustrado no exemplo de trecho abaixo (que cria um arranjo simples de duas classes)

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

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

myVectorLayer.setRenderer(myRenderer)

6.7.4. Trabalhando com Símbolos

Para representação de símbolos, existe a classe base QgsSymbol com três classes derivadas:

Todo símbolo consiste em uma ou mais camadas de símbolos (classes derivadas de QgsSymbolLayer). As camadas de símbolo fazem a renderização real, a própria classe de símbolo serve apenas como um contêiner para as camadas de símbolo.

Tendo uma instância de um símbolo (por exemplo, de um renderizador), é possível explorá-lo: o método type diz se é um marcador, linha ou símbolo com preenchimento. Existe um método dump que retorna uma breve descrição do símbolo. Para obter uma lista de camadas de símbolos:

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

Para descobrir a cor do símbolo, use o método color e setColor para alterar sua cor. Além disso, com os símbolos dos marcadores, é possível consultar o tamanho e a rotação do símbolo com os métodos size e angle. Para símbolos de linha, o método width retorna a largura da linha.

Por padrão, tamanho e largura são em milimetros e ângulos em graus.

6.7.4.1. Trabalhando com Camadas de Símbolos

Como dito anteriormente, as camadas de símbolos (subclasses de QgsSymbolLayer) determinam a aparência das feições. Existem várias classes básicas de camadas de símbolos para uso geral. É possível implementar novos tipos de camadas de símbolos e, assim, personalizar arbitrariamente como as feiçõe serão renderizadas. O método layerType() identifica exclusivamente a classe da camada de símbolo — os básicos e o padrão são SimpleMarker, SimpleLine e SimpleFill tipos de camadas de símbolos.

Você pode obter uma lista completa dos tipos de camadas de símbolos que pode criar para uma determinada classe de camadas de símbolos com o seguinte código:

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

A classe QgsSymbolLayerRegistry gerencia um banco de dados de todos os tipos de camada de símbolo disponíveis.

Para acessar os dados da camada de símbolo, use o método properties() que retorna um dicionário de valores-chave de propriedades que determinam a aparência. Cada tipo de camada de símbolo possui um conjunto específico de propriedades que ele usa. Além disso, existem os métodos genéricos color, size, angle e width, com suas definições correspondentes. É claro que o tamanho e o ângulo estão disponíveis apenas para as camadas de símbolos dos marcadores e a largura para as camadas de símbolos de linha.

6.7.4.2. Criando Tipos de Camadas de Símbolos Personalizadas

Imagine que você gostaria de personalizar a maneira como os dados são renderizados. Você pode criar sua própria classe de camada de símbolo que desenhará as feições exatamente como você deseja. Aqui está um exemplo de um marcador que desenha círculos vermelhos com raio especificado

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

class FooSymbolLayer(QgsMarkerSymbolLayer):

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

  def layerType(self):
     return "FooMarker"

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

  def startRender(self, context):
    pass

  def stopRender(self, context):
      pass

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

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

O método layerType determina o nome da camada de símbolo; deve ser único entre todas as camadas de símbolos. O método properties é usado para persistência de atributos. O método clone deve retornar uma cópia da camada de símbolo com todos os atributos exatamente iguais. Finalmente, existem métodos de renderização: startRender é chamado antes de renderizar o primeiro recurso, stopRender quando a renderização é concluída, e renderPoint é chamado para fazer a renderização. As coordenadas do(s) ponto(s) já estão transformadas nas coordenadas de saída.

Para polilinhas e polígonos, a única diferença seria no método de renderização: você usaria renderPolyline que recebe uma lista de linhas, enquanto :meth:`renderPolygon <qgis.core. QgsFillSymbolLayer.renderPolygon> `recebe uma lista de pontos no anel externo como primeiro parâmetro e uma lista de anéis internos (ou None) como segundo parâmetro.

Geralmente, é conveniente adicionar uma GUI para definir atributos do tipo de camada de símbolo para permitir que os usuários personalizem a aparência: no caso do nosso exemplo acima, podemos permitir que o usuário defina o raio do círculo. O código a seguir implementa esse widget

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

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

        self.layer = None

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

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

    def symbolLayer(self):
        return self.layer

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

Este widget pode ser incorporado na caixa de diálogo de propriedades do símbolo. Quando o tipo de camada de símbolo é selecionado na caixa de diálogo de propriedades do símbolo, ele cria uma instância da camada de símbolos e uma instância do widget da camada de símbolos. Em seguida, chama o método setSymbolLayer para atribuir a camada de símbolo ao widget. Nesse método, o widget deve atualizar a interface do usuário para refletir os atributos da camada de símbolo. O método symbolLayer é usado para recuperar a camada de símbolo novamente pela caixa de diálogo de propriedades para usá-la para o símbolo.

Em cada alteração de atributos, o widget deve emitir o sinal changed() para permitir que o diálogo de propriedades atualize a visualização do símbolo.

Agora falta apenas a cola final: fazer o QGIS entender essas novas classes. Isso é feito adicionando a camada de símbolo ao registro. É possível usar a camada de símbolo também sem adicioná-la ao registro, mas algumas funcionalidades não funcionarão: por exemplo carregamento de arquivos de projeto com as camadas de símbolos personalizados ou incapacidade de editar os atributos da camada na GUI.

Você terá que criar metadados para a camada de símbolos

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

class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):

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

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

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

Você deve passar o tipo de camada (igual ao retornado pela camada) e o tipo de símbolo (marcador/linha/preenchimento) para o construtor da classe pai. O método createSymbolLayer() cuida da criação de uma instância da camada de símbolos com atributos especificados no dicionário props. E existe o método createSymbolLayerWidget() que retorna o widget de configurações para esse tipo de camada de símbolo.

O último passo para adicionar este símbolo de camada para o registro — e estamos prontos.

6.7.5. Criando Renderizadores Personalizados

Pode ser útil criar uma nova implementação de renderizador se você desejar personalizar as regras de como selecionar símbolos para renderização de recursos. Alguns casos de uso em que você deseja fazer isso: o símbolo é determinado a partir de uma combinação de campos, o tamanho dos símbolos muda dependendo da escala atual etc.

O código a seguir mostra um renderizador personalizado simples que cria dois símbolos de marcador e escolhe aleatoriamente um deles para cada feição

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


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

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

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

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

  def usedAttributes(self, context):
    return []

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

O construtor da classe pai QgsFeatureRenderer precisa de um nome de renderizador (que deve ser exclusivo entre os renderizadores). O método symbolForFeature é o método que decide qual símbolo será usado para um recurso específico. startRender e stopRender cuida da inicialização/finalização da renderização de símbolo. O método usedAttributes pode retornar uma lista de nomes de campos que o representante espera estar presente. Finalmente, o método clone deve retornar uma cópia do renderizador.

Como nas camadas de símbolos, é possível anexar uma GUI para a configuração do renderizador. Ele deve ser derivado de QgsRendererWidget. O código de amostra a seguir cria um botão que permite ao usuário definir o primeiro símbolo

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


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

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

  def renderer(self):
    return self.r

O construtor recebe instâncias da camada ativa (QgsVectorLayer), do estilo global (QgsStyle) e do renderizador atual. Se não houver renderizador ou o renderizador tiver um tipo diferente, ele será substituído pelo nosso novo renderizador; caso contrário, usaremos o renderizador atual (que já possui o tipo que precisamos). O conteúdo do widget deve ser atualizado para mostrar o estado atual do renderizador. Quando a caixa de diálogo do renderizador é aceita, o método do widget renderer é chamado para obter o renderizador atual - ele será atribuído à camada.

A última parte ausente são os metadados e a inclusão do renderizador no registro, caso contrário, o carregamento de camadas com o renderizador não funcionará e o usuário não poderá selecioná-lo na lista de renderizadores. Vamos terminar o nosso exemplo RandomRenderer

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

class RandomRendererMetadata(QgsRendererAbstractMetadata):

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

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

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

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

Da mesma forma que nas camadas de símbolos, o construtor de metadados abstratos aguarda o nome do renderizador, nome visível para os usuários e, opcionalmente, o nome do ícone do renderizador. O método createRenderer passa uma instância de QDomElement que pode ser usada para restaurar o estado do renderizador da árvore DOM. O método createRendererWidget cria o widget de configuração. Ele não precisa estar presente ou pode retornar None se o renderizador não vier com a GUI.

Para associar um ícone ao renderizador, você pode atribuí-lo no construtor QgsRendererAbstractMetadata como um terceiro argumento (opcional) — o construtor da classe base na função RandomRendererMetadata __init__`() se torna

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

O ícone também pode ser associado posteriormente, usando o método setIcon da classe de metadados. O ícone pode ser carregado de um arquivo (como mostrado acima) ou de um recurso Qt (PyQt5 inclui o compilador .qrc para Python).

6.8. Outros Tópicos

TODO:

  • criando/modificando símbolos

  • trabalhando com estilo (QgsStyle)

  • trabalhando com rampa de cores (QgsColorRamp)

  • explorando a camada de símbolos e os registros do renderizador