mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-22 00:04:29 -04:00
This adds a lot of flexibility to algorithms, as it makes output sinks truely optional. For instance, the various "Extract by..." algorithms could add a new optional sink for features which 'fail' the extraction criteria. This effectively allows these algorithms to become feature 'routers', directing features onto other parts of a model depending on whether they pass or fail the test. But in this situation we don't always care about these failing features, and we don't want to force them to always be fetched from the provider. By making the outputs truely optional, the algorithm can tweak its logic to either fetch all features and send them to the correct output, or only fetch matching features from the provider in the first place (a big speed boost).
274 lines
12 KiB
Python
274 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
***************************************************************************
|
|
OutputSelectionPanel.py
|
|
---------------------
|
|
Date : August 2012
|
|
Copyright : (C) 2012 by Victor Olaya
|
|
Email : volayaf at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************
|
|
"""
|
|
from builtins import str
|
|
|
|
__author__ = 'Victor Olaya'
|
|
__date__ = 'August 2012'
|
|
__copyright__ = '(C) 2012, Victor Olaya'
|
|
|
|
# This will get replaced with a git SHA1 when you do a git archive
|
|
|
|
__revision__ = '$Format:%H$'
|
|
|
|
import re
|
|
import os
|
|
|
|
from qgis.PyQt import uic
|
|
from qgis.PyQt.QtCore import QCoreApplication
|
|
from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QFileDialog
|
|
from qgis.PyQt.QtGui import QCursor
|
|
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
|
|
from qgis.core import (QgsDataSourceUri,
|
|
QgsCredentials,
|
|
QgsExpression,
|
|
QgsSettings,
|
|
QgsProcessingParameterFeatureSink,
|
|
QgsProcessingOutputLayerDefinition,
|
|
QgsProcessingParameterDefinition)
|
|
from processing.core.ProcessingConfig import ProcessingConfig
|
|
from processing.core.outputs import OutputVector
|
|
from processing.core.outputs import OutputDirectory
|
|
from processing.tools.dataobjects import createContext
|
|
from processing.gui.PostgisTableSelector import PostgisTableSelector
|
|
from processing.gui.ParameterGuiUtils import getFileFilter
|
|
|
|
pluginPath = os.path.split(os.path.dirname(__file__))[0]
|
|
WIDGET, BASE = uic.loadUiType(
|
|
os.path.join(pluginPath, 'ui', 'widgetBaseSelector.ui'))
|
|
|
|
|
|
class DestinationSelectionPanel(BASE, WIDGET):
|
|
|
|
SAVE_TO_TEMP_FILE = QCoreApplication.translate(
|
|
'DestinationSelectionPanel', '[Save to temporary file]')
|
|
SAVE_TO_TEMP_LAYER = QCoreApplication.translate(
|
|
'DestinationSelectionPanel', '[Create temporary layer]')
|
|
SKIP_OUTPUT = QCoreApplication.translate(
|
|
'DestinationSelectionPanel', '[Skip output]')
|
|
|
|
def __init__(self, parameter, alg):
|
|
super(DestinationSelectionPanel, self).__init__(None)
|
|
self.setupUi(self)
|
|
|
|
self.parameter = parameter
|
|
self.alg = alg
|
|
settings = QgsSettings()
|
|
self.encoding = settings.value('/Processing/encoding', 'System')
|
|
self.use_temporary = True
|
|
|
|
if hasattr(self.leText, 'setPlaceholderText'):
|
|
if parameter.flags() & QgsProcessingParameterDefinition.FlagOptional and parameter.defaultValue() is None:
|
|
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
|
|
self.use_temporary = False
|
|
elif isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
|
|
and alg.provider().supportsNonFileBasedOutput():
|
|
# use memory layers for temporary files if supported
|
|
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
|
|
else:
|
|
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
|
|
|
|
self.btnSelect.clicked.connect(self.selectOutput)
|
|
self.leText.textEdited.connect(self.textChanged)
|
|
|
|
def textChanged(self):
|
|
self.use_temporary = False
|
|
|
|
def skipOutput(self):
|
|
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
|
|
self.use_temporary = False
|
|
|
|
def selectOutput(self):
|
|
if isinstance(self.parameter, OutputDirectory):
|
|
self.selectDirectory()
|
|
else:
|
|
popupMenu = QMenu()
|
|
|
|
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
|
|
and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
|
|
actionSkipOutput = QAction(
|
|
self.tr('Skip output'), self.btnSelect)
|
|
actionSkipOutput.triggered.connect(self.skipOutput)
|
|
popupMenu.addAction(actionSkipOutput)
|
|
|
|
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
|
|
and self.alg.provider().supportsNonFileBasedOutput():
|
|
# use memory layers for temporary layers if supported
|
|
actionSaveToTemp = QAction(
|
|
self.tr('Create temporary layer'), self.btnSelect)
|
|
else:
|
|
actionSaveToTemp = QAction(
|
|
self.tr('Save to a temporary file'), self.btnSelect)
|
|
actionSaveToTemp.triggered.connect(self.saveToTemporary)
|
|
popupMenu.addAction(actionSaveToTemp)
|
|
|
|
actionSaveToFile = QAction(
|
|
self.tr('Save to file...'), self.btnSelect)
|
|
actionSaveToFile.triggered.connect(self.selectFile)
|
|
popupMenu.addAction(actionSaveToFile)
|
|
|
|
actionShowExpressionsBuilder = QAction(
|
|
self.tr('Use expression...'), self.btnSelect)
|
|
actionShowExpressionsBuilder.triggered.connect(self.showExpressionsBuilder)
|
|
popupMenu.addAction(actionShowExpressionsBuilder)
|
|
|
|
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
|
|
and self.alg.provider().supportsNonFileBasedOutput():
|
|
actionSaveToSpatialite = QAction(
|
|
self.tr('Save to Spatialite table...'), self.btnSelect)
|
|
actionSaveToSpatialite.triggered.connect(self.saveToSpatialite)
|
|
popupMenu.addAction(actionSaveToSpatialite)
|
|
actionSaveToPostGIS = QAction(
|
|
self.tr('Save to PostGIS table...'), self.btnSelect)
|
|
actionSaveToPostGIS.triggered.connect(self.saveToPostGIS)
|
|
settings = QgsSettings()
|
|
settings.beginGroup('/PostgreSQL/connections/')
|
|
names = settings.childGroups()
|
|
settings.endGroup()
|
|
actionSaveToPostGIS.setEnabled(bool(names))
|
|
popupMenu.addAction(actionSaveToPostGIS)
|
|
|
|
popupMenu.exec_(QCursor.pos())
|
|
|
|
def showExpressionsBuilder(self):
|
|
context = self.alg.createExpressionContext({}, createContext())
|
|
dlg = QgsExpressionBuilderDialog(None, self.leText.text(), self, 'generic',
|
|
context)
|
|
dlg.setWindowTitle(self.tr('Expression based output'))
|
|
if dlg.exec_() == QDialog.Accepted:
|
|
expression = QgsExpression(dlg.expressionText())
|
|
self.leText.setText(expression.evaluate(context))
|
|
|
|
def saveToTemporary(self):
|
|
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.alg.provider().supportsNonFileBasedOutput():
|
|
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
|
|
else:
|
|
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
|
|
self.leText.setText('')
|
|
self.use_temporary = True
|
|
|
|
def saveToPostGIS(self):
|
|
dlg = PostgisTableSelector(self, self.parameter.name().lower())
|
|
dlg.exec_()
|
|
if dlg.connection:
|
|
self.use_temporary = False
|
|
settings = QgsSettings()
|
|
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" if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
|
|
|
|
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 saveToSpatialite(self):
|
|
fileFilter = self.tr('SpatiaLite files (*.sqlite)', 'OutputFile')
|
|
|
|
settings = QgsSettings()
|
|
if settings.contains('/Processing/LastOutputPath'):
|
|
path = settings.value('/Processing/LastOutputPath')
|
|
else:
|
|
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
|
|
|
|
fileDialog = QgsEncodingFileDialog(
|
|
self, self.tr('Save Spatialite'), path, fileFilter, self.encoding)
|
|
fileDialog.setFileMode(QFileDialog.AnyFile)
|
|
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
|
|
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, True)
|
|
|
|
if fileDialog.exec_() == QDialog.Accepted:
|
|
self.use_temporary = False
|
|
files = fileDialog.selectedFiles()
|
|
self.encoding = str(fileDialog.encoding())
|
|
fileName = str(files[0])
|
|
selectedFileFilter = str(fileDialog.selectedNameFilter())
|
|
if not fileName.lower().endswith(
|
|
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
|
|
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
|
|
if ext:
|
|
fileName += ext.group(1)
|
|
settings.setValue('/Processing/LastOutputPath',
|
|
os.path.dirname(fileName))
|
|
settings.setValue('/Processing/encoding', self.encoding)
|
|
|
|
uri = QgsDataSourceUri()
|
|
uri.setDatabase(fileName)
|
|
uri.setDataSource('', self.parameter.name().lower(),
|
|
'the_geom' if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
|
|
self.leText.setText("spatialite:" + uri.uri())
|
|
|
|
def selectFile(self):
|
|
fileFilter = getFileFilter(self.parameter)
|
|
|
|
settings = QgsSettings()
|
|
if settings.contains('/Processing/LastOutputPath'):
|
|
path = settings.value('/Processing/LastOutputPath')
|
|
else:
|
|
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
|
|
|
|
fileDialog = QgsEncodingFileDialog(
|
|
self, self.tr('Save file'), path, fileFilter, self.encoding)
|
|
fileDialog.setFileMode(QFileDialog.AnyFile)
|
|
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
|
|
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)
|
|
|
|
if fileDialog.exec_() == QDialog.Accepted:
|
|
self.use_temporary = False
|
|
files = fileDialog.selectedFiles()
|
|
self.encoding = str(fileDialog.encoding())
|
|
fileName = str(files[0])
|
|
selectedFileFilter = str(fileDialog.selectedNameFilter())
|
|
if not fileName.lower().endswith(
|
|
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
|
|
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
|
|
if ext:
|
|
fileName += ext.group(1)
|
|
self.leText.setText(fileName)
|
|
settings.setValue('/Processing/LastOutputPath',
|
|
os.path.dirname(fileName))
|
|
settings.setValue('/Processing/encoding', self.encoding)
|
|
|
|
def selectDirectory(self):
|
|
lastDir = ''
|
|
dirName = QFileDialog.getExistingDirectory(self, self.tr('Select directory'),
|
|
lastDir, QFileDialog.ShowDirsOnly)
|
|
self.leText.setText(dirName)
|
|
|
|
def getValue(self):
|
|
key = None
|
|
if self.use_temporary and isinstance(self.parameter, QgsProcessingParameterFeatureSink):
|
|
key = 'memory:'
|
|
else:
|
|
key = self.leText.text()
|
|
|
|
if not key and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
|
|
return None
|
|
|
|
value = QgsProcessingOutputLayerDefinition(key)
|
|
value.createOptions = {'fileEncoding': self.encoding}
|
|
return value
|