Skip to content

Commit 3f2866c

Browse files
committedOct 18, 2016
[FEATURE] [DBManager] Add a GeoPackage dedicated plugin
1 parent 68cb04a commit 3f2866c

File tree

14 files changed

+1258
-3
lines changed

14 files changed

+1258
-3
lines changed
 

‎python/plugins/db_manager/db_model.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def __init__(self, parent=None):
300300
self.importVector.connect(self.vectorImport)
301301

302302
self.hasSpatialiteSupport = "spatialite" in supportedDbTypes()
303+
self.hasGPKGSupport = "gpkg" in supportedDbTypes()
303304

304305
self.rootItem = TreeItem(None, None)
305306
for dbtype in supportedDbTypes():
@@ -401,7 +402,7 @@ def flags(self, index):
401402
flags |= Qt.ItemIsDropEnabled
402403

403404
# SL/Geopackage db files can be dropped everywhere in the tree
404-
if self.hasSpatialiteSupport:
405+
if self.hasSpatialiteSupport or self.hasGPKGSupport:
405406
flags |= Qt.ItemIsDropEnabled
406407

407408
return flags

‎python/plugins/db_manager/db_plugins/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
ADD_SUBDIRECTORY(postgis)
22
ADD_SUBDIRECTORY(spatialite)
3+
ADD_SUBDIRECTORY(gpkg)
34
IF(WITH_ORACLE)
45
ADD_SUBDIRECTORY(oracle)
56
ENDIF(WITH_ORACLE)

‎python/plugins/db_manager/db_plugins/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
class NotSupportedDbType(Exception):
2626

2727
def __init__(self, dbtype):
28-
self.msg = self.tr("%s is not supported yet") % dbtype
28+
from qgis.PyQt.QtWidgets import QApplication
29+
self.msg = QApplication.translate("DBManagerPlugin", "%s is not supported yet" % dbtype)
2930
Exception(self, self.msg)
3031

