Cargando capas de SpatiaLite en el visor de PyQGIS

Las bases de datos geográficos de SpatiaLite son una alternativa válida para almacenar información espacial adoptando lineamientos del OGC. QGIS ha sido de los primeros en soportarlo y lo dispone a través de su API. A continuación se muestra la forma de cargar datos de SpatiaLite en el visor de PyQGIS.

Insumos

Para empezar, se recomienda haber realizado los ejercicios anteriores con PyQGIS. En todo caso, puede descargarse el código fuente base desde aquí. El código corre en GNU/Linux y con un poco de configuración correrá en Windows sin problemas.

Datos

Si no se cuenta con datos de SpatiaLite se puede seguir este enlace para migrar datos desde formato Shapefile. De cualquier manera, pueden descargarse datos de prueba desde aquí.

La meta


Según la documentación de PyQGIS, la forma de cargar las capas de SpatiaLite es la siguiente:

  uri = QgsDataSourceURI()
  uri.setDatabase('/home/martin/test-2.3.sqlite')
  uri.setDataSource('', 'Towns', 'Geometry')
  vLayer = QgsVectorLayer(uri.uri(), 'Towns', 'spatialite')

Sin embargo, para integrarlo al visor de PyQGIS es preferible algo como esto:

Interfaz de cargue SpatiaLite

Para lograrlo, se crea un diálogo haciendo uso de la herramienta Qt Designer y se genera una clase en Python para controlar la lógica del diálogo. A continuación se describen los pasos a seguir.

Archivo recursos.qrc

Es necesario agregar al archivo de recursos de la aplicación, una imagen para el nuevo botón que abrirá el diálogo de cargue:

Botón cargue SpatiaLite

La imagen se puede descargar desde aquí y se copia a la carpeta imagenes que se encuentra en la raíz del proyecto (ver la sección de Insumos). Desde Qt Designer se agrega al archivo de recursos en el prefijo botones, así:

Recursos de la aplicación

Después de guardar el archivo se compila desde la consola para generar el archivo recursos.py de la siguiente forma: pyrcc4 -o recursos.py recursos.qrc

El diálogo

En Qt Designer se crea un nuevo diálogo con botones (Dialog with Buttons Bottom) y se agregan los siguientes controles:

ControlNombrePropiedades
QLineEdittxtRutaSolo lectura.
QPushButtonbtnAbrir
QListWidgetlstCapasselectionMode: MultiSelection selectionBehavior: SelectRows

Los nombres de los controles son importantes porque serán usados en el código.

Diálogo en Qt Designer

Se guarda el archivo como dlgCargueSpatiaLite.ui y se compila desde la consola para generar el archivo de Python correspondiente, así: pyuic4 -o dlgCargueSpatiaLite_ui.py dlgCargueSpatiaLite.ui

Archivo principal VisorShapefiles.py

En el archivo principal se debe incluir el siguiente código en el lugar apropiado:

Cargar el nuevo botón:

        self.actionCargarSpatiaLite = QAction(QIcon(':/botones/imagenes/mActionAddSpatiaLiteLayer.png'), 
"Agregar capa de SpatiaLite", self.frame)
self.connect(self.actionCargarSpatiaLite, SIGNAL("activated()"),
self.cargarSpatiaLite)

self.toolbar.addAction(self.actionCargarSpatiaLite)

Función para cargar las capas:

 def cargarSpatiaLite( self ): 
""" Agregar capa SpatiaLite al canvas """
from cargue_spatialite import DialogoCargueSpatiaLite
dialogoSL = DialogoCargueSpatiaLite( self )
result = dialogoSL.mostrar()

if result == QDialog.Accepted:
for layerName in dialogoSL.layers:
uri = QgsDataSourceURI()
uri.setDatabase( dialogoSL.dbPath )
uri.setDataSource( '', layerName, dialogoSL.layers[ layerName ] )

layer = QgsVectorLayer( uri.uri(), layerName, 'spatialite' )
self.cargarCapa( layer )

La clase cargue_spatialite.py

Esta clase se encarga de controlar la lógica del diálogo dlgCargueSpatiaLite creado anteriormente. Para empezar, permite abrir una ventana para ubicar el archivo de SpatiaLite que tiene extensión .sqlite (ver línea 32) y haciendo uso del módulo para SQLite que viene por defecto en Python desde la versión 2.5 (ver línea 46), genera una lista de las tablas espaciales disponibles para que el usuario seleccione las de su interés (ver línea 64).

