Escribir Scripts Python para el Marco de Procesamiento (QGIS3)¶
Uno puede escribir scripts pyqgis autónomos que pueden ser ejecutados mediante la Consola de Python en QGIS. Con unos pocos ajustes, puede hacer que sus scripts autónomos se ejecuten mediante el Marco de Procesamiento. Esto tiene varias ventajas. Primero, es mucho más fácil tomar entradas del usuario y escribir archivos de salida debido a que el Marco de Procesamiento ofrece un interfaz de usuario estandarizado para estos. Segundo, el tener su script en la Caja de Herramientas de Procesamiento también le permite que sea parte de cualquier Modelo de Procesamiento o que sea ejecutado como un Trabajo por Lotes con múltiples entradas. Este tutorial mostrará cómo escribir un script python personalizado que puede ser parte del Marco de Procesamiento en QGIS.
Nota
La API Procesamiento fue completamente revisada en QGIS3. Por favor refiérase a esta guía para las mejores prácticas y consejos.
Vista general de la tarea¶
Nuestro script realizará una operación de disolución basada en un campo escogido por el usuario. También sumará valores de otro campo para las entidades disueltas. En el ejemplo, disolveremos un archivo shape del mundo basado en un atributo CONTINENT
y sumaremos el campo POP_EST
para calcular la población total en la región disuelta.
Obtener los datos¶
Utilizaremos el conjunto de datos Admin 0 - Países de Natural Earth.
Descargue el archivo shape Admin 0 - países..
Fuente de Datos [NATURALEARTH]
Para su comodidad, puede descargar directamente un geopackage que contiene las capas de arriba de aquí abajo:
Procedimiento¶
En el Panel Navegador QGIS, localice el directorio donde guardó sus datos descargados. Expanda el
zip
o la entradagpkg
y seleccione la capane_10m_admin_0_countries
. Arrastre la capa a la pantalla.
Vaya a Scripts en la barra de herramientas y seleccione Crear Nuevo Script de Plantilla.
. Clic en el botón
La plantilla contiene todo el código repetitivo que es requerido para que el Marco Procesamiento lo reconozca como un script Procesamiento, y gestione entradas/salidas. Comencemos personalizando la plantilla ejemplo a nuestras necesidades. Primero cambie el nombre de clase de
ExampleProcessingAlgorithm
aDissolveProcessingAlgorithm
. Este nombre también necesita ser actualizado en el métodocreateInstance
. Agregue una dosctring a la clase que explica lo que hace el algoritmo.
A medida que se desliza hacia abajo, verá métodos que asignan nombre, grupo, descripción, etc. al script. cambie los valores de retorno para el método name para que sea
dissolve_with_sum
, el método displayName aDissolve with Sum
, el método group y el método groupId ascripts
. Cambie el valor de retorno del método shortHelpString a una descripción que aparecerá al usuario. Clic en el botón Guardar.
Nombre el script como
dissolve_with_sum
y guárdelo en la ubicación predeterminada bajo la carpeta .
Ahora definiremos las entradas para el script. La plantilla ya contiene una definición de un Capa Vector
INPUT
y una capaOUTPUT
. Agregaremos 2 nuevas entradas que permiten al usuario seleccionar unDISSOLVE_FIELD
y unSUM_FIELD
. Agregue un nueva importación arriba y el siguiente código en el métodoinitAlgorithm
. Clic en el botón Ejecutar para previsualizar los cambios.
from qgis.core import QgsProcessingParameterField
self.addParameter(
QgsProcessingParameterField(
self.DISSOLVE_FIELD,
'Choose Dissolve Field',
'',
self.INPUT))
self.addParameter(
QgsProcessingParameterField(
self.SUM_FIELD,
'Choose Sum Field',
'',
self.INPUT))
Verá un diálogo Disolver con Suma con nuestras entradas recientemente definidas. Seleccione la capa
ne_10m_admin_0_countries
como la Capa de entrada`. Como tanto Campo disolución como Campos Suma son filtrados en base a la capa de entrada, ellos serán pre-poblados con campos existentes de la capa de entrada. Clic en el botón Cerrar.
Ahora definiremos nuestra lógica personalizada para procesar datos en el método
processAlgorithm
. A este método se le pasa un diccionario lamadoparameters
. Contiene las entradas que el usuario tiene seleccionadas. Hay métodos que ayudan que le permiten tomar estas entradas y crear objetos apropiados. Primero conseguimos nuestras entradas usando los métodosparameterAsSource
yparameterAsString
. A continuación, queremos crear un sumidero de entidades donde escribiremos la salida. QGIS3 tiene una nueva clase llamadaQgsFeatureSink
que es la manera preferida para crear objetos que puedan aceptar nuevas entidades. La salida necesita sólo 2 campos - uno para el valor del campo disuelto y otro para la suma del campo seleccionado.
from PyQt5.QtCore import QVariant
from qgis.core import QgsField, QgsFields
source = self.parameterAsSource(
parameters,
self.INPUT,
context)
dissolve_field = self.parameterAsString(
parameters,
self.DISSOLVE_FIELD,
context)
sum_field = self.parameterAsString(
parameters,
self.SUM_FIELD,
context)
fields = QgsFields()
fields.append(QgsField(dissolve_field, QVariant.String))
fields.append(QgsField('SUM_' + sum_field, QVariant.Double))
(sink, dest_id) = self.parameterAsSink(
parameters,
self.OUTPUT,
context, fields, source.wkbType(), source.sourceCrs())
Ahora alistaremos los objetos de entrada y crearemos un diccionario para retener los valores únicos del dissolve_field y la suma de los valores del sum_field. Note el uso del método
feedback.pushInfo()
para comunicar el estado con el usuario.
feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')
features = source.getFeatures()
unique_values = set(f[dissolve_field] for f in features)
# Get Indices of dissolve field and sum field
dissolveIdx = source.fields().indexFromName(dissolve_field)
sumIdx = source.fields().indexFromName(sum_field)
# Find all unique values for the given dissolve_field and
# sum the corresponding values from the sum_field
sum_unique_values = {}
attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]} for f in source.getFeatures()]
for unique_value in unique_values:
val_list = [ f_attr[sum_field] for f_attr in attrs if f_attr[dissolve_field] == unique_value]
sum_unique_values[unique_value] = sum(val_list)
A continuación, llamaremos el algoritmo incorporado de procesamiento
native:dissolve
sobre la capa de entrada para generar las geometrías disueltas. Una vez que tengamos las geometrías disueltas, iteraremos a través de la salida del algoritmo de disolución y crearemos nuevos objetos para ser agregados a la salida. Al final regresaremos eldest_id
FeatureSink como la salida. Ahora el script está listo. Clic en el botón Ejecutar.
Nota
Note el uso de parameters[self.INPUT]
para ir a buscar la capa de entrada del diccionario de parámetros directamente sin definirlo como una fuente. Como estamos pasando el objeto de entrada al algoritmo sin hacer nada con él, no es necesario definirlo como una fuente.
from qgis.core import QgsFeature
# Running the processing dissolve algorithm
feedback.pushInfo('Dissolving features')
dissolved_layer = processing.run("native:dissolve", {
'INPUT': parameters[self.INPUT],
'FIELD': dissolve_field,
'OUTPUT': 'memory:'
}, context=context, feedback=feedback)['OUTPUT']
# Read the dissolved layer and create output features
for f in dissolved_layer.getFeatures():
new_feature = QgsFeature()
# Set geometry to dissolved geometry
new_feature.setGeometry(f.geometry())
# Set attributes from sum_unique_values dictionary that we had computed
new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
return {self.OUTPUT: dest_id}
En el diálogo Disolver con Suma, seleccione
ne_10m_admin_0_countries
como la Capa de entrada,CONTINENT
como el Campo de disolución yPOP_EST
como el Campo de suma. Clic en Ejecutar.
Una vez finalice el procesamiento, haga clic en el botón Cerrar y cámbiese a la ventana principal de QGIS.
Verá la capa de salida disuelta con un objeto espacial por cada continente y la población total agregada de los países individuales que pertenecen a ese continente.
Otra ventaja de escribir scripts de procesamiento es que los métodos dentro del Marco de Procesamiento identifican la sección de capa y automáticamente filtran sus entrada para usar sólo los objetos seleccionados. Esto sucede porque estamos definiendo nuestra entrada como una
QgsProcessingParameterFeatureSource
. Una fuente de objeto permite usar CUALQUIER objeto que contiene objetos vector, no sólo una capa vector, por lo que cuando hay objetos seleccionados en su capa y se pide a Procesamiento usar objetos seleccionados, la entrada es pasada a su script como un objetoQgsProcessingFeatureSource
que contiene los objetos seleccionados y no la capa vector completa. Aquí tiene una demostración rápida de esta funcionalidad. Digamos que queremos disolver sólo ciertos continentes. Creemos una selección usando la herramienta Seleccionar objeto por Expresión.
Ingrese la siguiente expresión para seleccionar objetos de Norte y Sud América y clic en Seleccionar.
"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
Verá que los objetos seleccionados son resaltados en amarillo. Localice el script
dissolve_with_sum
y haga doble-clic para ejecutarlo.
En el diálogo Disolver con Suma, seleccione
ne_10m_admin_0_countries
como la Capa de entrada. Esta vez, asegúrese que marca la casilla Sólo objetos seleccionados. ElijaSUBREGIÓN
como el Campo disolver yPOP_EST
como el Campo suma.
Una vez que termine el proceso, clic en Cerrar y vuelva a la ventana principal QGIS. Notará una nueva capa con sólo los objetos seleccionados disueltos. Clic en el botón Identificar y clic en un objeto para inspeccionar y verificar que el script funcionó correctamente.
Abajo está el script completo para referencia. Puede modificarlo para ajustarlo a sus necesidades.
# -*- coding: utf-8 -*-
"""
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
QgsFeatureSink,
QgsFeature,
QgsField,
QgsFields,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterField,
)
import processing
class DissolveProcessingAlgorithm(QgsProcessingAlgorithm):
"""
Dissolve algorithm that dissolves features based on selected
attribute and summarizes the selected field by cumputing the
sum of dissolved features.
"""
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
DISSOLVE_FIELD = 'dissolve_field'
SUM_FIELD = 'sum_field'
def tr(self, string):
"""
Returns a translatable string with the self.tr() function.
"""
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return DissolveProcessingAlgorithm()
def name(self):
"""
Returns the algorithm name, used for identifying the algorithm. This
string should be fixed for the algorithm, and must not be localised.
The name should be unique within each provider. Names should contain
lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'dissolve_with_sum'
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return self.tr('Dissolve with Sum')
def group(self):
"""
Returns the name of the group this algorithm belongs to. This string
should be localised.
"""
return self.tr('scripts')
def groupId(self):
"""
Returns the unique ID of the group this algorithm belongs to. This
string should be fixed for the algorithm, and must not be localised.
The group id should be unique within each provider. Group id should
contain lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'scripts'
def shortHelpString(self):
"""
Returns a localised short helper string for the algorithm. This string
should provide a basic description about what the algorithm does and the
parameters and outputs associated with it..
"""
return self.tr("Dissolves selected features and creates and sums values of features that were dissolved")
def initAlgorithm(self, config=None):
"""
Here we define the inputs and output of the algorithm, along
with some other properties.
"""
# We add the input vector features source. It can have any kind of
# geometry.
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr('Input layer'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
self.addParameter(
QgsProcessingParameterField(
self.DISSOLVE_FIELD,
'Choose Dissolve Field',
'',
self.INPUT))
self.addParameter(
QgsProcessingParameterField(
self.SUM_FIELD,
'Choose Sum Field',
'',
self.INPUT))
# We add a feature sink in which to store our processed features (this
# usually takes the form of a newly created vector layer when the
# algorithm is run in QGIS).
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
self.tr('Output layer')
)
)
def processAlgorithm(self, parameters, context, feedback):
"""
Here is where the processing itself takes place.
"""
source = self.parameterAsSource(
parameters,
self.INPUT,
context
)
dissolve_field = self.parameterAsString(
parameters,
self.DISSOLVE_FIELD,
context)
sum_field = self.parameterAsString(
parameters,
self.SUM_FIELD,
context)
fields = QgsFields()
fields.append(QgsField(dissolve_field, QVariant.String))
fields.append(QgsField('SUM_' + sum_field, QVariant.Double))
(sink, dest_id) = self.parameterAsSink(
parameters,
self.OUTPUT,
context, fields, source.wkbType(), source.sourceCrs())
# Create a dictionary to hold the unique values from the
# dissolve_field and the sum of the values from the sum_field
feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')
features = source.getFeatures()
unique_values = set(f[dissolve_field] for f in features)
# Get Indices of dissolve field and sum field
dissolveIdx = source.fields().indexFromName(dissolve_field)
sumIdx = source.fields().indexFromName(sum_field)
# Find all unique values for the given dissolve_field and
# sum the corresponding values from the sum_field
sum_unique_values = {}
attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]}
for f in source.getFeatures()]
for unique_value in unique_values:
val_list = [ f_attr[sum_field]
for f_attr in attrs if f_attr[dissolve_field] == unique_value]
sum_unique_values[unique_value] = sum(val_list)
# Running the processing dissolve algorithm
feedback.pushInfo('Dissolving features')
dissolved_layer = processing.run("native:dissolve", {
'INPUT': parameters[self.INPUT],
'FIELD': dissolve_field,
'OUTPUT': 'memory:'
}, context=context, feedback=feedback)['OUTPUT']
# Read the dissolved layer and create output features
for f in dissolved_layer.getFeatures():
new_feature = QgsFeature()
# Set geometry to dissolved geometry
new_feature.setGeometry(f.geometry())
# Set attributes from sum_unique_values dictionary that we had computed
new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
return {self.OUTPUT: dest_id}
If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)