Usar capas vectoriales

Esta sección sumariza varias acciones que pueden ser realizadas con las capas vectoriales

La mayor parte del trabajo acá expuesto está basado en los métodos de la clase QgsVectorLayer.

Recuperando información sobre atributos

Puede recuperar información sobre los campos asociados a una capa vectorial llamando el método fields()  de un objeto de la clase QgsVectorLayer

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

Iterando sobre la capa vectorial

a iteración de las entidades de una capa vectorial es una de las tareas más comunes. A continuación se muestra un ejemplo del código básico simple para realizar esta tarea y mostrar cierta información sobre cada característica. Se supone que la variable “”layer”” tiene un objeto QgsVectorLayer.

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)

Seleccionando objetos espaciales

En el escritorio QGIS, las entidades se pueden seleccionar de diferentes maneras: el usuario puede hacer clic en una entidad, dibujar un rectángulo en el lienzo del mapa o utilizar un filtro de expresión. Las entidades seleccionadas normalmente se resaltan en un color diferente (el valor predeterminado es el amarillo) para llamar la atención del usuario sobre la selección.

A veces puede ser útil seleccionar características mediante programación o cambiar el color predeterminado.

Para seleccionar todas las características, se puede utilizar el método selectAll()

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

Para seleccionar usando una expresión, utilice el 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 cambiar el color de selección puede utilizar el método setSelectionColor() de QgsMapCanvas como se muestra en el ejemplo siguiente:

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

Para agregar entidades a la lista de entidades seleccionada para una capa determinada, puede llamar a select() pasándole la lista de identificadores de las entidades:

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 borrar la selección:

layer.removeSelection()

Accediendo a atributos

Los atributos pueden ser referidos por su nombre:

print(feature['name'])

Alternativamente, los atributos pueden ser referidos por índice. Esto es un poco más rápido que usar el nombre. Por ejemplo, para obtener el primer atributo:

print(feature[0])

Iterando sobre rasgos seleccionados

Si solo necesita entidades seleccionadas, puede utilizar el método selectedFeatures() de la capa vectorial:

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

Iterando sobre un subconjunto de rasgos

Si desea iterar sobre un subconjunto determinado de entidades de una capa, como las que se encuentran en un área determinada, debe agregar un objeto QgsFeatureRequest a la llamada de getFeatures(). Este es un ejemplo:

areaOfInterest = QgsRectangle(450290,400520, 450750,400780)

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

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

En aras de la velocidad, la intersección a menudo se realiza solo con el cuadro delimitador de la entidad. Sin embargo, hay una bandera ExactIntersect que se asegura de que solo se devolverán las entidades que se cruzan:

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

Con setLimit() puede limitar el número de entidades solicitadas. Este es un ejemplo:

request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
    # loop through only 2 features

Si necesita un filtro basado en atributos en su lugar (o además) de uno espacial como se muestra en los ejemplos anteriores, puede crear un objeto QgsExpression y pasarlo al constructor QgsFeatureRequest. Este es un ejemplo:

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

Consulte Expresiones, Filtros y Calculando Valores para obtener detalles sobre la sintaxis admitida por QgsExpression.

La solicitud se puede utilizar para definir los datos recuperados para cada entidad, por lo que el iterador devuelve todas las entidades, pero devuelve datos parciales para cada una de ellas.

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

Modificación de capas vectoriales

La mayoría de los proveedores de datos vectoriales admiten la edición de datos de capa. A veces solo admiten un subconjunto de posibles acciones de edición. Utilice la función capabilities() para averiguar qué conjunto de funcionalidad es compatible.

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

Para obtener una lista de todas las capacidades disponibles, consulte la documentación API Documentation of QgsVectorDataProvider.

Para imprimir la descripción textual de las capacidades de las capas en una lista separada por comas, puede utilizar capabilitiesString() como en el ejemplo siguiente:

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'

