plugin.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS
  5. Date : May 23, 2011
  6. copyright : (C) 2011 by Giuseppe Sucameli
  7. email : brush.tyler@gmail.com
  8. ***************************************************************************/
  9. /***************************************************************************
  10. * *
  11. * This program is free software; you can redistribute it and/or modify *
  12. * it under the terms of the GNU General Public License as published by *
  13. * the Free Software Foundation; either version 2 of the License, or *
  14. * (at your option) any later version. *
  15. * *
  16. ***************************************************************************/
  17. """
  18. # this will disable the dbplugin if the connector raise an ImportError
  19. from .connector import GPKGDBConnector
  20. from qgis.PyQt.QtCore import Qt, QFileInfo, QCoreApplication
  21. from qgis.PyQt.QtGui import QIcon
  22. from qgis.PyQt.QtWidgets import QApplication, QAction, QFileDialog
  23. from qgis.core import (
  24. Qgis,
  25. QgsApplication,
  26. QgsDataSourceUri,
  27. QgsSettings,
  28. QgsProviderRegistry,
  29. )
  30. from qgis.gui import QgsMessageBar
  31. from ..plugin import DBPlugin, Database, Table, VectorTable, RasterTable, TableField, TableIndex, TableTrigger, \
  32. InvalidDataException
  33. def classFactory():
  34. return GPKGDBPlugin
  35. class GPKGDBPlugin(DBPlugin):
  36. @classmethod
  37. def icon(self):
  38. return QgsApplication.getThemeIcon("/mGeoPackage.svg")
  39. @classmethod
  40. def typeName(self):
  41. return 'gpkg'
  42. @classmethod
  43. def typeNameString(self):
  44. return QCoreApplication.translate('db_manager', 'GeoPackage')
  45. @classmethod
  46. def providerName(self):
  47. return 'ogr'
  48. @classmethod
  49. def connectionSettingsKey(self):
  50. return 'providers/ogr/GPKG/connections'
  51. def databasesFactory(self, connection, uri):
  52. return GPKGDatabase(connection, uri)
  53. def connect(self, parent=None):
  54. conn_name = self.connectionName()
  55. md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
  56. conn = md.findConnection(conn_name)
  57. if conn is None: # non-existent entry?
  58. raise InvalidDataException(self.tr('There is no defined database connection "{0}".').format(conn_name))
  59. uri = QgsDataSourceUri()
  60. uri.setDatabase(conn.uri())
  61. return self.connectToUri(uri)
  62. @classmethod
  63. def addConnection(self, conn_name, uri):
  64. md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
  65. conn = md.createConnection(uri.database(), {})
  66. md.saveConnection(conn, conn_name)
  67. return True
  68. @classmethod
  69. def addConnectionActionSlot(self, item, action, parent, index):
  70. QApplication.restoreOverrideCursor()
  71. try:
  72. filename, selected_filter = QFileDialog.getOpenFileName(parent,
  73. parent.tr("Choose GeoPackage file"), None, "GeoPackage (*.gpkg)")
  74. if not filename:
  75. return
  76. finally:
  77. QApplication.setOverrideCursor(Qt.WaitCursor)
  78. conn_name = QFileInfo(filename).fileName()
  79. uri = QgsDataSourceUri()
  80. uri.setDatabase(filename)
  81. self.addConnection(conn_name, uri)
  82. index.internalPointer().itemChanged()
  83. class GPKGDatabase(Database):
  84. def __init__(self, connection, uri):
  85. Database.__init__(self, connection, uri)
  86. def connectorsFactory(self, uri):
  87. return GPKGDBConnector(uri, self.connection())
  88. def dataTablesFactory(self, row, db, schema=None):
  89. return GPKGTable(row, db, schema)
  90. def vectorTablesFactory(self, row, db, schema=None):
  91. return GPKGVectorTable(row, db, schema)
  92. def rasterTablesFactory(self, row, db, schema=None):
  93. return GPKGRasterTable(row, db, schema)
  94. def info(self):
  95. from .info_model import GPKGDatabaseInfo
  96. return GPKGDatabaseInfo(self)
  97. def sqlResultModel(self, sql, parent):
  98. from .data_model import GPKGSqlResultModel
  99. return GPKGSqlResultModel(self, sql, parent)
  100. def sqlResultModelAsync(self, sql, parent):
  101. from .data_model import GPKGSqlResultModelAsync
  102. return GPKGSqlResultModelAsync(self, sql, parent)
  103. def registerDatabaseActions(self, mainWindow):
  104. action = QAction(self.tr("Run &Vacuum"), self)
  105. mainWindow.registerAction(action, self.tr("&Database"), self.runVacuumActionSlot)
  106. Database.registerDatabaseActions(self, mainWindow)
  107. def runVacuumActionSlot(self, item, action, parent):
  108. QApplication.restoreOverrideCursor()
  109. try:
  110. if not isinstance(item, (DBPlugin, Table)) or item.database() is None:
  111. parent.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."),
  112. Qgis.Info, parent.iface.messageTimeout())
  113. return
  114. finally:
  115. QApplication.setOverrideCursor(Qt.WaitCursor)
  116. self.runVacuum()
  117. def runVacuum(self):
  118. self.database().aboutToChange.emit()
  119. self.database().connector.runVacuum()
  120. self.database().refresh()
  121. def runAction(self, action):
  122. action = str(action)
  123. if action.startswith("vacuum/"):
  124. if action == "vacuum/run":
  125. self.runVacuum()
  126. return True
  127. return Database.runAction(self, action)
  128. def uniqueIdFunction(self):
  129. return None
  130. def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
  131. from qgis.core import QgsVectorLayer
  132. vl = QgsVectorLayer(self.uri().database() + '|subset=' + sql, layerName, 'ogr')
  133. return vl
  134. def supportsComment(self):
  135. return False
  136. class GPKGTable(Table):
  137. def __init__(self, row, db, schema=None):
  138. """Constructs a GPKGTable
  139. :param row: a three elements array with: [table_name, is_view, is_sys_table]
  140. :type row: array [str, bool, bool]
  141. :param db: database instance
  142. :type db:
  143. :param schema: schema name, defaults to None, ignored by GPKG
  144. :type schema: str, optional
  145. """
  146. Table.__init__(self, db, None)
  147. self.name, self.isView, self.isSysTable = row
  148. def ogrUri(self):
  149. ogrUri = "%s|layername=%s" % (self.uri().database(), self.name)
  150. return ogrUri
  151. def mimeUri(self):
  152. # QGIS has no provider to load Geopackage vectors, let's use OGR
  153. return "vector:ogr:%s:%s" % (self.name, self.ogrUri())
  154. def toMapLayer(self, geometryType=None, crs=None):
  155. from qgis.core import QgsVectorLayer
  156. provider = "ogr"
  157. uri = self.ogrUri()
  158. if geometryType:
  159. geom_mapping = {
  160. 'POINT': 'Point',
  161. 'LINESTRING': 'LineString',
  162. 'POLYGON': 'Polygon',
  163. }
  164. geometryType = geom_mapping[geometryType]
  165. uri = "{}|geometrytype={}".format(uri, geometryType)
  166. return QgsVectorLayer(uri, self.name, provider)
  167. def tableFieldsFactory(self, row, table):
  168. return GPKGTableField(row, table)
  169. def tableIndexesFactory(self, row, table):
  170. return GPKGTableIndex(row, table)
  171. def tableTriggersFactory(self, row, table):
  172. return GPKGTableTrigger(row, table)
  173. def tableDataModel(self, parent):
  174. from .data_model import GPKGTableDataModel
  175. return GPKGTableDataModel(self, parent)
  176. class GPKGVectorTable(GPKGTable, VectorTable):
  177. def __init__(self, row, db, schema=None):
  178. GPKGTable.__init__(self, row[:-5], db, schema)
  179. VectorTable.__init__(self, db, schema)
  180. # GPKG does case-insensitive checks for table names, but the
  181. # GPKG provider didn't do the same in QGIS < 1.9, so self.geomTableName
  182. # stores the table name like stored in the geometry_columns table
  183. self.geomTableName, self.geomColumn, self.geomType, self.geomDim, self.srid = row[-5:]
  184. self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=False)
  185. def uri(self):
  186. uri = self.database().uri()
  187. uri.setDataSource('', self.geomTableName, self.geomColumn)
  188. return uri
  189. def hasSpatialIndex(self, geom_column=None):
  190. geom_column = geom_column if geom_column is not None else self.geomColumn
  191. return self.database().connector.hasSpatialIndex((self.schemaName(), self.name), geom_column)
  192. def createSpatialIndex(self, geom_column=None):
  193. self.aboutToChange.emit()
  194. ret = VectorTable.createSpatialIndex(self, geom_column)
  195. if ret is not False:
  196. self.database().refresh()
  197. return ret
  198. def deleteSpatialIndex(self, geom_column=None):
  199. self.aboutToChange.emit()
  200. ret = VectorTable.deleteSpatialIndex(self, geom_column)
  201. if ret is not False:
  202. self.database().refresh()
  203. return ret
  204. def refreshTableEstimatedExtent(self):
  205. return
  206. def refreshTableExtent(self):
  207. prevExtent = self.extent
  208. self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=True)
  209. if self.extent != prevExtent:
  210. self.refresh()
  211. def runAction(self, action):
  212. if GPKGTable.runAction(self, action):
  213. return True
  214. return VectorTable.runAction(self, action)
  215. class GPKGRasterTable(GPKGTable, RasterTable):
  216. def __init__(self, row, db, schema=None):
  217. GPKGTable.__init__(self, row[:-3], db, schema)
  218. RasterTable.__init__(self, db, schema)
  219. self.prefixName, self.geomColumn, self.srid = row[-3:]
  220. self.geomType = 'RASTER'
  221. self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn)
  222. def gpkgGdalUri(self):
  223. gdalUri = 'GPKG:%s:%s' % (self.uri().database(), self.prefixName)
  224. return gdalUri
  225. def mimeUri(self):
  226. # QGIS has no provider to load rasters, let's use GDAL
  227. uri = "raster:gdal:%s:%s" % (self.name, self.uri().database())
  228. return uri
  229. def toMapLayer(self, geometryType=None, crs=None):
  230. from qgis.core import QgsRasterLayer, QgsContrastEnhancement
  231. # QGIS has no provider to load rasters, let's use GDAL
  232. uri = self.gpkgGdalUri()
  233. rl = QgsRasterLayer(uri, self.name)
  234. if rl.isValid():
  235. rl.setContrastEnhancement(QgsContrastEnhancement.StretchToMinimumMaximum)
  236. return rl
  237. class GPKGTableField(TableField):
  238. def __init__(self, row, table):
  239. TableField.__init__(self, table)
  240. self.num, self.name, self.dataType, self.notNull, self.default, self.primaryKey = row
  241. self.hasDefault = self.default
  242. class GPKGTableIndex(TableIndex):
  243. def __init__(self, row, table):
  244. TableIndex.__init__(self, table)
  245. self.num, self.name, self.isUnique, self.columns = row
  246. class GPKGTableTrigger(TableTrigger):
  247. def __init__(self, row, table):
  248. TableTrigger.__init__(self, table)
  249. self.name, self.function = row