Skip to content

Commit

Permalink
[processing] Hookup backend API to allow sinks to append to an existi…
Browse files Browse the repository at this point in the history
…ng layer

(respecting the sink mapping definition)
  • Loading branch information
nyalldawson committed Apr 7, 2020
1 parent 0d7773a commit bf6f017
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 20 deletions.
Expand Up @@ -162,12 +162,15 @@ and by coercing geometries to the format required by the destination sink.
%End
public:


QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink );
%Docstring
Constructor for QgsRemappingProxyFeatureSink, using the specified ``mappingDefinition``
to manipulate features before sending them to the destination ``sink``.
%End

~QgsRemappingProxyFeatureSink();

void setExpressionContext( const QgsExpressionContext &context );
%Docstring
Sets the expression ``context`` to use when evaluating mapped field values.
Expand Down
9 changes: 8 additions & 1 deletion src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -579,6 +579,8 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
QgsProject *destinationProject = nullptr;
QString destName;
QVariantMap createOptions;
QgsRemappingSinkDefinition remapDefinition;
bool useRemapDefinition = false;
if ( val.canConvert<QgsProcessingOutputLayerDefinition>() )
{
// input is a QgsProcessingOutputLayerDefinition - get extra properties from it
Expand All @@ -588,6 +590,11 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar

val = fromVar.sink;
destName = fromVar.destinationName;
if ( fromVar.useRemapping() )
{
useRemapDefinition = true;
remapDefinition = fromVar.remappingDefinition();
}
}

QString dest;
Expand Down Expand Up @@ -622,7 +629,7 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
if ( dest.isEmpty() )
return nullptr;

std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( dest, context, fields, geometryType, crs, createOptions, sinkFlags ) );
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( dest, context, fields, geometryType, crs, createOptions, sinkFlags, useRemapDefinition ? &remapDefinition : nullptr ) );
destinationIdentifier = dest;

if ( destinationProject )
Expand Down
59 changes: 43 additions & 16 deletions src/core/processing/qgsprocessingutils.cpp
Expand Up @@ -675,7 +675,7 @@ void QgsProcessingUtils::parseDestinationString( QString &destination, QString &
}
}

QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions, QgsFeatureSink::SinkFlags sinkFlags )
QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions, QgsFeatureSink::SinkFlags sinkFlags, QgsRemappingSinkDefinition *remappingDefinition )
{
QVariantMap options = createOptions;
if ( !options.contains( QStringLiteral( "fileEncoding" ) ) )
Expand Down Expand Up @@ -719,7 +719,7 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs
bool useWriter = false;
parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );

QgsFields newFields = fields;
QgsFields newFields = remappingDefinition ? remappingDefinition->destinationFields() : fields;
if ( useWriter && providerKey == QLatin1String( "ogr" ) )
{
// use QgsVectorFileWriter for OGR destinations instead of QgsVectorLayerImport, as that allows
Expand All @@ -731,34 +731,61 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs
saveOptions.datasourceOptions = QgsVectorFileWriter::defaultDatasetOptions( format );
saveOptions.layerOptions = QgsVectorFileWriter::defaultLayerOptions( format );
saveOptions.symbologyExport = QgsVectorFileWriter::NoSymbology;
saveOptions.actionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
saveOptions.actionOnExistingFile = remappingDefinition ? QgsVectorFileWriter::AppendToLayerNoNewFields : QgsVectorFileWriter::CreateOrOverwriteFile;
std::unique_ptr< QgsVectorFileWriter > writer( QgsVectorFileWriter::create( destination, newFields, geometryType, crs, context.transformContext(), saveOptions, sinkFlags, &finalFileName ) );
if ( writer->hasError() )
{
throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, writer->errorMessage() ) );
}
destination = finalFileName;
return new QgsProcessingFeatureSink( writer.release(), destination, context, true );
if ( remappingDefinition )
{
std::unique_ptr< QgsRemappingProxyFeatureSink > remapSink = qgis::make_unique< QgsRemappingProxyFeatureSink >( *remappingDefinition, writer.release(), true );
remapSink->setExpressionContext( context.expressionContext() );
remapSink->setTransformContext( context.transformContext() );
return new QgsProcessingFeatureSink( remapSink.release(), destination, context, true );
}
else
return new QgsProcessingFeatureSink( writer.release(), destination, context, true );
}
else
{
//create empty layer
const QgsVectorLayer::LayerOptions layerOptions { context.transformContext() };
std::unique_ptr< QgsVectorLayerExporter > exporter = qgis::make_unique<QgsVectorLayerExporter>( uri, providerKey, newFields, geometryType, crs, true, options, sinkFlags );
if ( exporter->errorCode() )
if ( remappingDefinition )
{
throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, exporter->errorMessage() ) );
//write to existing layer