Mediante el uso de cualquiera de los métodos siguientes para la edición de capas vectoriales, los cambios se confirman directamente en el almacén de datos subyacente (un archivo, una base de datos, etc.). En caso de que desee realizar solo cambios temporales, vaya a la siguiente sección que explica cómo hacer modificaciones con la edición de cambios de búfer.

Nota

Si está trabajando dentro de QGIS (ya sea desde la consola o desde un complemento), podría ser necesario forzar un redibujo del lienzo del mapa para ver los cambios que ha realizado en la geometría, en el estilo o en los atributos:

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

Añadir Entidades

Cree algunas instancias de QgsFeature y pase una lista de ellas al método del proveedor addFeatures() Devolverá dos valores: resultado (verdadero/falso) y lista de características agregadas (su identificador lo establece el almacén de datos).

Para configurar los atributos de la entidad, puede inicializar la entidad pasando un objeto QgsFields (puede obtenerlo del método fields() de la capa vectorial) o llamar a initAttributes() pasando el número de campos que desea agregar.

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

Borrar Entidades

Para eliminar algunas entidades, solo tiene que proporcionar una lista de identificaciones de entidades.

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

Modificar los objetos espaciales

Es posible cambiar la geometría de la entidad o cambiar algunos atributos. En el ejemplo siguiente se cambian primero los valores de los atributos con los índices 0 y 1 y, a continuación, se cambia la geometría de la entidad.

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

Truco

Favorecer la clase QgsVectorLayerEditUtils para ediciones de solo geometría

Si solo necesita cambiar geometrías, podría considerar el uso de QgsVectorLayerEditUtils que proporciona algunos métodos útiles para editar geometrías (trasladar, insertar o mover vértices, etc.).

Modificación de capas vectoriales con un búfer de edición

Al editar vectores dentro de la aplicación QGIS, primero tiene que comenzar el modo de edición para una capa en particular, luego hacer algunas modificaciones y finalmente confirmar (o revertir) los cambios. Todos los cambios que realice no se escribirán hasta que los confirme — ellos permanecen en el búfer de edición en memoria de la capa. Es posible utilizar esta funcionalidad también mediante programación — es sólo otro método para la edición de capas vectoriales que complementa el uso directo de proveedores de datos. Utilice esta opción al proporcionar algunas herramientas GUI para la edición de capas vectoriales, ya que esto permitirá al usuario decidir si desea confirmar/revertir y permite el uso de deshacer/rehacer. Cuando se confirman los cambios, todos los cambios del búfer de edición se guardan en el proveedor de datos.

Los métodos son similares a los que hemos visto en el proveedor, pero se llaman en el objeto QgsVectorLayer en su lugar.

Para que estos métodos funcionen, la capa debe estar en modo de edición. Para iniciar el modo de edición, utilice el método startEditing() Para detener la edición, utilice los métodos commitChanges() o rollBack(). El primero confirmará todos los cambios en el origen de datos, mientras que el segundo los descartará y no modificará el origen de datos en absoluto.

Para averiguar si una capa está en modo de edición, utilice el método isEditable().

Aquí tiene algunos ejemplos que muestran cómo utilizar estos métodos de edición.

from qgis.PyQt.QtCore import QVariant

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

# set new geometry (QgsGeometry instance) for a feature
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to a given value
layer.changeAttributeValue(fid, fieldIndex, value)

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

Para hacer que deshacer/rehacer trabaje correctamente, las llamadas mencionadas arriba tienen que ser envueltas en los comandos undo. (Si no le importa deshacer/rehacer y desea que los cambios se almacenen inmediatamente, entonces tendrá un trabajo más fácil por editando con proveedor de datos.)

Así es cómo usted puede utilizar la funcionalidad de deshacer:

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

