Ejercicio 7: Cálculo de indicadores hídricos
Este ejercicio es distinto a los seis anteriores: en vez de trabajar con capas ráster (imágenes), trabaja con una capa vectorial de puntos (localidades) y modifica directamente su tabla de atributos, calculando 3 indicadores de acceso a servicios de vivienda a partir de datos censales.
Conceptos clave de POO y PyQGIS usados en el ejercicio
-
Sin capa de salida separada: a diferencia de todos los
ejercicios anteriores,
initAlgorithmsolo declara una entrada (la capa vectorial) y ninguna salida de tipo "destino": el resultado se escribe directamente sobre la misma capa de entrada, agregando columnas nuevas a su tabla de atributos. -
Manipular una capa vectorial en memoria:
QgsVectorLayery sudataProvider()permiten leer y modificar los campos (columnas) y las entidades (filas) de una capa vectorial directamente desde código, algo que no aparecía en los ejercicios de raster. -
Expresiones de QGIS evaluadas por código:
QgsExpressionpermite construir una fórmula de QGIS (como las que se escriben en el "calculador de campos") como texto y evaluarla programáticamente para cada fila de la tabla. -
Sesión de edición con
with edit(...): es el equivalente en PyQGIS a iniciar y guardar automáticamente la edición de una capa; si ocurre un error dentro del bloque, los cambios no se confirman, evitando dejar la capa en un estado inconsistente. -
Iterar sobre las entidades (features) de una capa:
for renglon in localidades.getFeatures():recorre cada punto (cada localidad) de la capa, uno por uno, para calcular y asignar su indicador correspondiente.
Análisis y explicación de la estructura y lógica del código
"""
Script para calcular indicadores hídricos de localidades.
Se oupan los atributos asociados a una capa vectorial de puntos
(shapefile)
"""
#Importamos las blibliotecas o módulos necesarios para el correcto funcionamiento del script
from qgis.core import (
QgsProcessingAlgorithm, #Clase base de nuestra clase creada
QgsProcessingParameterVectorLayer, #Define la capa vectorial de entrada
QgsWkbTypes, # Restringe la entrada a geometrías de puntos
QgsProcessingMultiStepFeedback, #Muestra ek avance y perminte cancelar el proceso
QgsVectorLayer, #Carga y manipula la capa vectorial
QgsField, #Para la creación de los campos
QgsExpression, #Contruye y evalúa las expresiones
QgsExpressionContext, #Define el contexto para evakluar cada feature
QgsExpressionContextUtils, #Añade los ámbitos globalres del proyecto y de la capa
edit, #Abre una sesión de edición para modificar atributos
QgsProject #Para agregar la capa de salida el proyecto de QGIS
)
from qgis.PyQt.QtCore import QVariant #Para definir el tipo de dato de los campos nuevos
#Se crea la clase para la herramienta
class indicadores_hidricos(QgsProcessingAlgorithm):
#1. Nombre de la herramienta
def name(self):
return 'indicadores_hidricos'
#2. Nombre a mostrar en la GUI
def displayName(self):
return 'Cálculo de indicadores hídricos'
#3. Grupo de herramientas al que pertenece la herramienta
def group(self):
return 'Herramientas vectoriales'
#4. ID del grupo de herramientas
def groupId(self):
return 'herramientas_vectoriales'
#5. Descripción breve de la heramienta
def shortHelpString(self):
descripcion = """
Esta herramienta calcula indicadores hídricos básicos a partir de los
datos de un censo de población, un conteo de población o una encuesta
intercensal. En todos los casos los datos deben estar a nivel de localidad.
Los indicadores requieren las variables:
1. Total de viviendas particulares habitadas (VPH).
2. Total de VPH con servicio de agua dentro de la vivienda.
3. Total de VPH con servicio de sanitario.
4. Total de VPH con servicio de drenaje.
Al final se presentan los indicadores como proporción de viviendas
con dichos servicios. Los datos de salida se almacenan en la misma tabla
de atributos de la capa vectorial de entrada.
"""
return descripcion
#6. Cargar las entradas como parámetros de algorítmo
def initAlgorithm(self, config=None):
#Capa vectorial de entrada
self.addParameter(QgsProcessingParameterVectorLayer('capa_entrada', 'Capa vectorial de entrada con datos de viviendas', defaultValue=None))
#7. Algoritmo de procesamiento
def processAlgorithm(self, parameters, context, model_feedback):
feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
outputs = {}
results = {}
#Asignar el vector a una variable para manejarla en el código
localidades = QgsVectorLayer(parameters["capa_entrada"],'Localidades','ogr')
#Recuperar el proveedor de la capa vectorial
localidades_provider = localidades.dataProvider()
#Crear un diccionario para iterar en el cálcilo de los indicadores
indicadores_dict = {'ind1':['IndAguaDV', 'VPH_AGUADV'],
'ind2':['IndExcsa', 'VPH_EXCSA'],
'ind3':['IndDrenaj', 'VPH_DRENAJ']
}
#Variable para el total de viviendas habitadas
total_VPH = 'TVIVPARHAB'
step = 0
#Verificación de campos y cálculo de indicadores
for indicador in indicadores_dict:
#Verificar si ya existe el campo que se desea crear. En caso de que exista, borrar.
indicador_hidrico = indicadores_dict[indicador][0]
campo_de_tabla = indicadores_dict[indicador][1]
for campo in localidades.fields():
nombre_campo = campo.name()
if nombre_campo == indicador_hidrico:
index_campo = localidades_provider.fieldNameIndex(nombre_campo)
localidades_provider.deleteAttributes([index_campo])
localidades.updateFields()
print('Se borró un campo ya existente.')
#Agergar el nuevo campo a la tabla de atributos
localidades_provider.addAttributes([QgsField(indicador_hidrico, QVariant.Double)])
localidades.updateFields()
#Expresión con el cálculo del indicador
exp = QgsExpression(f'({campo_de_tabla} / {total_VPH}) * 100')
#Iteracion 1: '(VPH_AGUADV / TVIVPARHAB) * 100'
#Iteracion 2: '(VPH_EXCSA / TVIVPARHAB) * 100'
#Iteracion 3: '(VPH_DRENAJ / TVIVPARHAB) * 100'
#Se valida la expresión
validacion_expresion = QgsExpression.isValid(exp)
print('¿Expresion válida?', validacion_expresion)
#Si la expresión es válida, entonces de aplica en la tabla de atributos
if validacion_expresion == True:
contexto_capa = QgsExpressionContext()
contexto_capa.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(localidades))
#Aplicación de la expresion
with edit(localidades):
for renglon in localidades.getFeatures():
contexto_capa.setFeature(renglon)
renglon[indicador_hidrico] = exp.evaluate(contexto_capa)
localidades.updateFeature(renglon)
print(f'Expresión aplicadada para calcular {indicador_hidrico}')
else:
print(f'No se pudo aplicar la expresión para calcular {indicador_hidrico}')
#Señalar el progreso
step += 1
feedback.setCurrentStep(step)
if feedback.isCanceled():
return{}
#Regresar el resultado
outputs['indicadores'] = parameters['capa_entrada']
results['indicadores'] = outputs['indicadores']
#Despliegue de la capa con el resultado
if localidades.isValid() == True:
QgsProject.instance().addMapLayer(localidades)
else:
print('No se pudo cargar la capa vectorial.')
return results
#8. Creación de la instancia
def createInstance(self):
return self.__class__()
Sección 1 · Para qué sirve cada biblioteca importada
from qgis.core import (
QgsProcessingAlgorithm, #Clase base de nuestra clase creada
QgsProcessingParameterVectorLayer, #Define la capa vectorial de entrada
QgsWkbTypes, # Restringe la entrada a geometrías de puntos
QgsProcessingMultiStepFeedback, #Muestra ek avance y perminte cancelar el proceso
QgsVectorLayer, #Carga y manipula la capa vectorial
QgsField, #Para la creación de los campos
QgsExpression, #Contruye y evalúa las expresiones
QgsExpressionContext, #Define el contexto para evakluar cada feature
QgsExpressionContextUtils, #Añade los ámbitos globalres del proyecto y de la capa
edit, #Abre una sesión de edición para modificar atributos
QgsProject #Para agregar la capa de salida el proyecto de QGIS
)
from qgis.PyQt.QtCore import QVariant #Para definir el tipo de dato de los campos nuevos
from qgis.core import (...): en vez de escribir una líneafrom ... import ...por cada elemento (como en los ejercicios anteriores), aquí se agrupan todas las importaciones deqgis.coredentro de un solo paréntesis. Es otra forma válida de organizar losimportcuando son muchos.QgsProcessingAlgorithm: clase base de la que heredaindicadores_hidricos, igual que en todos los ejercicios anteriores.QgsProcessingParameterVectorLayer: a diferencia deQgsProcessingParameterRasterLayer(usado en los ejercicios 1 a 6), este parámetro declara una capa vectorial de entrada (puntos, líneas o polígonos).QgsWkbTypes: se importa para poder restringir el tipo de geometría aceptado (en este script se usaría para exigir que la capa sea de puntos), aunque en la versión actual del código no se aplica esa restricción dentro deinitAlgorithm.QgsProcessingMultiStepFeedback: reporta el progreso de los 3 pasos (uno por cada indicador calculado) y permite cancelar la ejecución.QgsVectorLayer: es la clase que representa una capa vectorial cargada en memoria; se usa para volver a abrir la capa de entrada y poder leer/escribir su tabla de atributos.QgsField: se usa para definir un nuevo campo (columna) que se agregará a la tabla de atributos, junto con su tipo de dato.QgsExpression: permite construir, como texto, una fórmula de QGIS (por ejemplo una división y una multiplicación) y luego evaluarla.QgsExpressionContext: representa el "contexto" necesario para evaluar una expresión sobre una entidad (feature) específica de la capa.QgsExpressionContextUtils: aporta funciones auxiliares para completar ese contexto con la información del proyecto y de la capa.edit: función especial de PyQGIS que abre automáticamente una sesión de edición sobre una capa (y la guarda o la cancela sola, según si hubo errores).QgsProject: se usa al final para agregar la capa ya modificada al proyecto actual de QGIS, de modo que el usuario la vea reflejada en el panel de capas.from qgis.PyQt.QtCore import QVariant:QVariantproviene de Qt (la biblioteca gráfica en la que está basada la interfaz de QGIS) y se usa para indicar el tipo de dato del nuevo campo (en este caso,QVariant.Double, un número decimal).
Sección 2 · Para qué sirve cada método de la clase
name(): devuelve'indicadores_hidricos', el identificador interno de la herramienta.displayName(): devuelve'Cálculo de indicadores hídricos', el nombre visible en QGIS.group()/groupId(): ubican la herramienta en el grupo'Herramientas vectoriales', distinto de los grupos usados en los ejercicios anteriores.shortHelpString(): explica qué variables censales necesita la capa de entrada (viviendas con agua, sanitario y drenaje) y qué calculará la herramienta.initAlgorithm(): declara la única entrada necesaria: la capa vectorial de localidades.processAlgorithm(): contiene toda la lógica de cálculo: crea los campos nuevos, arma y evalúa las expresiones, y escribe los resultados en la tabla de atributos.createInstance(): permite a QGIS crear nuevas copias de la herramienta.
Sección 3 · Declaración de parámetros (initAlgorithm)
def initAlgorithm(self, config=None):
#Capa vectorial de entrada
self.addParameter(QgsProcessingParameterVectorLayer('capa_entrada', 'Capa vectorial de entrada con datos de viviendas', defaultValue=None))
Solo se declara un parámetro: 'capa_entrada',
la capa vectorial con los datos censales de vivienda. No hay parámetro de
salida, porque el resultado se escribirá directamente sobre esta misma capa.
Sección 4 · Preparar la capa y el diccionario de indicadores
feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
outputs = {}
results = {}
#Asignar el vector a una variable para manejarla en el código
localidades = QgsVectorLayer(parameters["capa_entrada"],'Localidades','ogr')
#Recuperar el proveedor de la capa vectorial
localidades_provider = localidades.dataProvider()
#Crear un diccionario para iterar en el cálcilo de los indicadores
indicadores_dict = {'ind1':['IndAguaDV', 'VPH_AGUADV'],
'ind2':['IndExcsa', 'VPH_EXCSA'],
'ind3':['IndDrenaj', 'VPH_DRENAJ']
}
#Variable para el total de viviendas habitadas
total_VPH = 'TVIVPARHAB'
step = 0
feedback = QgsProcessingMultiStepFeedback(3, model_feedback): se declaran 3 pasos, uno por cada indicador que se calculará.localidades = QgsVectorLayer(parameters["capa_entrada"], 'Localidades', 'ogr'): vuelve a abrir la capa de entrada como un objetoQgsVectorLayermanipulable, usando'ogr'como proveedor de datos (el controlador estándar para shapefiles y formatos similares).localidades_provider = localidades.dataProvider(): obtiene el "proveedor de datos" de la capa, que es el objeto que permite agregar o eliminar campos de la tabla de atributos.indicadores_dict = {...}: un diccionario donde cada llave ('ind1','ind2','ind3') guarda una lista con el nombre del nuevo campo a crear y el nombre del campo existente que se usará en el cálculo. Esto evita repetir 3 veces casi el mismo bloque de código: en su lugar, se recorre este diccionario con un ciclo.total_VPH = 'TVIVPARHAB': guarda el nombre del campo que contiene el total de viviendas particulares habitadas, usado como denominador en los 3 indicadores.step = 0: variable contador que llevará el registro de en qué paso va la ejecución, para actualizar la barra de progreso.
Sección 5 · Ciclo principal: crear el campo y calcular cada indicador
for indicador in indicadores_dict:
#Verificar si ya existe el campo que se desea crear. En caso de que exista, borrar.
indicador_hidrico = indicadores_dict[indicador][0]
campo_de_tabla = indicadores_dict[indicador][1]
for campo in localidades.fields():
nombre_campo = campo.name()
if nombre_campo == indicador_hidrico:
index_campo = localidades_provider.fieldNameIndex(nombre_campo)
localidades_provider.deleteAttributes([index_campo])
localidades.updateFields()
print('Se borró un campo ya existente.')
#Agergar el nuevo campo a la tabla de atributos
localidades_provider.addAttributes([QgsField(indicador_hidrico, QVariant.Double)])
localidades.updateFields()
for indicador in indicadores_dict:: recorre las 3 llaves del diccionario ('ind1','ind2','ind3'), repitiendo todo el bloque interior una vez por cada indicador.indicador_hidrico = indicadores_dict[indicador][0]: obtiene el nombre del campo nuevo a crear (por ejemplo,'IndAguaDV').campo_de_tabla = indicadores_dict[indicador][1]: obtiene el nombre del campo existente que se usará en la fórmula (por ejemplo,'VPH_AGUADV').for campo in localidades.fields():: recorre todos los campos actuales de la tabla de atributos, para revisar si el campo que se quiere crear ya existe de una ejecución anterior.if nombre_campo == indicador_hidrico:: si encuentra un campo con ese mismo nombre, lo elimina primero (deleteAttributes) para evitar duplicados o valores de una ejecución previa.localidades.updateFields(): refresca la lista de campos de la capa después de eliminar uno (es necesario llamarlo para que QGIS "vea" el cambio).localidades_provider.addAttributes([QgsField(indicador_hidrico, QVariant.Double)]): agrega el nuevo campo a la tabla, indicando que su tipo de dato será decimal (QVariant.Double).
Sección 6 · Construir, validar y aplicar la expresión
#Expresión con el cálculo del indicador
exp = QgsExpression(f'({campo_de_tabla} / {total_VPH}) * 100')
#Se valida la expresión
validacion_expresion = QgsExpression.isValid(exp)
print('¿Expresion válida?', validacion_expresion)
#Si la expresión es válida, entonces de aplica en la tabla de atributos
if validacion_expresion == True:
contexto_capa = QgsExpressionContext()
contexto_capa.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(localidades))
#Aplicación de la expresion
with edit(localidades):
for renglon in localidades.getFeatures():
contexto_capa.setFeature(renglon)
renglon[indicador_hidrico] = exp.evaluate(contexto_capa)
localidades.updateFeature(renglon)
print(f'Expresión aplicadada para calcular {indicador_hidrico}')
else:
print(f'No se pudo aplicar la expresión para calcular {indicador_hidrico}')
#Señalar el progreso
step += 1
feedback.setCurrentStep(step)
if feedback.isCanceled():
return{}
exp = QgsExpression(f'({campo_de_tabla} / {total_VPH}) * 100'): construye, como texto, la fórmula del indicador (por ejemplo,'(VPH_AGUADV / TVIVPARHAB) * 100'), es decir, el porcentaje de viviendas con ese servicio.validacion_expresion = QgsExpression.isValid(exp): revisa que la fórmula esté bien escrita (sin errores de sintaxis) antes de intentar usarla.if validacion_expresion == True:: solo continúa con el cálculo si la expresión es válida; si no lo es, simplemente informa el error por consola y sigue con el siguiente indicador.contexto_capa = QgsExpressionContext()y.appendScopes(...): preparan el "entorno" necesario para que la expresión pueda usar variables del proyecto y de la capa al evaluarse.with edit(localidades):: abre una sesión de edición segura sobre la capa; todos los cambios hechos dentro de este bloque se confirman juntos al final (o se cancelan si algo falla).for renglon in localidades.getFeatures():: recorre cada punto (cada localidad) de la capa, uno por uno.contexto_capa.setFeature(renglon): le indica al contexto de la expresión sobre cuál entidad (fila) debe evaluarse en este momento.renglon[indicador_hidrico] = exp.evaluate(contexto_capa): calcula el valor de la fórmula para esta localidad y lo guarda en el campo nuevo.localidades.updateFeature(renglon): guarda el cambio hecho en esta entidad dentro de la sesión de edición.step += 1/feedback.setCurrentStep(step): avanza el contador de pasos y actualiza la barra de progreso después de terminar con un indicador completo.if feedback.isCanceled(): return {}: permite cancelar la ejecución entre un indicador y el siguiente.
Sección 7 · Entregar el resultado y mostrar la capa
#Regresar el resultado
outputs['indicadores'] = parameters['capa_entrada']
results['indicadores'] = outputs['indicadores']
#Despliegue de la capa con el resultado
if localidades.isValid() == True:
QgsProject.instance().addMapLayer(localidades)
else:
print('No se pudo cargar la capa vectorial.')
return results
outputs['indicadores'] = parameters['capa_entrada']/results['indicadores'] = outputs['indicadores']: como no hay una capa de salida nueva, el resultado que se reporta es la referencia a la misma capa de entrada, ya modificada.if localidades.isValid() == True:: comprueba que la capa siga siendo válida después de todos los cambios realizados.QgsProject.instance().addMapLayer(localidades): agrega (o vuelve a mostrar) la capa ya actualizada en el panel de capas del proyecto de QGIS, para que el usuario pueda verla de inmediato con sus nuevos campos calculados.return results: entrega el resultado final a QGIS, cerrando la ejecución de la herramienta.