// use destination string as layer name (eg "postgis:..." )
std::unique_ptr< QgsVectorLayer > layer = qgis::make_unique<QgsVectorLayer>( uri, destination, providerKey, layerOptions );
// update destination to layer ID
destination = layer->id();

context.temporaryLayerStore()->addMapLayer( layer.release() );

std::unique_ptr< QgsRemappingProxyFeatureSink > remapSink = qgis::make_unique< QgsRemappingProxyFeatureSink >( *remappingDefinition, layer->dataProvider(), false );
remapSink->setExpressionContext( context.expressionContext() );
remapSink->setTransformContext( context.transformContext() );
return new QgsProcessingFeatureSink( remapSink.release(), destination, context, true );
}
else
{
//create empty layer
std::unique_ptr< QgsVectorLayerExporter > exporter = qgis::make_unique<QgsVectorLayerExporter>( uri, providerKey, newFields, geometryType, crs, true, options, sinkFlags );
if ( exporter->errorCode() )
{
throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, exporter->errorMessage() ) );
}

// use destination string as layer name (eg "postgis:..." )
if ( !layerName.isEmpty() )
uri += QStringLiteral( "|layername=%1" ).arg( layerName );
std::unique_ptr< QgsVectorLayer > layer = qgis::make_unique<QgsVectorLayer>( uri, destination, providerKey, layerOptions );
// update destination to layer ID
destination = layer->id();
// use destination string as layer name (eg "postgis:..." )
if ( !layerName.isEmpty() )
uri += QStringLiteral( "|layername=%1" ).arg( layerName );
std::unique_ptr< QgsVectorLayer > layer = qgis::make_unique<QgsVectorLayer>( uri, destination, providerKey, layerOptions );
// update destination to layer ID
destination = layer->id();

context.temporaryLayerStore()->addMapLayer( layer.release() );
return new QgsProcessingFeatureSink( exporter.release(), destination, context, true );
context.temporaryLayerStore()->addMapLayer( layer.release() );
return new QgsProcessingFeatureSink( exporter.release(), destination, context, true );
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/core/processing/qgsprocessingutils.h
Expand Up @@ -27,6 +27,7 @@
#include "qgsfeaturesink.h"
#include "qgsfeaturesource.h"
#include "qgsproxyfeaturesink.h"
#include "qgsremappingproxyfeaturesink.h"

class QgsMeshLayer;
class QgsProject;
Expand Down Expand Up @@ -222,7 +223,8 @@ class CORE_EXPORT QgsProcessingUtils
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
const QVariantMap &createOptions = QVariantMap(),
QgsFeatureSink::SinkFlags sinkFlags = nullptr ) SIP_FACTORY;
QgsFeatureSink::SinkFlags sinkFlags = nullptr,
QgsRemappingSinkDefinition *remappingDefinition = nullptr ) SIP_FACTORY;
#endif

/**
Expand Down
10 changes: 9 additions & 1 deletion src/core/qgsremappingproxyfeaturesink.cpp
Expand Up @@ -18,12 +18,19 @@
#include "qgsremappingproxyfeaturesink.h"
#include "qgslogger.h"

QgsRemappingProxyFeatureSink::QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink )
QgsRemappingProxyFeatureSink::QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink, bool ownsSink )
: QgsFeatureSink()
, mDefinition( mappingDefinition )
, mSink( sink )
, mOwnsSink( ownsSink )
{}

QgsRemappingProxyFeatureSink::~QgsRemappingProxyFeatureSink()
{
if ( mOwnsSink )
delete mSink;
}

void QgsRemappingProxyFeatureSink::setExpressionContext( const QgsExpressionContext &context )
{
mContext = context;
Expand All @@ -39,6 +46,7 @@ QgsFeatureList QgsRemappingProxyFeatureSink::remapFeature( const QgsFeature &fea
QgsFeatureList res;

mContext.setFeature( feature );
mContext.setFields( feature.fields() );

// remap fields first
QgsFeature f;
Expand Down
17 changes: 17 additions & 0 deletions src/core/qgsremappingproxyfeaturesink.h
Expand Up @@ -179,11 +179,27 @@ class CORE_EXPORT QgsRemappingProxyFeatureSink : public QgsFeatureSink
{
public:

#ifndef SIP_RUN

/**
* Constructor for QgsRemappingProxyFeatureSink, using the specified \a mappingDefinition
* to manipulate features before sending them to the destination \a sink.
*
* Ownership of \a sink is dictated by \a ownsSink. If \a ownsSink is FALSE,
* ownership is not transferred, and callers must ensure that \a sink exists for the lifetime of this object.
* If \a ownsSink is TRUE, then this object will take ownership of \a sink.
*/
QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink, bool ownsSink = false );
#else