El método beginEditCommand() creará un comando interno de «activo» y registrará los cambios posteriores en la capa vectorial. Con la llamada a el comando endEditCommand() se inserta en la pila de deshacer y el usuario podrá deshacer/rehacerlo desde la GUI. En caso de que algo saliera mal al realizar los cambios, el método destroyEditCommand() quitará el comando y revertirá todos los cambios realizados mientras este comando estaba activo.

También puede utilizar la instrucción with edit(layer)- para encapsular la confirmación y la reversión en un bloque de código más semántico, como se muestra en el ejemplo siguiente:

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

Esto llamará automáticamente a commitChanges() al final. Si ocurre alguna excepción, hará rollBack() a todos los cambios. En caso de que se encuentre un problema dentro de commitChanges() (cuando el método devuelve False) se producirá una excepción QgsEditError.

Agregando y Removiendo Campos

Para agregar campos (atributos), usted necesita especificar una lista de definiciones de campo. Para la eliminación de campos sólo proporcione una lista de índices de campo.

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

Después de agregar o quitar campos en el proveedor de datos, los campos de la capa deben actualizarse porque los cambios no se propagan automáticamente.

layer.updateFields()

Truco

Guarde directamente los cambios usando el comando basado en with

Usando with edit(layer): los cambios se confirmarán automáticamente llamando a commitChanges() al final. Si se produce alguna excepción, hará un rollBack() de todos los cambios. Consulte Modificación de capas vectoriales con un búfer de edición.

Usar índice espacial

Los índices espaciales pueden mejorar drásticamente el rendimiento del código si necesita realizar consultas frecuentes en una capa vectorial. Imagine, por ejemplo, que está escribiendo un algoritmo de interpolación, y que para una ubicación determinada necesita conocer los 10 puntos más cercanos de una capa de puntos, con el fin de utilizar esos puntos para calcular el valor interpolado. Sin un índice espacial, la única manera de que QGIS encuentre esos 10 puntos es calcular la distancia desde todos y cada uno de los puntos hasta la ubicación especificada y luego comparar esas distancias. Esto puede ser una tarea que consume mucho tiempo, especialmente si necesita repetirse para varias ubicaciones. Si existe un índice espacial para la capa, la operación es mucho más efectiva.

Piense en una capa sin un índice espacial como una guía telefónica en la que los números de teléfono no se ordenan ni indexan. La única manera de encontrar el número de teléfono de una persona determinada es leer desde el principio hasta que lo encuentres.

Los índices espaciales no se crean de forma predeterminada para una capa vectorial QGIS, pero puede crearlos fácilmente. Esto es lo que tienes que hacer:

  • crear índice espacial utilizando la clase QgsSpatialIndex():

    index = QgsSpatialIndex()
    
  • agregar entidades al índice — el índice toma el objeto QgsFeature y lo agrega a la estructura de datos interna. Puede crear el objeto manualmente o usar uno de una llamada anterior al método getFeatures() del proveedor.

    index.insertFeature(feat)
    
  • alternativamente, puede cargar todas las entidades de una capa a la vez utilizando la carga masiva

    index = QgsSpatialIndex(layer.getFeatures())
    
  • Una vez que el índice espacial se llena con algunos valores, puede realizar algunas consultas

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

Creación de capas vectoriales

Hay varias maneras de generar un dataset de capa vectorial:

  • la clase QgsVectorFileWriter class: Una clase cómoda para escribir archivos vectoriales en el disco, utilizando una llamada estática a writeAsVectorFormat() que guarda toda la capa vectorial o crea una instancia de la clase y emite llamadas a addFeature(). Esta clase admite todos los formatos vectoriales que Soporta OGR (GeoPackage, Shapefile, GeoJSON, KML y otros).

  • la clase QgsVectorLayer: crea una instancia de un proveedor de datos que interpreta la ruta de acceso proporcionada (url) del origen de datos para conectarse a los datos y tener acceso a ellos. Se puede utilizar para crear capas temporales basadas en memoria (memory) y conectarse a datasets OGR (ogr), bases de datos (postgres, spatialite, mysql, mssql) y más (wfs, gpx, delimitedtext…).

