mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-18 00:03:05 -04:00
If a feature sink parameter is optional and not set, don't create the sink
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).
This commit is contained in:
parent
2d2dff9b4a
commit
6b55300fbc
@ -118,6 +118,7 @@ class AlgorithmDialog(AlgorithmDialogBase):
|
||||
dest_project = QgsProject.instance()
|
||||
|
||||
value = self.mainWidget.outputWidgets[param.name()].getValue()
|
||||
if value:
|
||||
value.destinationProject = dest_project
|
||||
parameters[param.name()] = value
|
||||
|
||||
|
@ -39,7 +39,8 @@ from qgis.core import (QgsDataSourceUri,
|
||||
QgsExpression,
|
||||
QgsSettings,
|
||||
QgsProcessingParameterFeatureSink,
|
||||
QgsProcessingOutputLayerDefinition)
|
||||
QgsProcessingOutputLayerDefinition,
|
||||
QgsProcessingParameterDefinition)
|
||||
from processing.core.ProcessingConfig import ProcessingConfig
|
||||
from processing.core.outputs import OutputVector
|
||||
from processing.core.outputs import OutputDirectory
|
||||
@ -58,6 +59,8 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
'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)
|
||||
@ -67,9 +70,13 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
self.alg = alg
|
||||
settings = QgsSettings()
|
||||
self.encoding = settings.value('/Processing/encoding', 'System')
|
||||
self.use_temporary = True
|
||||
|
||||
if hasattr(self.leText, 'setPlaceholderText'):
|
||||
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
|
||||
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)
|
||||
@ -77,6 +84,14 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
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):
|
||||
@ -84,6 +99,13 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
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
|
||||
@ -133,12 +155,18 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
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')
|
||||
@ -173,6 +201,7 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
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])
|
||||
@ -208,6 +237,7 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
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])
|
||||
@ -230,11 +260,14 @@ class DestinationSelectionPanel(BASE, WIDGET):
|
||||
|
||||
def getValue(self):
|
||||
key = None
|
||||
if not self.leText.text():
|
||||
if isinstance(self.parameter, QgsProcessingParameterFeatureSink):
|
||||
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
|
||||
|
@ -231,6 +231,11 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
|
||||
}
|
||||
else if ( !val.isValid() || val.toString().isEmpty() )
|
||||
{
|
||||
if ( definition && definition->flags() & QgsProcessingParameterDefinition::FlagOptional && !definition->defaultValue().isValid() )
|
||||
{
|
||||
// unset, optional sink, no default => no sink
|
||||
return nullptr;
|
||||
}
|
||||
// fall back to default
|
||||
dest = definition->defaultValue().toString();
|
||||
}
|
||||
|
@ -2562,11 +2562,11 @@ void TestQgsProcessing::processingFeatureSink()
|
||||
context.setProject( &p );
|
||||
|
||||
// first using static string definition
|
||||
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "layer" ) );
|
||||
std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
|
||||
QVariantMap params;
|
||||
params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( "memory:test", nullptr ) );
|
||||
QString dest;
|
||||
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def, params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) );
|
||||
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) );
|
||||
QVERIFY( sink.get() );
|
||||
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
|
||||
QVERIFY( layer );
|
||||
@ -2574,11 +2574,46 @@ void TestQgsProcessing::processingFeatureSink()
|
||||
|
||||
// next using property based definition
|
||||
params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('memory' + ':test2')" ) ), nullptr ) );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def, params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
QVERIFY( sink.get() );
|
||||
QgsVectorLayer *layer2 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
|
||||
QVERIFY( layer2 );
|
||||
QCOMPARE( layer2->crs().authid(), QStringLiteral( "EPSG:3113" ) );
|
||||
|
||||
|
||||
// non optional sink
|
||||
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QVariant(), false ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
|
||||
QVERIFY( !def->checkValueIsAcceptable( QString() ) );
|
||||
QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
|
||||
QVERIFY( !def->checkValueIsAcceptable( 5 ) );
|
||||
params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
QVERIFY( sink.get() );
|
||||
|
||||
// optional sink
|
||||
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QVariant(), true ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QString() ) );
|
||||
QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
|
||||
QVERIFY( !def->checkValueIsAcceptable( 5 ) );
|
||||
params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
QVERIFY( sink.get() );
|
||||
// optional sink, not set - should be no sink
|
||||
params.insert( QStringLiteral( "layer" ), QVariant() );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
QVERIFY( !sink.get() );
|
||||
|
||||
//.... unless there's a default set
|
||||
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QStringLiteral( "memory:defaultlayer" ), true ) );
|
||||
params.insert( QStringLiteral( "layer" ), QVariant() );
|
||||
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
|
||||
QVERIFY( sink.get() );
|
||||
}
|
||||
|
||||
void TestQgsProcessing::algorithmScope()
|
||||
|
Loading…
x
Reference in New Issue
Block a user