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étodogetFeatures()
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 awriteAsVectorFormat()
que guarda toda la capa vectorial o crea una instancia de la clase y emite llamadas aaddFeature()
. 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 |
Renders all features with the same symbol |
|
categorizedSymbol |
Renders features using a different symbol for each category |
|
graduatedSymbol |
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:
QgsMarkerSymbol
— for point featuresQgsLineSymbol
— for line featuresQgsFillSymbol
— for polygon features
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