Desde una instancia de QgsVectorFileWriter

# Write to a GeoPackage (default)
error = QgsVectorFileWriter.writeAsVectorFormat(layer,
                                                "/path/to/folder/my_data",
                                                "")
if error[0] == QgsVectorFileWriter.NoError:
    print("success!")
# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
error = QgsVectorFileWriter.writeAsVectorFormat(layer,
                                                "/path/to/folder/my_esridata",
                                                "UTF-8",
                                                driverName="ESRI Shapefile")
if error[0] == QgsVectorFileWriter.NoError:
    print("success again!")

El tercer parámetro (obligatorio) especifica la codificación de texto de salida. Sólo algunos controladores necesitan esto para el funcionamiento correcto - Shapefile es uno de ellos (otros controladores ignorarán este parámetro). Especificar la codificación correcta es importante si utiliza caracteres internacionales (no US-ASCII).

# Write to an ESRI GDB file
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.driverName = "FileGDB"
# if no geometry
opts.overrideGeometryType = QgsWkbTypes.NullGeometry
opts.actionOnExistingFile = QgsVectorFileWriter.ActionOnExistingFile.CreateOrOverwriteLayer
opts.layerName = 'my_new_layer_name'
error = QgsVectorFileWriter.writeAsVectorFormat(layer=vlayer,
                                                fileName=gdb_path,
                                                options=opts)
if error[0] == QgsVectorFileWriter.NoError:
  print("success!")
else:
  print(error)

También puede convertir campos para que sean compatibles con formatos diferentes usando FieldValueConverter. Por ejemplo, para convertir tipos de variable matriz (por ejemplo, en Postgres) en un tipo texto, puede hacer lo siguiente:

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 is a QgsVectorFileWriter.SaveVectorOptions as above
opts.fieldValueConverter = converter

También se puede especificar un CRS de destino — si se pasa una instancia válida de QgsCoordinateReferenceSystem como cuarto parámetro, la capa se transforma a ese CRS.

