mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-16 00:05:45 -04:00
[processing] allow output directly on PostGIS tables
This commit is contained in:
parent
22ace3df2e
commit
11b5092140
@ -306,5 +306,5 @@ class OutputVector(Output):
|
||||
|
||||
w = VectorWriter(self.value, self.encoding, fields, geomType,
|
||||
crs, options)
|
||||
self.memoryLayer = w.memLayer
|
||||
self.layer = w.layer
|
||||
return w
|
||||
|
@ -32,10 +32,12 @@ from PyQt4 import uic
|
||||
from PyQt4.QtCore import QCoreApplication, QSettings
|
||||
from PyQt4.QtGui import QDialog, QMenu, QAction, QCursor, QFileDialog
|
||||
from qgis.gui import QgsEncodingFileDialog
|
||||
from qgis.core import *
|
||||
|
||||
from processing.core.ProcessingConfig import ProcessingConfig
|
||||
from processing.core.outputs import OutputVector
|
||||
from processing.core.outputs import OutputDirectory
|
||||
from processing.gui.PostgisTableSelector import PostgisTableSelector
|
||||
|
||||
pluginPath = os.path.split(os.path.dirname(__file__))[0]
|
||||
WIDGET, BASE = uic.loadUiType(
|
||||
@ -81,12 +83,41 @@ class OutputSelectionPanel(BASE, WIDGET):
|
||||
self.tr('Save to memory layer'), self.btnSelect)
|
||||
actionSaveToMemory.triggered.connect(self.saveToMemory)
|
||||
popupMenu.addAction(actionSaveToMemory)
|
||||
actionSaveToPostGIS = QAction(
|
||||
self.tr('Save to PostGIS table...'), self.btnSelect)
|
||||
actionSaveToPostGIS.triggered.connect(self.saveToPostGIS)
|
||||
settings = QSettings()
|
||||
settings.beginGroup('/PostgreSQL/connections/')
|
||||
names = settings.childGroups()
|
||||
settings.endGroup()
|
||||
actionSaveToPostGIS.setEnabled(bool(names))
|
||||
popupMenu.addAction(actionSaveToPostGIS)
|
||||
|
||||
popupMenu.exec_(QCursor.pos())
|
||||
|
||||
def saveToTemporaryFile(self):
|
||||
self.leText.setText('')
|
||||
|
||||
def saveToPostGIS(self):
|
||||
dlg = PostgisTableSelector(self, self.output.name.lower())
|
||||
dlg.exec_()
|
||||
if dlg.connection:
|
||||
settings = QSettings()
|
||||
mySettings = '/PostgreSQL/connections/' + dlg.connection
|
||||
dbname = settings.value(mySettings + '/database')
|
||||
user = settings.value(mySettings + '/username')
|
||||
host = settings.value(mySettings + '/host')
|
||||
port = settings.value(mySettings + '/port')
|
||||
password = settings.value(mySettings + '/password')
|
||||
uri = QgsDataSourceURI()
|
||||
uri.setConnection(host, str(port), dbname, user, password)
|
||||
uri.setDataSource(dlg.schema, dlg.table, "the_geom")
|
||||
connInfo = uri.connectionInfo()
|
||||
(success, user, passwd ) = QgsCredentials.instance().get(connInfo, None, None)
|
||||
if success:
|
||||
QgsCredentials.instance().put(connInfo, user, passwd)
|
||||
self.leText.setText("postgis:" + uri.uri())
|
||||
|
||||
def saveToMemory(self):
|
||||
self.leText.setText('memory:')
|
||||
|
||||
@ -124,10 +155,8 @@ class OutputSelectionPanel(BASE, WIDGET):
|
||||
|
||||
def selectDirectory(self):
|
||||
lastDir = ''
|
||||
|
||||
dirName = QFileDialog.getExistingDirectory(self,
|
||||
self.tr('Select directory'), lastDir, QFileDialog.ShowDirsOnly)
|
||||
|
||||
dirName = QFileDialog.getExistingDirectory(self,self.tr('Select directory'),
|
||||
lastDir, QFileDialog.ShowDirsOnly)
|
||||
self.leText.setText(dirName)
|
||||
|
||||
def getValue(self):
|
||||
@ -136,10 +165,14 @@ class OutputSelectionPanel(BASE, WIDGET):
|
||||
value = None
|
||||
elif fileName.startswith('memory:'):
|
||||
value = fileName
|
||||
elif fileName.startswith('postgis:'):
|
||||
value = fileName
|
||||
elif not os.path.isabs(fileName):
|
||||
value = ProcessingConfig.getSetting(
|
||||
ProcessingConfig.OUTPUT_FOLDER) + os.sep + fileName
|
||||
else:
|
||||
value = fileName
|
||||
|
||||
|
||||
|
||||
return value
|
||||
|
92
python/plugins/processing/gui/PostgisTableSelector.py
Normal file
92
python/plugins/processing/gui/PostgisTableSelector.py
Normal file
@ -0,0 +1,92 @@
|
||||
import os
|
||||
from PyQt4 import uic, QtCore, QtGui
|
||||
from processing.algs.qgis.postgis_utils import GeoDB
|
||||
from qgis.core import *
|
||||
from PyQt4.QtGui import QMessageBox
|
||||
|
||||
pluginPath = os.path.split(os.path.dirname(__file__))[0]
|
||||
WIDGET, BASE = uic.loadUiType(
|
||||
os.path.join(pluginPath, 'ui', 'DlgPostgisTableSelector.ui'))
|
||||
|
||||
|
||||
class PostgisTableSelector(BASE, WIDGET):
|
||||
|
||||
def __init__(self, parent, tablename):
|
||||
super(PostgisTableSelector, self).__init__(parent)
|
||||
self.connection = None
|
||||
self.table = None
|
||||
self.schema = None
|
||||
self.setupUi(self)
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup('/PostgreSQL/connections/')
|
||||
names = settings.childGroups()
|
||||
settings.endGroup()
|
||||
for n in names:
|
||||
item = ConnectionItem(n)
|
||||
self.treeConnections.addTopLevelItem(item)
|
||||
|
||||
def itemExpanded(item):
|
||||
try:
|
||||
item.populateSchemas()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.treeConnections.itemExpanded.connect(itemExpanded)
|
||||
|
||||
self.textTableName.setText(tablename)
|
||||
|
||||
self.buttonBox.accepted.connect(self.okPressed)
|
||||
self.buttonBox.rejected.connect(self.cancelPressed)
|
||||
|
||||
def cancelPressed(self):
|
||||
self.close()
|
||||
|
||||
def okPressed(self):
|
||||
if self.textTableName.text().strip() == "":
|
||||
self.textTableName.setStyleSheet("QLineEdit{background: yellow}")
|
||||
return
|
||||
item = self.treeConnections.currentItem()
|
||||
if isinstance(item, ConnectionItem):
|
||||
QMessageBox.warning(self, "Wrong selection", "Select a schema item in the tree")
|
||||
return
|
||||
self.schema = item.text(0)
|
||||
self.table = self.textTableName.text().strip()
|
||||
self.connection = item.parent().text(0)
|
||||
self.close()
|
||||
|
||||
class ConnectionItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
connIcon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/postgis.png')
|
||||
schemaIcon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/namespace.png')
|
||||
|
||||
def __init__(self, connection):
|
||||
QtGui.QTreeWidgetItem.__init__(self)
|
||||
self.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ShowIndicator)
|
||||
self.connection = connection
|
||||
self.setText(0, connection)
|
||||
self.setIcon(0, self.connIcon)
|
||||
|
||||
def populateSchemas(self):
|
||||
if self.childCount() != 0:
|
||||
return
|
||||
settings = QtCore.QSettings()
|
||||
connSettings = '/PostgreSQL/connections/' + self.connection
|
||||
database = settings.value(connSettings + '/database')
|
||||
user = settings.value(connSettings + '/username')
|
||||
host = settings.value(connSettings + '/host')
|
||||
port = settings.value(connSettings + '/port')
|
||||
passwd = settings.value(connSettings + '/password')
|
||||
uri = QgsDataSourceURI()
|
||||
uri.setConnection(host, str(port), database, user, passwd)
|
||||
connInfo = uri.connectionInfo()
|
||||
(success, user, passwd ) = QgsCredentials.instance().get(connInfo, None, None)
|
||||
if success:
|
||||
QgsCredentials.instance().put(connInfo, user, passwd)
|
||||
geodb = GeoDB(host, int(port), database, user, passwd)
|
||||
schemas = geodb.list_schemas()
|
||||
for oid, name, owner, perms in schemas:
|
||||
item = QtGui.QTreeWidgetItem()
|
||||
item.setText(0, name)
|
||||
item.setIcon(0, self.schemaIcon)
|
||||
self.addChild(item)
|
||||
|
@ -59,9 +59,8 @@ def handleAlgorithmResults(alg, progress=None, showResults=True):
|
||||
continue
|
||||
if isinstance(out, (OutputRaster, OutputVector, OutputTable)):
|
||||
try:
|
||||
if out.value.startswith('memory:'):
|
||||
layer = out.memoryLayer
|
||||
QgsMapLayerRegistry.instance().addMapLayers([layer])
|
||||
if out.layer is not None:
|
||||
QgsMapLayerRegistry.instance().addMapLayers([out.layer])
|
||||
else:
|
||||
if ProcessingConfig.getSetting(
|
||||
ProcessingConfig.USE_FILENAME_AS_LAYER_NAME):
|
||||
|
BIN
python/plugins/processing/images/namespace.png
Normal file
BIN
python/plugins/processing/images/namespace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 436 B |
BIN
python/plugins/processing/images/postgis.png
Normal file
BIN
python/plugins/processing/images/postgis.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 B |
@ -16,6 +16,7 @@
|
||||
* *
|
||||
***************************************************************************
|
||||
"""
|
||||
from processing.algs.qgis import postgis_utils
|
||||
|
||||
__author__ = 'Victor Olaya'
|
||||
__date__ = 'February 2013'
|
||||
@ -33,6 +34,9 @@ import cStringIO
|
||||
from PyQt4.QtCore import QVariant, QSettings
|
||||
from qgis.core import QGis, QgsFields, QgsField, QgsGeometry, QgsRectangle, QgsSpatialIndex, QgsMapLayerRegistry, QgsMapLayer, QgsVectorLayer, QgsVectorFileWriter, QgsDistanceArea
|
||||
from processing.core.ProcessingConfig import ProcessingConfig
|
||||
from PyQt4 import QtSql
|
||||
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
||||
from qgis.core import *
|
||||
|
||||
|
||||
GEOM_TYPE_MAP = {
|
||||
@ -58,6 +62,13 @@ TYPE_MAP_MEMORY_LAYER = {
|
||||
QVariant.Int: "integer"
|
||||
}
|
||||
|
||||
TYPE_MAP_POSTGIS_LAYER = {
|
||||
QVariant.String: "VARCHAR",
|
||||
QVariant.Double: "REAL",
|
||||
QVariant.Int: "INTEGER",
|
||||
QVariant.Bool: "BOOLEAN"
|
||||
}
|
||||
|
||||
|
||||
def features(layer):
|
||||
"""This returns an iterator over features in a vector layer,
|
||||
@ -411,20 +422,21 @@ def bufferedBoundingBox(bbox, buffer_size):
|
||||
class VectorWriter:
|
||||
|
||||
MEMORY_LAYER_PREFIX = 'memory:'
|
||||
POSTGIS_LAYER_PREFIX = 'postgis:'
|
||||
|
||||
def __init__(self, fileName, encoding, fields, geometryType,
|
||||
def __init__(self, destination, encoding, fields, geometryType,
|
||||
crs, options=None):
|
||||
self.fileName = fileName
|
||||
self.isMemory = False
|
||||
self.memLayer = None
|
||||
self.destination = destination
|
||||
self.isNotFileBased = False
|
||||
self.layer = None
|
||||
self.writer = None
|
||||
|
||||
if encoding is None:
|
||||
settings = QSettings()
|
||||
encoding = settings.value('/Processing/encoding', 'System', type=str)
|
||||
|
||||
if self.fileName.startswith(self.MEMORY_LAYER_PREFIX):
|
||||
self.isMemory = True
|
||||
if self.destination.startswith(self.MEMORY_LAYER_PREFIX):
|
||||
self.isNotFileBased = True
|
||||
|
||||
uri = GEOM_TYPE_MAP[geometryType] + "?uuid=" + unicode(uuid.uuid4())
|
||||
if crs.isValid():
|
||||
@ -437,8 +449,44 @@ class VectorWriter:
|
||||
if fieldsdesc:
|
||||
uri += '&' + '&'.join(fieldsdesc)
|
||||
|
||||
self.memLayer = QgsVectorLayer(uri, self.fileName, 'memory')
|
||||
self.writer = self.memLayer.dataProvider()
|
||||
self.layer = QgsVectorLayer(uri, self.destination, 'memory')
|
||||
self.writer = self.layer.dataProvider()
|
||||
elif self.destination.startswith(self.POSTGIS_LAYER_PREFIX):
|
||||
self.isNotFileBased = True
|
||||
uri = QgsDataSourceURI(self.destination[len(self.POSTGIS_LAYER_PREFIX):])
|
||||
connInfo = uri.connectionInfo()
|
||||
(success, user, passwd ) = QgsCredentials.instance().get(connInfo, None, None)
|
||||
if success:
|
||||
QgsCredentials.instance().put(connInfo, user, passwd)
|
||||
else:
|
||||
raise GeoAlgorithmExecutionException("Couldn't connect to database")
|
||||
print uri.uri()
|
||||
try:
|
||||
db = postgis_utils.GeoDB(host=uri.host(), port=int(uri.port()),
|
||||
dbname=uri.database(), user=user, passwd=passwd)
|
||||
except postgis_utils.DbError as e:
|
||||
raise GeoAlgorithmExecutionException(
|
||||
"Couldn't connect to database:\n%s" % e.message)
|
||||
|
||||
def _runSQL(sql):
|
||||
try:
|
||||
db._exec_sql_and_commit(unicode(sql))
|
||||
except postgis_utils.DbError as e:
|
||||
raise GeoAlgorithmExecutionException(
|
||||
'Error creating output PostGIS table:\n%s' % e.message)
|
||||
|
||||
fields = [_toQgsField(f) for f in fields]
|
||||
fieldsdesc = ",".join('%s %s' % (f.name(),
|
||||
TYPE_MAP_POSTGIS_LAYER.get(f.type(), "VARCHAR"))
|
||||
for f in fields)
|
||||
|
||||
_runSQL("CREATE TABLE %s.%s (%s)" % (uri.schema(), uri.table().lower(), fieldsdesc))
|
||||
_runSQL("SELECT AddGeometryColumn('{schema}', '{table}', 'the_geom', {srid}, '{typmod}', 2)".format(
|
||||
table=uri.table().lower(), schema=uri.schema(), srid=crs.authid().split(":")[-1],
|
||||
typmod=GEOM_TYPE_MAP[geometryType].upper()))
|
||||
|
||||
self.layer = QgsVectorLayer(uri.uri(), uri.table(), "postgres")
|
||||
self.writer = self.layer.dataProvider()
|
||||
else:
|
||||
formats = QgsVectorFileWriter.supportedFiltersAndFormats()
|
||||
OGRCodes = {}
|
||||
@ -448,21 +496,20 @@ class VectorWriter:
|
||||
extension = extension[:extension.find(' ')]
|
||||
OGRCodes[extension] = value
|
||||
|
||||
extension = self.fileName[self.fileName.rfind('.') + 1:]
|
||||
extension = self.destination[self.destination.rfind('.') + 1:]
|
||||
if extension not in OGRCodes:
|
||||
extension = 'shp'
|
||||
self.filename = self.filename + 'shp'
|
||||
self.destination = self.destination + '.shp'
|
||||
|
||||
qgsfields = QgsFields()
|
||||
for field in fields:
|
||||
qgsfields.append(_toQgsField(field))
|
||||
|
||||
self.writer = QgsVectorFileWriter(
|
||||
self.fileName, encoding,
|
||||
self.writer = QgsVectorFileWriter(self.destination, encoding,
|
||||
qgsfields, geometryType, crs, OGRCodes[extension])
|
||||
|
||||
def addFeature(self, feature):
|
||||
if self.isMemory:
|
||||
if self.isNotFileBased:
|
||||
self.writer.addFeatures([feature])
|
||||
else:
|
||||
self.writer.addFeature(feature)
|
||||
|
64
python/plugins/processing/ui/DlgPostgisTableSelector.ui
Normal file
64
python/plugins/processing/ui/DlgPostgisTableSelector.ui
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>464</width>
|
||||
<height>395</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string> output table</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Select connection and schema</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="treeConnections">
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Table name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textTableName"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user