From 607fed8c486b69e4dd3eb9eab64a2d258213199a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 6 Jun 2017 10:34:57 +1000 Subject: [PATCH] Restore ability to save outputs directly to Spatialite/PostGIS providers --- .../processing/qgsprocessingparameters.sip | 7 +++ .../gui/DestinationSelectionPanel.py | 12 ++--- .../plugins/processing/gui/Postprocessing.py | 2 +- src/core/processing/qgsnativealgorithms.cpp | 4 ++ .../processing/qgsprocessingparameters.cpp | 19 +++++++ src/core/processing/qgsprocessingparameters.h | 6 +++ src/core/processing/qgsprocessingutils.cpp | 51 ++++++++++--------- tests/src/core/testqgsprocessing.cpp | 11 ++++ 8 files changed, 80 insertions(+), 32 deletions(-) diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip index 917c73f12d6..680cc2fcc15 100644 --- a/python/core/processing/qgsprocessingparameters.sip +++ b/python/core/processing/qgsprocessingparameters.sip @@ -1206,6 +1206,13 @@ class QgsProcessingParameterFeatureSink : QgsProcessingParameterDefinition :rtype: QgsProcessingParameterDefinition.LayerType %End + bool hasGeometry() const; +%Docstring + Returns true if sink is likely to include geometries. In cases were presence of geometry + cannot be reliably determined in advance, this method will default to returning true. + :rtype: bool +%End + void setDataType( QgsProcessingParameterDefinition::LayerType type ); %Docstring Sets the layer ``type`` for the sinks associated with the parameter. diff --git a/python/plugins/processing/gui/DestinationSelectionPanel.py b/python/plugins/processing/gui/DestinationSelectionPanel.py index bd53f2f0e7a..38b73f1b503 100644 --- a/python/plugins/processing/gui/DestinationSelectionPanel.py +++ b/python/plugins/processing/gui/DestinationSelectionPanel.py @@ -37,7 +37,7 @@ from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog from qgis.core import (QgsDataSourceUri, QgsCredentials, QgsSettings, - QgsProcessingOutputVectorLayer, + QgsProcessingParameterFeatureSink, QgsProcessingFeatureSinkDefinition) from processing.core.ProcessingConfig import ProcessingConfig from processing.core.outputs import OutputVector @@ -67,7 +67,7 @@ class DestinationSelectionPanel(BASE, WIDGET): self.encoding = settings.value('/Processing/encoding', 'System') if hasattr(self.leText, 'setPlaceholderText'): - if isinstance(self.parameter, QgsProcessingOutputVectorLayer) \ + if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \ and alg.provider().supportsNonFileBasedOutput(): # use memory layers for temporary files if supported self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER) @@ -82,7 +82,7 @@ class DestinationSelectionPanel(BASE, WIDGET): else: popupMenu = QMenu() - if isinstance(self.parameter, QgsProcessingOutputVectorLayer) \ + if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \ and self.alg.provider().supportsNonFileBasedOutput(): # use memory layers for temporary layers if supported actionSaveToTemp = QAction( @@ -103,7 +103,7 @@ class DestinationSelectionPanel(BASE, WIDGET): actionShowExpressionsBuilder.triggered.connect(self.showExpressionsBuilder) popupMenu.addAction(actionShowExpressionsBuilder) - if isinstance(self.parameter, QgsProcessingOutputVectorLayer) \ + if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \ and self.alg.provider().supportsNonFileBasedOutput(): actionSaveToSpatialite = QAction( self.tr('Save to Spatialite table...'), self.btnSelect) @@ -145,7 +145,7 @@ class DestinationSelectionPanel(BASE, WIDGET): uri = QgsDataSourceUri() uri.setConnection(host, str(port), dbname, user, password) uri.setDataSource(dlg.schema, dlg.table, - "the_geom" if self.parameter.hasGeometry() else None) + "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) @@ -185,7 +185,7 @@ class DestinationSelectionPanel(BASE, WIDGET): uri = QgsDataSourceUri() uri.setDatabase(fileName) uri.setDataSource('', self.parameter.name().lower(), - 'the_geom' if self.parameter.hasGeometry() else None) + 'the_geom' if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None) self.leText.setText("spatialite:" + uri.uri()) def selectFile(self): diff --git a/python/plugins/processing/gui/Postprocessing.py b/python/plugins/processing/gui/Postprocessing.py index e380df9a6cc..bf378e0d977 100644 --- a/python/plugins/processing/gui/Postprocessing.py +++ b/python/plugins/processing/gui/Postprocessing.py @@ -59,7 +59,7 @@ def handleAlgorithmResults(alg, context, feedback=None, showResults=True): feedback.setProgress(100 * i / float(len(context.layersToLoadOnCompletion()))) try: layer = QgsProcessingUtils.mapLayerFromString(l, context) - if layer: + if layer is not None: layer.setName(details.name) details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer)) else: diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 1f16cdd72d1..7b48c3aa9d5 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -84,6 +84,8 @@ QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meter QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LAYER" ), context, source->fields(), QgsWkbTypes::Point, source->sourceCrs(), dest ) ); + if ( !sink ) + return QVariantMap(); long count = source->featureCount(); if ( count <= 0 ) @@ -157,6 +159,8 @@ QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LAYER" ), context, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs(), dest ) ); + if ( !sink ) + return QVariantMap(); // fixed parameters //bool dissolve = QgsProcessingParameters::parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context ); diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 6b6282dafef..389eb1e007d 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -1348,6 +1348,25 @@ QgsProcessingParameterDefinition::LayerType QgsProcessingParameterFeatureSink::d return mDataType; } +bool QgsProcessingParameterFeatureSink::hasGeometry() const +{ + switch ( mDataType ) + { + case TypeAny: + case TypeVectorAny: + case TypeVectorPoint: + case TypeVectorLine: + case TypeVectorPolygon: + case TypeTable: + return true; + + case TypeRaster: + case TypeFile: + return false; + } + return true; +} + void QgsProcessingParameterFeatureSink::setDataType( QgsProcessingParameterDefinition::LayerType type ) { mDataType = type; diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index cc3c21b3fa3..eaab688ce3d 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -1202,6 +1202,12 @@ class CORE_EXPORT QgsProcessingParameterFeatureSink : public QgsProcessingParame */ QgsProcessingParameterDefinition::LayerType dataType() const; + /** + * Returns true if sink is likely to include geometries. In cases were presence of geometry + * cannot be reliably determined in advance, this method will default to returning true. + */ + bool hasGeometry() const; + /** * Sets the layer \a type for the sinks associated with the parameter. * \see dataType() diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index a0d6a685130..ad4e6d58f4a 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -265,7 +265,6 @@ void parseDestinationString( QString &destination, QString &providerKey, QString QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions ) { QVariantMap options = createOptions; - QgsVectorLayer *layer = nullptr; if ( !options.contains( QStringLiteral( "fileEncoding" ) ) ) { // no destination encoding specified, use default @@ -275,7 +274,23 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs if ( destination.isEmpty() || destination.startsWith( QStringLiteral( "memory:" ) ) ) { // memory provider cannot be used with QgsVectorLayerImport - so create layer manually - layer = QgsMemoryProviderUtils::createMemoryLayer( destination, fields, geometryType, crs ); + std::unique_ptr< QgsVectorLayer > layer( QgsMemoryProviderUtils::createMemoryLayer( destination, fields, geometryType, crs ) ); + if ( !layer ) + return nullptr; + + if ( !layer->isValid() ) + { + return nullptr; + } + + // update destination to layer ID + destination = layer->id(); + + // this is a factory, so we need to return a proxy + std::unique_ptr< QgsProxyFeatureSink > sink( new QgsProxyFeatureSink( layer->dataProvider() ) ); + context.temporaryLayerStore()->addMapLayer( layer.release() ); + + return sink.release(); } else { @@ -297,33 +312,19 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs else { //create empty layer - { - QgsVectorLayerExporter import( uri, providerKey, fields, geometryType, crs, false, &options ); - if ( import.errorCode() ) - return nullptr; - } + std::unique_ptr< QgsVectorLayerExporter > exporter( new QgsVectorLayerExporter( uri, providerKey, fields, geometryType, crs, false, &options ) ); + if ( exporter->errorCode() ) + return nullptr; // use destination string as layer name (eg "postgis:..." ) - layer = new QgsVectorLayer( uri, destination, providerKey ); + std::unique_ptr< QgsVectorLayer > layer( new QgsVectorLayer( uri, destination, providerKey ) ); + // update destination to layer ID + destination = layer->id(); + context.temporaryLayerStore()->addMapLayer( layer.release() ); + return exporter.release(); } } - - if ( !layer ) - return nullptr; - - if ( !layer->isValid() ) - { - delete layer; - return nullptr; - } - - // update destination to layer ID - destination = layer->id(); - - context.temporaryLayerStore()->addMapLayer( layer ); - - // this is a factory, so we need to return a proxy - return new QgsProxyFeatureSink( layer->dataProvider() ); + return nullptr; } void QgsProcessingUtils::createFeatureSinkPython( QgsFeatureSink **sink, QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &options ) diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 871665945e8..0127058e5ff 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -2286,6 +2286,17 @@ void TestQgsProcessing::parameterFeatureSink() QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSinkDefinition( "layer1231123" ) ) ); + + // test hasGeometry + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeAny ).hasGeometry() ); + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeVectorAny ).hasGeometry() ); + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeVectorPoint ).hasGeometry() ); + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeVectorLine ).hasGeometry() ); + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeVectorPolygon ).hasGeometry() ); + QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeRaster ).hasGeometry() ); + QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeFile ).hasGeometry() ); + QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessingParameterDefinition::TypeTable ).hasGeometry() ); + } void TestQgsProcessing::checkParamValues()