Para los nombres de controlador válidos, llame al método supportedFiltersAndFormats o consulte los “formatos admitidos por OGR`_ — debe pasar el valor en la columna «Código» como el nombre del controlador.

Opcionalmente, puede establecer si desea exportar solo las entidades seleccionadas, pasar más opciones específicas del controlador para la creación o indicar al escritor que no cree atributos… Hay una serie de otros parámetros (opcionales); consulte la documentación de QgsVectorFileWriter para más detalles.

Directamente desde las funciones

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. encoding of the attributes
3. field map
4. geometry type - from WKBTYPE enum
5. layer's spatial reference (instance of
   QgsCoordinateReferenceSystem) - optional
6. driver name for the output file """

writer = QgsVectorFileWriter("my_shapes.shp", "UTF-8", fields, QgsWkbTypes.Point, driverName="ESRI Shapefile")

if writer.hasError() != QgsVectorFileWriter.NoError:
    print("Error when creating shapefile: ",  w.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

Desde una instancia de QgsVectorLayer

Entre todos los proveedores de datos admitidos por la clase QgsVectorLayer, vamos a centrarnos en las capas basadas en memoria. Proveedor de memoria está destinado a ser utilizado principalmente por plugins o desarrolladores de aplicaciones de 3as partes. No almacena datos en el disco, lo que permite a los desarrolladores utilizarlos como un backend rápido para algunas capas temporales.

El proveedor admite los campos string, int y double.

El proveedor de memoria también admite la indexación espacial, que se habilita llamando a la función createSpatialIndex() del proveedor. Una vez creado el índice espacial, podrá recorrer iterando sobre las entidades dentro de regiones más pequeñas más rápido (ya que no es necesario atravesar todas las entidades, solo las del rectángulo especificado)..

Un proveedor de memoria se crea pasando "memory" como la cadena del proveedor al constructor QgsVectorLayer.

El constructor también toma un URI que define el tipo de geometría de la capa, uno de: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" o "None".

El URI también puede especificar el sistema de referencia de coordenadas, los campos y la indexación del proveedor de memoria en el URI. La sintaxis es:

crs=definición

Especifica el sistema de referencia de coordenadas, donde la definition puede ser cualquiera de las formas aceptadas por QgsCoordinateReferenceSystem.createFromString

index=yes

Especifica que el proveedor utilizará un índice espacial

campo

Especifica un atributo de la capa. El atributo tiene un nombre y, opcionalmente, un tipo (entero, doble o cadena), longitud y precisión. Puede haber múltiples definiciones de campo

El siguiente ejemplo de una URI incorpora todas estas opciones

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

El siguiente código de ejemplo ilustra como crear y rellenar un proveedor de memoria

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 a comprobar si todo salió bien

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

Apariencia (Simbología) de capas vectoriales

Cuando una capa vectorial se representa, la apariencia de los datos se indica por renderer y símbolos asociados a la capa. Los símbolos son clases que se encargan del dibujo de la representación visual de las entidades, mientras que los renderizadores determinan qué símbolo se utilizará para una entidad determinada.

El renderizador para una capa determinada se puede obtener como se muestra a continuación:

renderer = layer.renderer()

Y con esa referencia, vamos a explorar un poco

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

Hay varios tipos de renderizadores conocidos disponibles en la biblioteca principal de QGIS:

Tipo

Clase

Descripción

singleSymbol

QgsSingleSymbolRenderer

Renders all features with the same symbol

categorizedSymbol

QgsCategorizedSymbolRenderer

Renders features using a different symbol for each category

graduatedSymbol

QgsGraduatedSymbolRenderer

Renders features using a different symbol for each range of values


There might be also some custom renderer types, so never make an assumption there are just these types. You can query the application’s QgsRendererRegistry to find out currently available renderers:

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

It is possible to obtain a dump of a renderer contents in text form — can be useful for debugging

print(renderer.dump())

Representador de Símbolo Único

You can get the symbol used for rendering by calling symbol() method and change it with setSymbol() method (note for C++ devs: the renderer takes ownership of the symbol.)

You can change the symbol used by a particular vector layer by calling setSymbol() passing an instance of the appropriate symbol instance. Symbols for point, line and polygon layers can be created by calling the createSimple() function of the corresponding classes QgsMarkerSymbol, QgsLineSymbol and QgsFillSymbol.

The dictionary passed to createSimple() sets the style properties of the symbol.

For example you can replace the symbol used by a particular point layer by calling setSymbol() passing an instance of a QgsMarkerSymbol, as in the following code example:

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

name indicates the shape of the marker, and can be any of the following:

  • circle

  • cuadrado

  • cross

  • rectangle

  • Diamante

  • pentagon

  • triángulo

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

To get the full list of properties for the first symbol layer of a symbol instance you can follow the example code:

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

This can be useful if you want to alter some properties:

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

Representador de símbolo categorizado

When using a categorized renderer, you can query and set the attribute that is used for classification: use the classAttribute() and setClassAttribute() methods.

To get a list of categories

for cat in renderer.categories():
    print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))

Where value() is the value used for discrimination between categories, label() is a text used for category description and symbol() method returns the assigned symbol.

The renderer usually stores also original symbol and color ramp which were used for the classification: sourceColorRamp() and sourceSymbol() methods.

Graduated Symbol Renderer

This renderer is very similar to the categorized symbol renderer described above, but instead of one attribute value per class it works with ranges of values and thus can be used only with numerical attributes.

To find out more about ranges used in the renderer

for ran in renderer.ranges():
    print("{} - {}: {} {}".format(
        ran.lowerValue(),
        ran.upperValue(),
        ran.label(),
        ran.symbol()
      ))

you can again use the classAttribute (to find the classification attribute name), sourceSymbol and sourceColorRamp methods. Additionally there is the mode method which determines how the ranges were created: using equal intervals, quantiles or some other method.

If you wish to create your own graduated symbol renderer you can do so as illustrated in the example snippet below (which creates a simple two class arrangement)

from qgis.PyQt import QtGui

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

myVectorLayer.setRenderer(myRenderer)
QgsProject.instance().addMapLayer(myVectorLayer)

Trabajo con Símbolos

For representation of symbols, there is QgsSymbol base class with three derived classes:

Every symbol consists of one or more symbol layers (classes derived from QgsSymbolLayer). The symbol layers do the actual rendering, the symbol class itself serves only as a container for the symbol layers.

Having an instance of a symbol (e.g. from a renderer), it is possible to explore it: the type method says whether it is a marker, line or fill symbol. There is a dump method which returns a brief description of the symbol. To get a list of symbol layers:

for i in range(symbol.symbolLayerCount()):
    lyr = symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))

To find out symbol’s color use color method and setColor to change its color. With marker symbols additionally you can query for the symbol size and rotation with the size and angle methods. For line symbols the width method returns the line width.

De forma predeterminada el tamaño y ancho están en milímetros, los ángulos en grados.

Working with Symbol Layers

As said before, symbol layers (subclasses of QgsSymbolLayer) determine the appearance of the features. There are several basic symbol layer classes for general use. It is possible to implement new symbol layer types and thus arbitrarily customize how features will be rendered. The layerType() method uniquely identifies the symbol layer class — the basic and default ones are SimpleMarker, SimpleLine and SimpleFill symbol layers types.

You can get a complete list of the types of symbol layers you can create for a given symbol layer class with the following code:

from qgis.core import QgsSymbolLayerRegistry
myRegistry = QgsApplication.symbolLayerRegistry()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
    print(item)

Salida:

EllipseMarker
FilledMarker
FontMarker
GeometryGenerator
SimpleMarker
SvgMarker
VectorField

The QgsSymbolLayerRegistry class manages a database of all available symbol layer types.

To access symbol layer data, use its properties() method that returns a key-value dictionary of properties which determine the appearance. Each symbol layer type has a specific set of properties that it uses. Additionally, there are the generic methods color, size, angle and width, with their setter counterparts. Of course size and angle are available only for marker symbol layers and width for line symbol layers.

Creating Custom Symbol Layer Types

Imagine you would like to customize the way how the data gets rendered. You can create your own symbol layer class that will draw the features exactly as you wish. Here is an example of a marker that draws red circles with specified radius

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)

The layerType method determines the name of the symbol layer; it has to be unique among all symbol layers. The properties method is used for persistence of attributes. The clone method must return a copy of the symbol layer with all attributes being exactly the same. Finally there are rendering methods: startRender is called before rendering the first feature, stopRender when the rendering is done, and renderPoint is called to do the rendering. The coordinates of the point(s) are already transformed to the output coordinates.

For polylines and polygons the only difference would be in the rendering method: you would use renderPolyline which receives a list of lines, while renderPolygon receives a list of points on the outer ring as the first parameter and a list of inner rings (or None) as a second parameter.

Usually it is convenient to add a GUI for setting attributes of the symbol layer type to allow users to customize the appearance: in case of our example above we can let user set circle radius. The following code implements such widget

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

This widget can be embedded into the symbol properties dialog. When the symbol layer type is selected in symbol properties dialog, it creates an instance of the symbol layer and an instance of the symbol layer widget. Then it calls the setSymbolLayer method to assign the symbol layer to the widget. In that method the widget should update the UI to reflect the attributes of the symbol layer. The symbolLayer method is used to retrieve the symbol layer again by the properties dialog to use it for the symbol.

On every change of attributes, the widget should emit the changed() signal to let the properties dialog update the symbol preview.

Now we are missing only the final glue: to make QGIS aware of these new classes. This is done by adding the symbol layer to registry. It is possible to use the symbol layer also without adding it to the registry, but some functionality will not work: e.g. loading of project files with the custom symbol layers or inability to edit the layer’s attributes in GUI.

We will have to create metadata for the symbol layer

from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry

class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):

  def __init__(self):
    QgsSymbolLayerAbstractMetadata.__init__(self, "FooMarker", QgsSymbol.Marker)

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

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

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

You should pass layer type (the same as returned by the layer) and symbol type (marker/line/fill) to the constructor of the parent class. The createSymbolLayer() method takes care of creating an instance of symbol layer with attributes specified in the props dictionary. And there is the createSymbolLayerWidget() method which returns the settings widget for this symbol layer type.

El último pase es adicionar esta capa símbolo al registro — y estamos listos.

Creating Custom Renderers

It might be useful to create a new renderer implementation if you would like to customize the rules how to select symbols for rendering of features. Some use cases where you would want to do it: symbol is determined from a combination of fields, size of symbols changes depending on current scale etc.

The following code shows a simple custom renderer that creates two marker symbols and chooses randomly one of them for every feature

import random
from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer


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

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

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

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

  def usedAttributes(self):
    return []

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

from qgis.gui import QgsRendererWidget
class RandomRendererWidget(QgsRendererWidget):
  def __init__(self, layer, style, renderer):
    QgsRendererWidget.__init__(self, 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.clicked.connect(self.setColor1)

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

  def renderer(self):
    return self.r

The constructor of the parent QgsFeatureRenderer class needs a renderer name (which has to be unique among renderers). The symbolForFeature method is the one that decides what symbol will be used for a particular feature. startRender and stopRender take care of initialization/finalization of symbol rendering. The usedAttributes method can return a list of field names that the renderer expects to be present. Finally, the clone method should return a copy of the renderer.

Like with symbol layers, it is possible to attach a GUI for configuration of the renderer. It has to be derived from QgsRendererWidget. The following sample code creates a button that allows the user to set the first symbol

from qgis.gui import QgsRendererWidget, QgsColorButton

class RandomRendererWidget(QgsRendererWidget):
  def __init__(self, layer, style, renderer):
    QgsRendererWidget.__init__(self, 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.connect(self.btn1, SIGNAL("clicked()"), self.setColor1)

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

  def renderer(self):
    return self.r

The constructor receives instances of the active layer (QgsVectorLayer), the global style (QgsStyle) and the current renderer. If there is no renderer or the renderer has different type, it will be replaced with our new renderer, otherwise we will use the current renderer (which has already the type we need). The widget contents should be updated to show current state of the renderer. When the renderer dialog is accepted, the widget’s renderer method is called to get the current renderer — it will be assigned to the layer.

The last missing bit is the renderer metadata and registration in registry, otherwise loading of layers with the renderer will not work and user will not be able to select it from the list of renderers. Let us finish our RandomRenderer example

from qgis.core import QgsRendererAbstractMetadata,QgsRendererRegistry,QgsApplication

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

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

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

Similarly as with symbol layers, abstract metadata constructor awaits renderer name, name visible for users and optionally name of renderer’s icon. The createRenderer method passes a QDomElement instance that can be used to restore the renderer’s state from the DOM tree. The createRendererWidget method creates the configuration widget. It does not have to be present or can return None if the renderer does not come with GUI.

To associate an icon with the renderer you can assign it in the QgsRendererAbstractMetadata constructor as a third (optional) argument — the base class constructor in the RandomRendererMetadata __init__() function becomes

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

The icon can also be associated at any later time using the setIcon method of the metadata class. The icon can be loaded from a file (as shown above) or can be loaded from a Qt resource (PyQt5 includes .qrc compiler for Python).

Más Temas

PENDIENTE:

  • crear/modificar símbolos

  • working with style (QgsStyle)

  • working with color ramps (QgsColorRamp)

  • exploring symbol layer and renderer registries