Integración de PyQGIS con Jupyter Lab usando Anaconda
Instala PyQGIS en un ambiente de Anaconda para automatizar procesos de QGIS usando Python
Este breve tutorial es un complemento a la entrada anterior de mi blog, Introducción a PyQGIS, donde describí cómo empezar a usar PyQGIS desde la consola de Python integrada en la interfaz gráfica de QGIS. Si bien lo recomendable es que mientras desarrollamos un proceso de automatización con PyQGIS usemos la consola integrada para ir viendo visualmente que los resultados que obtenemos son los que deseamos, también es un poco engorroso escribir código desde el editor de esa consola, porque es muy básico y le faltan características como autocompletado, resaltado, revisión de sintaxis y otras herramientas que los IDES modernos ofrecen.
Desde hace pocos años es posible instalar QGIS como una librería más de conda, lo que permite crear ambientes aisalados que incluyan QGIS. De esta forma, es posible desarrollar usando PyQGIS sin afectar la instalación de la aplicación de escritorio de QGIS. Esto es importante, porque si queremos instalar librerías adicionales de análisis de datos, es mucho más seguro hacerlo desde un ambiente aislado, así en caso que haya incompatibilidades entre librerías o que se afecten aquellas de las que depende PyQGIS y esto genere errores, no hay más que crear un nuevo ambiente y empezar otra vez. Si eso ocurriera dentro de la aplicación de escritorio, probablemente tendríamos que desinstalar y volver a instalar QGIS. Antes de que QGIS fuera una librería de conda, no era fácil establecer un ambiente de desarrollo de PyQGIS que no estuviera ligado al intérprete de Python incluido en QGIS. Afortunadamente con conda es tremendamente fácil instalar un ambiente.
Para empezar es necesario contar con Anaconda o Miniconda. Luego desde la terminal de comandos podemos ejecutar de forma secuencial los sigientes comandos:
conda create -n qgis -c conda-forge qgis jupyterlab matplotlib
conda activate qgis
jupyter lab
En la primera línea estamos creando un nuevo ambiente llamado qgis
y además le pedimos que instale las librerías qgis
, jupyterlab
y matplotlib
desde el canal de conda-forge
. En la segunda línea activamos el ambiente y en la tercera lanzamos Jupyter Lab que es mi IDE preferido para trabajar en análisis datos.
Con esto podemos crear un nuevo notebook y ya podremos ejecutar código de PyQGIS. Así de sencillo. Por ejemplo, usando una versión ligeramente modificada del primer script que usé en mi entrada anterior así lo puedo ejecutar:
import json
import os
import sys
from qgis.core import QgsProject, QgsVectorLayer, QgsProcessingFeatureSourceDefinition, QgsFeatureRequest, QgsVectorFileWriter, QgsApplication, QgsProcessingProvider
from qgis.analysis import QgsNativeAlgorithms
import matplotlib.pyplot as plt
os.environ["QT_QPA_PLATFORM"] = "offscreen" # Esta línea es necesaria para poder iniciar una instancia QgsApplication(), en caso de que sea necesario
QgsApplication.setPrefixPath(None, True)
import processing
from processing.core.Processing import Processing
# Inicia algoritmos
Processing.initialize()
# establece directorio de trabajo
os.chdir('/home/studio-lab-user/sagemaker-studiolab-notebooks/intro_pyqgis')
proyecto = QgsProject.instance()
# carpeta donde están los datos
dir_data = "datos"
nombre_layer_colonias = "colonias_cdmx"
layer_colonias = QgsVectorLayer(f"{dir_data}/colonias_iecm_2019/mgpc_2019.shp", nombre_layer_colonias)
if not proyecto.mapLayersByName(nombre_layer_colonias):
proyecto.addMapLayer(layer_colonias)
print("Número de capas en el proyecto:", proyecto.count())
print("CRS del proyecto:", proyecto.crs().description())
print("Directorio del proyecto:", proyecto.homePath())
print("Número de filas:", layer_colonias.featureCount())
print("Número de columnas:", len(layer_colonias.fields()))
print("Sistema de coordenadas:", layer_colonias.crs().description())
for f in layer_colonias.fields():
print(f.name(), f.typeName())
#
feature = layer_colonias.getFeature(10)
print(feature)
print(feature.attributes())
#
geom = feature.geometry()
print(geom.centroid())
coords = json.loads(geom.asJson())["coordinates"][0][0]
coords_centroide = json.loads(geom.centroid().asJson())["coordinates"]
x, y = [[xy[n] for xy in coords] for n in [0, 1]]
ax = plt.subplot()
ax.plot(x, y)
ax.plot([coords_centroide[0]], [coords_centroide[1]], 'bo')
ax.text(x=coords_centroide[0], y=coords_centroide[1], s=feature.attribute("NOMUT"))
Como podemos ver, el resultado es el mismo que el que obtuvimos usando la consola de Python dentro de QGIS.
Para poder correr este script tuve que hacer unas pequeñas modificaciones, la más importante y necesaria para que todo funcione, fue que tuve que agregar la línea:
QgsApplication.setPrefixPath(None, True)
antes de importar la librería processing
e inicializar los algoritmos. Usualmente el primer argumento es la ubicación de la carpeta donde está instalado QGIS, aunque en mi caso parece que el sistema lo identifica automáticamente y por eso simplemente puedo poner un None
. En cualquier caso, si al intentarlo no te funciona, intenta poniendo la dirección a la carpeta de tu instalación de QGIS, en mi caso sería:
QgsApplication.setPrefixPath('/home/studio-lab-user/.conda/envs/qgis/share/qgis', True)
Otra línea que tuve que agregar fue:
os.chdir('/home/studio-lab-user/sagemaker-studiolab-notebooks/intro_pyqgis')
para establecer el directorio de trabajo principal ya que este notebook lo estoy escribiendo desde el servicio de Amazon SageMaker Studio Lab, que ofrece de forma gratuita (y sin pedir medios de pago ni cuenta de AWS) una máquina virtual de 16GB de RAM y 15GB de almacenamiento persistente. El almacenamiento persistente me permite crear ambientes de conda y las librerías instaladas permanecen siempre, a diferencia de Google Colab (que también es un gran, gran, gran servicio) donde debes instalar las librerías cada vez que inicias una sesión. Que por cierto, en un post de su blog Lerry W nos muestra cómo instalar QGIS dentro de Colab.
Volviendo al código, a continuación ejecutamos un fragmento del script intro_pyqgis_04.py
en el que calculamos los centroides de la capa de colonias_cdmx
y exportamos los resultados en formato GeoJSON. La diferencia con el publicado anteriormente es que en lugar de añadir la capa resultante al proyecto, ya mejor la exportamos directamente especificando la ruta en el parámetro 'OUTPUT'
. Dado que ya no estamos usando la interfaz gráfica, en realidad no tiene mucho sentido añadirla al proyecto para poder visualizarla.
params = {
'INPUT': QgsProcessingFeatureSourceDefinition(
nombre_layer_colonias, selectedFeaturesOnly=False,
featureLimit=-1,
flags=QgsProcessingFeatureSourceDefinition.FlagOverrideDefaultGeometryCheck,
geometryCheck=QgsFeatureRequest.GeometrySkipInvalid
),
'ALL_PARTS': False,
'OUTPUT': f"{dir_data}/centroides_colonias.geojson"
}
try:
out = processing.run("native:centroids", params)
except:
out = processing.run("native:centroids", params)
try
-except
porque, por alguna razón desconocida para mí, al ejecutar una vez la línea processing.run("native:centroids", params)
arroja un error, pero después de dos o más ejecuciones funciona sin problema. Hay que tener en cuenta que esta versión de QGIS en conda no es oficialmente soportada por los desarrolladores QGIS y puede que tenga comportamientos un poco distintos que la versión oficial que es la que viene con QGIS.
Y precisamente, como no tenemos la interfaz gráfica de QGIS, no podemos visualizar directamente los resultados. Aún así, nos la podemos arreglar para usar matplotlib y graficar los puntos. Otra opción obvia es GeoPandas.
layer_centroides = QgsVectorLayer(out["OUTPUT"])
geoms = [f.geometry().asPoint() for f in layer_centroides.getFeatures()]
points_x = [p.x() for p in geoms]
points_y = [p.y() for p in geoms]
ax = plt.subplot()
ax.plot(points_x, points_y, 'bo', alpha=0.2)
ax.set_aspect('equal')
ax.figure.set_size_inches(8, 8)
ax.set_title("Colonias de la CDMX")
Revisa otras entradas de este blog:
- Introducción a PyQGIS (Python + QGIS)
- Mapas de puntos con Python
- Introducción a bases de datos relacionales y SQL para científicos sociales
- Recuadros para mapas en Geopandas
- Etiquetado de variables y valores en las encuestas de INEGI usando Python
- Generando archivos de Excel con formatos y gráficas usando Python
- Trabajando con archivos de Excel complejos en Pandas
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
for alg in QgsApplication.processingRegistry().algorithms():
print(alg.id(), "--->", alg.displayName())