1.  # -*- coding:utf-8 -*-
2.
3. from os.path import isfile
4.
5. from PyQt4.QtCore import Qt, QObject, SIGNAL, QVariant
6. from PyQt4.QtGui import ( QDialog, QFileDialog, QListWidgetItem, QIcon,
7. QDialogButtonBox, QMessageBox )
8.
9. from dlgCargueSpatiaLite_ui import Ui_CargueSpatiaLite
10.
11. class DialogoCargueSpatiaLite( QDialog, Ui_CargueSpatiaLite ):
12. """ Open a dialog to load SpatiaLite layers """
13. def __init__( self, parent ):
14. self.parent = parent
15. QDialog.__init__( self, self.parent )
16. self.setupUi( self )
17.
18. self.dbLayers = {} # layer name:[layer name, geometry column, geometry type, srid]
19. self.layers = {} # layer name: geometry column
20. self.dbPath = ''
21.
22. self.btnAceptar = self.buttonBox.button( QDialogButtonBox.Ok )
23. self.btnAceptar.setEnabled( False )
24.
25. # SIGNALS - SLOTS
26. self.connect( self.lstCapas, SIGNAL( "itemSelectionChanged()" ), self.selectionChanged )
27. self.connect( self.btnAbrir, SIGNAL( "clicked()" ), self.openFileDialog )
28. QObject.connect( self.buttonBox, SIGNAL( "accepted()" ), self.aceptar )
29.
30. def openFileDialog( self ):
31. """ Open a dialog to locate the sqlite file """
32. path = QFileDialog.getOpenFileName( self.parent, "Abrir base de datos de SpatiaLite",
33. '.', "SpatiaLite (*.sqlite)" )
34.
35. if path: self.dbPath = path # To make possible cancel the FileDialog and continue loading a predefined db
36.
37. self.openDBFile( self.dbPath )
38.
39. def openDBFile( self, path ):
40. """ Open the SpatiaLite file to extract info about geographic tables """
41. if isfile( unicode( path ) ):
42. self.txtRuta.setText( path )
43. self.lstCapas.clear()
44.
45. # Execute a query in SQLite to show the available layers
46. from sqlite3 import connect
47. conn = connect( str( self.dbPath ) )
48. cursor = conn.cursor()
49. cursor.execute( "SELECT f_table_name, f_geometry_column, type, srid FROM 'geometry_columns'" )
50.
51. self.dbLayers = {}
52. for row in cursor:
53. self.dbLayers[ row[ 0 ] ] = row # Load the layer info into the dictionary
54.
55. if len( self.dbLayers ):
56. self.cargarCapasALista()
57. else:
58. msg = QMessageBox(QMessageBox.Warning, 'Advertencia',
59. 'La base de datos de SpatiaLite no contiene \\n' \\
60. + 'tablas con datos geográficos.', QMessageBox.Ok, self)
61. msg.addButton('Aceptar', QMessageBox.AcceptRole)
62. msg.exec_()
63.
64. def cargarCapasALista( self ):
65. """ Load available layers into the list """
66. for layerName in self.dbLayers:
67. # Geometry handling
68. geom = self.dbLayers[ layerName ][ 2 ]
69. if geom == "POINT" or geom == 'MULTIPOINT':
70. icono= QIcon( ":/imgs/imagenes/punto.png" )
71. elif geom == "LINESTRING" or geom == "MULTILINESTRING" or geom == "LINESTRINGM":
72. icono= QIcon( ":/imgs/imagenes/linea.png" )
73. elif geom == "POLYGON" or geom == "MULTIPOLYGON":
74. icono= QIcon( ":/imgs/imagenes/poligono.png" )
75. else:
76. raise RuntimeError, "Geometría desconocida: " + str( geom )
77.
78. newItem = QListWidgetItem( icono, layerName )
79. newItem.setData( Qt.UserRole, QVariant( self.dbLayers[ layerName ][ 1 ] ) )
80. self.lstCapas.addItem( newItem )
81.
82. def selectionChanged( self ):
83. """ Enable/disable the Ok button """
84. if len( self.lstCapas.selectedItems() ) > 0:
85. self.btnAceptar.setEnabled( True )
86. else:
87. self.btnAceptar.setEnabled( False )
88.
89. def mostrar( self ):
90. """ Show the dialog """
91. return self.exec_()
92.
93. def aceptar( self ):
94. """ Load the selected layers into the dictionary """
95. for item in self.lstCapas.selectedItems():
96. self.layers[ item.text() ] = item.data( Qt.UserRole ).toString()

La clase cargue_spatialite.py puede descargarse desde aquí.

Finalmente, el visor de PyQGIS luce así:

Visor PyQGIS con capas de SpatiaLite

Conclusión

La API de QGIS admite diversos formatos que pueden ayudar a enriquecer el visor de PyQGIS. Por ejemplo, es posible cargar archivos ráster, servicios web de mapas (WMS), archivos de texto delimitados por comas y geometrías de bases de datos de PostgreSQL/PostGIS y SpatiaLite.