3132
def __str__(self):
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
FILE(GLOB PY_FILES *.py)
3+
FILE(GLOB ICON_FILES icons/*.png)
4+
5+
PYQT_ADD_RESOURCES(PYRC_FILES resources.qrc)
6+
7+
PLUGIN_INSTALL(db_manager db_plugins/gpkg ${PY_FILES} ${PYRC_FILES})
8+
PLUGIN_INSTALL(db_manager db_plugins/gpkg/icons ${ICON_FILES})
9+

‎python/plugins/db_manager/db_plugins/gpkg/__init__.py

Whitespace-only changes.

‎python/plugins/db_manager/db_plugins/gpkg/connector.py

Lines changed: 796 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
/***************************************************************************
5+
Name : DB Manager
6+
Description : Database manager plugin for QGIS
7+
Date : May 23, 2011
8+
copyright : (C) 2011 by Giuseppe Sucameli
9+
email : brush.tyler@gmail.com
10+
11+
***************************************************************************/
12+
13+
/***************************************************************************
14+
* *
15+
* This program is free software; you can redistribute it and/or modify *
16+
* it under the terms of the GNU General Public License as published by *
17+
* the Free Software Foundation; either version 2 of the License, or *
18+
* (at your option) any later version. *
19+
* *
20+
***************************************************************************/
21+
"""
22+
23+
from ..data_model import TableDataModel, SqlResultModel
24+
25+
26+
class GPKGTableDataModel(TableDataModel):
27+
28+
def __init__(self, table, parent=None):
29+
TableDataModel.__init__(self, table, parent)
30+
31+
#fields_txt = u", ".join(self.fields)
32+
#table_txt = self.db.quoteId((self.table.schemaName(), self.table.name))
33+
34+
# run query and get results
35+
#sql = u"SELECT %s FROM %s" % (fields_txt, table_txt)
36+
#self.resdata = self.db._fetchAll(sql, include_fid_and_geometry = True)
37+
38+
self.resdata = self.db._fetchAllFromLayer(table)
39+
40+
self.fetchedFrom = 0
41+
self.fetchedCount = len(self.resdata)
42+
43+
def _sanitizeTableField(self, field):
44+
return self.db.quoteId(field.name)
45+
46+
def rowCount(self, index=None):
47+
return self.fetchedCount
48+
49+
50+
class GPKGSqlResultModel(SqlResultModel):
51+
pass
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
/***************************************************************************
5+
Name : DB Manager
6+
Description : Database manager plugin for QGIS
7+
Date : May 23, 2011
8+
copyright : (C) 2011 by Giuseppe Sucameli
9+
email : brush.tyler@gmail.com
10+
11+
***************************************************************************/
12+
13+
/***************************************************************************
14+
* *
15+
* This program is free software; you can redistribute it and/or modify *
16+
* it under the terms of the GNU General Public License as published by *
17+
* the Free Software Foundation; either version 2 of the License, or *
18+
* (at your option) any later version. *
19+
* *
20+
***************************************************************************/
21+
"""
22+
23+
from qgis.PyQt.QtWidgets import QApplication
24+
25+
from ..info_model import DatabaseInfo
26+
from ..html_elems import HtmlTable
27+
28+
29+
class GPKGDatabaseInfo(DatabaseInfo):
30+
31+
def __init__(self, db):
32+
self.db = db
33+
34+
def connectionDetails(self):
35+
tbl = [
36+
(QApplication.translate("DBManagerPlugin", "Filename:"), self.db.connector.dbname)
37+
]
38+
return HtmlTable(tbl)
39+
40+
def generalInfo(self):
41+
return None
42+
43+
def spatialInfo(self):
44+
return None
45+
46+
def privilegesDetails(self):
47+
return None
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
/***************************************************************************
5+
Name : DB Manager
6+
Description : Database manager plugin for QGIS
7+
Date : May 23, 2011
8+
copyright : (C) 2011 by Giuseppe Sucameli
9+
email : brush.tyler@gmail.com
10+
11+
***************************************************************************/
12+
13+
/***************************************************************************
14+
* *
15+
* This program is free software; you can redistribute it and/or modify *
16+
* it under the terms of the GNU General Public License as published by *
17+
* the Free Software Foundation; either version 2 of the License, or *
18+
* (at your option) any later version. *
19+
* *
20+
***************************************************************************/
21+
"""
22+
from builtins import str
23+
24+
# this will disable the dbplugin if the connector raise an ImportError
25+
from .connector import GPKGDBConnector
26+
27+
from qgis.PyQt.QtCore import Qt, QSettings, QFileInfo
28+
from qgis.PyQt.QtGui import QIcon
29+
from qgis.PyQt.QtWidgets import QApplication, QAction, QFileDialog
30+
from qgis.core import QgsDataSourceUri
31+
from qgis.gui import QgsMessageBar
32+
33+
from ..plugin import DBPlugin, Database, Table, VectorTable, RasterTable, TableField, TableIndex, TableTrigger, \
34+
InvalidDataException
35+
36+
from . import resources_rc
37+
hasattr(resources_rc, 'foo')
38+
39+
40+
def classFactory():
41+
return GPKGDBPlugin
42+
43+
44+
class GPKGDBPlugin(DBPlugin):
45+
46+
@classmethod
47+
def icon(self):
48+
return QIcon(":/db_manager/gpkg/icon")
49+
50+
@classmethod
51+
def typeName(self):
52+
return 'gpkg'
53+
54+
@classmethod
55+
def typeNameString(self):
56+
return 'GeoPackage'
57+
58+
@classmethod
59+
def providerName(self):
60+
return 'ogr'
61+
62+
@classmethod
63+
def connectionSettingsKey(self):
64+
return '/GPKG/connections'
65+
66+
def databasesFactory(self, connection, uri):
67+
return GPKGDatabase(connection, uri)
68+
69+
def connect(self, parent=None):
70+
conn_name = self.connectionName()
71+
settings = QSettings()
72+
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
73+
74+
if not settings.contains("gpkgpath"): # non-existent entry?
75+
raise InvalidDataException(u'there is no defined database connection "%s".' % conn_name)
76+
77+
database = settings.value("gpkgpath")
78+
79+
uri = QgsDataSourceUri()
80+
uri.setDatabase(database)
81+
return self.connectToUri(uri)
82+
83+
@classmethod
84+
def addConnection(self, conn_name, uri):
85+
settings = QSettings()
86+
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
87+
settings.setValue("gpkgpath", uri.database())
88+
return True
89+
90+
@classmethod
91+
def addConnectionActionSlot(self, item, action, parent, index):
92+
QApplication.restoreOverrideCursor()
93+
try:
94+
filename, selected_filter = QFileDialog.getOpenFileName(parent,
95+
parent.tr("Choose GeoPackage file"), None, "GeoPackage (*.gpkg)")
96+
if not filename:
97+
return
98+
finally:
99+
QApplication.setOverrideCursor(Qt.WaitCursor)
100+
101+
conn_name = QFileInfo(filename).fileName()
102+
uri = QgsDataSourceUri()
103+
uri.setDatabase(filename)
104+
self.addConnection(conn_name, uri)
105+
index.internalPointer().itemChanged()
106+
107+
108+
class GPKGDatabase(Database):
109+
110+
def __init__(self, connection, uri):
111+
Database.__init__(self, connection, uri)
112+
113+
def connectorsFactory(self, uri):
114+
return GPKGDBConnector(uri)
115+
116+
def dataTablesFactory(self, row, db, schema=None):
117+
return GPKGTable(row, db, schema)
118+
119+
def vectorTablesFactory(self, row, db, schema=None):
120+
return GPKGVectorTable(row, db, schema)
121+
122+
def rasterTablesFactory(self, row, db, schema=None):
123+
return GPKGRasterTable(row, db, schema)
124+
125+
def info(self):
126+
from .info_model import GPKGDatabaseInfo
127+
128+
return GPKGDatabaseInfo(self)
129+
130+
def sqlResultModel(self, sql, parent):
131+
from .data_model import GPKGSqlResultModel
132+
133+
return GPKGSqlResultModel(self, sql, parent)
134+
135+
def registerDatabaseActions(self, mainWindow):
136+
action = QAction(self.tr("Run &Vacuum"), self)
137+
mainWindow.registerAction(action, self.tr("&Database"), self.runVacuumActionSlot)
138+
139+
Database.registerDatabaseActions(self, mainWindow)
140+
141+
def runVacuumActionSlot(self, item, action, parent):
142+
QApplication.restoreOverrideCursor()
143+
try:
144+
if not isinstance(item, (DBPlugin, Table)) or item.database() is None:
145+
parent.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."),
146+
QgsMessageBar.INFO, parent.iface.messageTimeout())
147+
return
148+
finally:
149+
QApplication.setOverrideCursor(Qt.WaitCursor)
150+
151+
self.runVacuum()
152+
153+
def runVacuum(self):
154+
self.database().aboutToChange.emit()
155+
self.database().connector.runVacuum()
156+
self.database().refresh()
157+
158+
def runAction(self, action):
159+
action = str(action)
160+
161+
if action.startswith("vacuum/"):
162+
if action == "vacuum/run":
163+
self.runVacuum()
164+
return True
165+
166+
return Database.runAction(self, action)
167+
168+
def uniqueIdFunction(self):
169+
return None
170+
171+
def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
172+
from qgis.core import QgsVectorLayer
173+
174+
vl = QgsVectorLayer(self.uri().database(), layerName, 'ogr')
175+
vl.setSubsetString(sql)
176+
return vl
177+
178+
179+
class GPKGTable(Table):
180+
181+
def __init__(self, row, db, schema=None):
182+
Table.__init__(self, db, None)
183+
self.name, self.isView, self.isSysTable = row
184+
185+
def ogrUri(self):
186+
ogrUri = u"%s|layername=%s" % (self.uri().database(), self.name)
187+
return ogrUri
188+
189+
def mimeUri(self):
190+
191+
# QGIS has no provider to load Geopackage vectors, let's use OGR
192+
return u"vector:ogr:%s:%s" % (self.name, self.ogrUri())
193+
194+
def toMapLayer(self):
195+
from qgis.core import QgsVectorLayer
196+
197+
provider = "ogr"
198+
uri = self.ogrUri()
199+
200+
return QgsVectorLayer(uri, self.name, provider)
201+
202+
def tableFieldsFactory(self, row, table):
203+
return GPKGTableField(row, table)
204+
205+
def tableIndexesFactory(self, row, table):
206+
return GPKGTableIndex(row, table)
207+
208+
def tableTriggersFactory(self, row, table):
209+
return GPKGTableTrigger(row, table)
210+
211+
def tableDataModel(self, parent):
212+
from .data_model import GPKGTableDataModel
213+
214+
return GPKGTableDataModel(self, parent)
215+
216+
217+
class GPKGVectorTable(GPKGTable, VectorTable):
218+
219+
def __init__(self, row, db, schema=None):
220+
GPKGTable.__init__(self, row[:-5], db, schema)
221+
VectorTable.__init__(self, db, schema)
222+
# GPKG does case-insensitive checks for table names, but the
223+
# GPKG provider didn't do the same in Qgis < 1.9, so self.geomTableName
224+
# stores the table name like stored in the geometry_columns table
225+
self.geomTableName, self.geomColumn, self.geomType, self.geomDim, self.srid = row[-5:]
226+
self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=False)
227+
228+
def uri(self):
229+
uri = self.database().uri()
230+
uri.setDataSource('', self.geomTableName, self.geomColumn)
231+
return uri
232+
233+
def hasSpatialIndex(self, geom_column=None):
234+
geom_column = geom_column if geom_column is not None else self.geomColumn
235+
return self.database().connector.hasSpatialIndex((self.schemaName(), self.name), geom_column)
236+
237+
def createSpatialIndex(self, geom_column=None):
238+
self.aboutToChange.emit()
239+
ret = VectorTable.createSpatialIndex(self, geom_column)
240+
if ret is not False:
241+
self.database().refresh()
242+
return ret
243+
244+
def deleteSpatialIndex(self, geom_column=None):
245+
self.aboutToChange.emit()
246+
ret = VectorTable.deleteSpatialIndex(self, geom_column)
247+
if ret is not False:
248+
self.database().refresh()
249+
return ret
250+
251+
def refreshTableEstimatedExtent(self):
252+
return
253+
254+
def refreshTableExtent(self):
255+
prevExtent = self.extent
256+
self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=True)
257+
if self.extent != prevExtent:
258+
self.refresh()
259+
260+
def runAction(self, action):
261+
if GPKGTable.runAction(self, action):
262+
return True
263+
return VectorTable.runAction(self, action)
264+
265+
266+
class GPKGRasterTable(GPKGTable, RasterTable):
267+
268+
def __init__(self, row, db, schema=None):
269+
GPKGTable.__init__(self, row[:-3], db, schema)
270+
RasterTable.__init__(self, db, schema)
271+
self.prefixName, self.geomColumn, self.srid = row[-3:]
272+
self.geomType = 'RASTER'
273+
self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn)
274+
275+
def gpkgGdalUri(self):
276+
gdalUri = u'GPKG:%s:%s' % (self.uri().database(), self.prefixName)
277+
return gdalUri
278+
279+
def mimeUri(self):
280+
# QGIS has no provider to load rasters, let's use GDAL
281+
uri = u"raster:gdal:%s:%s" % (self.name, self.uri().database())
282+
return uri
283+
284+
def toMapLayer(self):
285+
from qgis.core import QgsRasterLayer, QgsContrastEnhancement
286+
287+
# QGIS has no provider to load rasters, let's use GDAL
288+
uri = self.gpkgGdalUri()
289+
rl = QgsRasterLayer(uri, self.name)
290+
if rl.isValid():
291+
rl.setContrastEnhancement(QgsContrastEnhancement.StretchToMinimumMaximum)
292+
return rl
293+
294+
295+
class GPKGTableField(TableField):
296+
297+
def __init__(self, row, table):
298+
TableField.__init__(self, table)
299+
self.num, self.name, self.dataType, self.notNull, self.default, self.primaryKey = row
300+
self.hasDefault = self.default
301+
302+
303+
class GPKGTableIndex(TableIndex):
304+
305+
def __init__(self, row, table):
306+
TableIndex.__init__(self, table)
307+
self.num, self.name, self.isUnique, self.columns = row
308+
309+
310+
class GPKGTableTrigger(TableTrigger):
311+
312+
def __init__(self, row, table):
313+
TableTrigger.__init__(self, table)
314+
self.name, self.function = row
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<RCC>
2+
<qresource prefix="/db_manager/gpkg">
3+
<file alias="icon">icons/gpkg_icon.png</file>
4+
</qresource>
5+
</RCC>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
sql_dictionary.py
6+
---------------------
7+
Date : April 2012
8+
Copyright : (C) 2012 by Giuseppe Sucameli
9+
Email : brush dot tyler at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
21+
def getSqlDictionary(spatial=True):
22+
from ..spatialite.sql_dictionary import getSqlDictionary
23+
return getSqlDictionary(spatial)
24+
25+
26+
def getQueryBuilderDictionary():
27+
from ..spatialite.sql_dictionary import getQueryBuilderDictionary
28+
return getQueryBuilderDictionary()

‎python/plugins/db_manager/db_tree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def contextMenuEvent(self, ev):
137137
menu.addAction(self.tr("Re-connect"), self.reconnect)
138138
menu.addAction(self.tr("Remove"), self.delete)
139139

140-
elif not index.parent().isValid() and item.typeName() == "spatialite":
140+
elif not index.parent().isValid() and item.typeName() in ("spatialite", "gpkg"):
141141
menu.addAction(self.tr("New Connection..."), self.newConnection)
142142

143143
if not menu.isEmpty():

‎python/plugins/db_manager/dlg_sql_layer_window.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def __init__(self, iface, layer, parent=None):
7171
dbplugin = createDbPlugin('oracle', 'oracle')
7272
elif layer.dataProvider().name() == 'virtual':
7373
dbplugin = createDbPlugin('vlayers', 'virtual')
74+
elif layer.dataProvider().name() == 'ogr':
75+
dbplugin = createDbPlugin('gpkg', 'gpkg')
7476
if dbplugin:
7577
dbplugin.connectToUri(uri)
7678
db = dbplugin.db

0 commit comments

Comments
 (0)
Please sign in to comment.