Skip to content

Commit e4996d7

Browse files
committed
[processing] allow output directly on Spatialite tables
like 11b5092 but for Spatialite
1 parent 9f3bd1d commit e4996d7

File tree

3 files changed

+202
-1
lines changed

3 files changed

+202
-1
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
spatialite_utils.py
6+
---------------------
7+
Date : November 2015
8+
Copyright : (C) 2015 by René-Luc Dhont
9+
Email : volayaf 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+
__author__ = 'René-Luc Dhont'
21+
__date__ = 'November 2015'
22+
__copyright__ = '(C) 2015, René-Luc Dhont'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
from pyspatialite import dbapi2 as sqlite
29+
30+
class DbError(Exception):
31+
32+
def __init__(self, message, query=None):
33+
# Save error. funny that the variables are in utf-8
34+
self.message = unicode(message, 'utf-8')
35+
self.query = (unicode(query, 'utf-8') if query is not None else None)
36+
37+
def __str__(self):
38+
return 'MESSAGE: %s\nQUERY: %s' % (self.message, self.query)
39+
40+
class GeoDB:
41+
42+
def __init__(self, uri=None):
43+
self.uri = uri
44+
self.dbname = uri.database()
45+
46+
try:
47+
self.con = sqlite.connect(self.con_info())
48+
49+
except (sqlite.InterfaceError, sqlite.OperationalError) as e:
50+
raise DbError(e.message)
51+
52+
self.has_spatialite = self.check_spatialite()
53+
if not self.has_spatialite:
54+
self.has_spatialite = self.init_spatialite()
55+
56+
def con_info(self):
57+
return unicode(self.dbname)
58+
59+
def init_spatialite(self):
60+
# Get spatialite version
61+
c = self.con.cursor()
62+
try:
63+
self._exec_sql(c, u'SELECT spatialite_version()')
64+
rep = c.fetchall()
65+
v = [int(a) for a in rep[0][0].split('.')]
66+
vv = v[0] * 100000 + v[1] * 1000 + v[2] * 10
67+
68+
# Add spatialite support
69+
if vv >= 401000:
70+
# 4.1 and above
71+
sql = "SELECT initspatialmetadata(1)"
72+
else:
73+
# Under 4.1
74+
sql = "SELECT initspatialmetadata()"
75+
self._exec_sql_and_commit(sql)
76+
except:
77+
return False
78+
finally:
79+
self.con.close()
80+
81+
try:
82+
self.con = sqlite.connect(self.con_info())
83+
84+
except (sqlite.InterfaceError, sqlite.OperationalError) as e:
85+
raise DbError(e.message)
86+
87+
return self.check_spatialite()
88+
89+
def check_spatialite(self):
90+
try:
91+
c = self.con.cursor()
92+
self._exec_sql(c, u"SELECT CheckSpatialMetaData()")
93+
v = c.fetchone()[0]
94+
self.has_geometry_columns = v == 1 or v == 3
95+
self.has_spatialite4 = v == 3
96+
except Exception as e:
97+
self.has_geometry_columns = False
98+
self.has_spatialite4 = False
99+
100+
self.has_geometry_columns_access = self.has_geometry_columns
101+
return self.has_geometry_columns
102+
103+
def _exec_sql(self, cursor, sql):
104+
try:
105+
cursor.execute(sql)
106+
except (sqlite.Error, sqlite.ProgrammingError, sqlite.Warning, sqlite.InterfaceError, sqlite.OperationalError) as e:
107+
raise DbError(e.message, sql)
108+
109+
def _exec_sql_and_commit(self, sql):
110+
"""Tries to execute and commit some action, on error it rolls
111+
back the change.
112+
"""
113+
114+
try:
115+
c = self.con.cursor()
116+
self._exec_sql(c, sql)
117+
self.con.commit()
118+
except DbError as e:
119+
self.con.rollback()
120+
raise

python/plugins/processing/gui/OutputSelectionPanel.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ def selectOutput(self):
8383
self.tr('Save to memory layer'), self.btnSelect)
8484
actionSaveToMemory.triggered.connect(self.saveToMemory)
8585
popupMenu.addAction(actionSaveToMemory)
86+
actionSaveToSpatialite = QAction(
87+
self.tr('Save to Spatialite table...'), self.btnSelect)
88+
actionSaveToSpatialite.triggered.connect(self.saveToSpatialite)
89+
popupMenu.addAction(actionSaveToSpatialite)
8690
actionSaveToPostGIS = QAction(
8791
self.tr('Save to PostGIS table...'), self.btnSelect)
8892
actionSaveToPostGIS.triggered.connect(self.saveToPostGIS)
@@ -118,6 +122,42 @@ def saveToPostGIS(self):
118122
QgsCredentials.instance().put(connInfo, user, passwd)
119123
self.leText.setText("postgis:" + uri.uri())
120124