/**
* Constructor for QgsRemappingProxyFeatureSink, using the specified \a mappingDefinition
* to manipulate features before sending them to the destination \a sink.
*/
QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink );
#endif

~QgsRemappingProxyFeatureSink() override;

/**
* Sets the expression \a context to use when evaluating mapped field values.
Expand Down Expand Up @@ -215,6 +231,7 @@ class CORE_EXPORT QgsRemappingProxyFeatureSink : public QgsFeatureSink
QgsCoordinateTransform mTransform;
QgsFeatureSink *mSink = nullptr;
mutable QgsExpressionContext mContext;
bool mOwnsSink = false;
};

#endif // QGSREMAPPINGPROXYFEATURESINK_H
Expand Down
35 changes: 34 additions & 1 deletion tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -1800,6 +1800,8 @@ void TestQgsProcessing::createFeatureSink()
prevDest = QDir::tempPath() + "/create_feature_sink2.gpkg";
sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::PointZ, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
QVERIFY( sink.get() );
f = QgsFeature( fields );
f.setAttributes( QgsAttributes() << "val" );
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PointZ(1 2 3)" ) ) );
QVERIFY( sink->addFeature( f ) );
QCOMPARE( destination, prevDest );
Expand All @@ -1812,7 +1814,38 @@ void TestQgsProcessing::createFeatureSink()
QCOMPARE( layer->fields().at( 1 ).name(), QStringLiteral( "my_field" ) );
QCOMPARE( layer->fields().at( 1 ).type(), QVariant::String );
QCOMPARE( layer->featureCount(), 1L );

// append to existing OGR layer
QgsRemappingSinkDefinition remapDef;
remapDef.setDestinationFields( layer->fields() );
remapDef.setDestinationCrs( layer->crs() );
remapDef.setSourceCrs( QgsCoordinateReferenceSystem( "EPSG:4326" ) );
remapDef.setDestinationWkbType( QgsWkbTypes::Polygon );
remapDef.addMappedField( QStringLiteral( "fid" ), QgsProperty::fromValue( 3 ) );
remapDef.addMappedField( QStringLiteral( "my_field" ), QgsProperty::fromExpression( QStringLiteral( "field2 || @extra" ) ) );
QgsFields fields2;
fields2.append( QgsField( "field2", QVariant::String ) );
context.expressionContext().appendScope( new QgsExpressionContextScope() );
context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "extra" ), 2 );
sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields2, QgsWkbTypes::Point, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QVariantMap(), nullptr, &remapDef ) );
QVERIFY( sink.get() );
f = QgsFeature( fields2 );
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Point(10 0)" ) ) );
f.setAttributes( QgsAttributes() << "val" );
QVERIFY( sink->addFeature( f ) );
QCOMPARE( destination, prevDest );
sink.reset( nullptr );
layer = new QgsVectorLayer( destination );
QVERIFY( layer->isValid() );
QCOMPARE( layer->featureCount(), 2L );
QgsFeatureIterator it = layer->getFeatures();
QVERIFY( it.nextFeature( f ) );
QCOMPARE( f.attributes().at( 1 ).toString(), QStringLiteral( "val" ) );
QCOMPARE( f.geometry().asWkt( 1 ), QStringLiteral( "PointZ (1 2 3)" ) );
QVERIFY( it.nextFeature( f ) );
QCOMPARE( f.attributes().at( 0 ).toInt(), 3 );
QCOMPARE( f.attributes().at( 1 ).toString(), QStringLiteral( "val2" ) );
QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Point (-10199761 -4017774)" ) );
delete layer;
//windows style path
destination = "d:\\temp\\create_feature_sink.tab";
sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
Expand Down

0 comments on commit bf6f017

Please sign in to comment.