125+
def saveToSpatialite(self):
126+
fileFilter = self.output.tr('Spatialite files(*.sqlite)', 'OutputFile')
127+
128+
settings = QSettings()
129+
if settings.contains('/Processing/LastOutputPath'):
130+
path = settings.value('/Processing/LastOutputPath')
131+
else:
132+
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
133+
134+
encoding = settings.value('/Processing/encoding', 'System')
135+
fileDialog = QgsEncodingFileDialog(
136+
self, self.tr('Save Spatialite'), path, fileFilter, encoding)
137+
fileDialog.setFileMode(QFileDialog.AnyFile)
138+
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
139+
fileDialog.setConfirmOverwrite(False)
140+
141+
if fileDialog.exec_() == QDialog.Accepted:
142+
files = fileDialog.selectedFiles()
143+
encoding = unicode(fileDialog.encoding())
144+
self.output.encoding = encoding
145+
fileName = unicode(files[0])
146+
selectedFileFilter = unicode(fileDialog.selectedNameFilter())
147+
if not fileName.lower().endswith(
148+
tuple(re.findall("\*(\.[a-z]{1,10})", fileFilter))):
149+
ext = re.search("\*(\.[a-z]{1,10})", selectedFileFilter)
150+
if ext:
151+
fileName += ext.group(1)
152+
settings.setValue('/Processing/LastOutputPath',
153+
os.path.dirname(fileName))
154+
settings.setValue('/Processing/encoding', encoding)
155+
156+
uri = QgsDataSourceURI()
157+
uri.setDatabase(fileName)
158+
uri.setDataSource('', self.output.name.lower(), 'the_geom')
159+
self.leText.setText("spatialite:" + uri.uri())
160+
121161
def saveToMemory(self):
122162
self.leText.setText('memory:')
123163

@@ -167,6 +207,8 @@ def getValue(self):
167207
value = fileName
168208
elif fileName.startswith('postgis:'):
169209
value = fileName
210+
elif fileName.startswith('spatialite:'):
211+
value = fileName
170212
elif not os.path.isabs(fileName):
171213
value = ProcessingConfig.getSetting(
172214
ProcessingConfig.OUTPUT_FOLDER) + os.sep + fileName

python/plugins/processing/tools/vector.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
***************************************************************************
1818
"""
1919
from processing.algs.qgis import postgis_utils
20+
from processing.algs.qgis import spatialite_utils
2021

2122
__author__ = 'Victor Olaya'
2223
__date__ = 'February 2013'
@@ -69,6 +70,13 @@
6970
QVariant.Bool: "BOOLEAN"
7071
}
7172

73+
TYPE_MAP_SPATIALITE_LAYER = {
74+
QVariant.String: "VARCHAR",
75+
QVariant.Double: "REAL",
76+
QVariant.Int: "INTEGER",
77+
QVariant.Bool: "INTEGER"
78+
}
79+
7280

7381
def features(layer):
7482
"""This returns an iterator over features in a vector layer,
@@ -423,6 +431,7 @@ class VectorWriter:
423431

424432
MEMORY_LAYER_PREFIX = 'memory:'
425433
POSTGIS_LAYER_PREFIX = 'postgis:'
434+
SPATIALITE_LAYER_PREFIX = 'spatialite:'
426435

427436
def __init__(self, destination, encoding, fields, geometryType,
428437
crs, options=None):
@@ -485,7 +494,37 @@ def _runSQL(sql):
485494
table=uri.table().lower(), schema=uri.schema(), srid=crs.authid().split(":")[-1],
486495
typmod=GEOM_TYPE_MAP[geometryType].upper()))
487496

488-
self.layer = QgsVectorLayer(uri.uri(), uri.table(), "postgres")
497+
self.layer = QgsVectorLayer(uri.uri(), uri.table(), "spatialite")
498+
self.writer = self.layer.dataProvider()
499+
elif self.destination.startswith(self.SPATIALITE_LAYER_PREFIX):
500+
self.isNotFileBased = True
501+
uri = QgsDataSourceURI(self.destination[len(self.SPATIALITE_LAYER_PREFIX):])
502+
print uri.uri()
503+
try:
504+
db = spatialite_utils.GeoDB(uri=uri)
505+
except spatialite_utils.DbError as e:
506+
raise GeoAlgorithmExecutionException(
507+
"Couldn't connect to database:\n%s" % e.message)
508+
509+
def _runSQL(sql):
510+
try:
511+
db._exec_sql_and_commit(unicode(sql))
512+
except spatialite_utils.DbError as e:
513+
raise GeoAlgorithmExecutionException(
514+
'Error creating output Spatialite table:\n%s' % e.message)
515+
516+
fields = [_toQgsField(f) for f in fields]
517+
fieldsdesc = ",".join('%s %s' % (f.name(),
518+
TYPE_MAP_SPATIALITE_LAYER.get(f.type(), "VARCHAR"))
519+
for f in fields)
520+
521+
_runSQL("DROP TABLE IF EXISTS %s" % uri.table().lower())
522+
_runSQL("CREATE TABLE %s (%s)" % (uri.table().lower(), fieldsdesc))
523+
_runSQL("SELECT AddGeometryColumn('{table}', 'the_geom', {srid}, '{typmod}', 2)".format(
524+
table=uri.table().lower(), srid=crs.authid().split(":")[-1],
525+
typmod=GEOM_TYPE_MAP[geometryType].upper()))
526+
527+
self.layer = QgsVectorLayer(uri.uri(), uri.table(), "spatialite")
489528
self.writer = self.layer.dataProvider()
490529
else:
491530
formats = QgsVectorFileWriter.supportedFiltersAndFormats()

0 commit comments

Comments
 (0)