Skip to content

Commit a8a3cc8

Browse files
committed
[processing] Port vector.createVectorWriter to c++
This implements an improved version of vector.createVectorWriter in QgsProcessingUtils. The improved version relies on the core class QgsVectorLayerImport to create empty layers, which: - reduces duplicate code and reuses the mature QgsVectorLayerImport routines - avoids manual conversion of field types to destination provider field types - potentially allows any writable provider to be used as a feature sink for algorithms (e.g. output direct to MSSQL/Oracle/db2). This should work now - it just needs exposing via UI.
1 parent 7efcfee commit a8a3cc8

File tree

8 files changed

+367
-0
lines changed

8 files changed

+367
-0
lines changed

python/core/processing/qgsprocessingcontext.sip

+14
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,23 @@ class QgsProcessingContext
118118
%End
119119

120120

121+
QString defaultEncoding() const;
122+
%Docstring
123+
Returns the default encoding to use for newly created files.
124+
.. seealso:: setDefaultEncoding()
125+
:rtype: str
126+
%End
127+
128+
void setDefaultEncoding( const QString &encoding );
129+
%Docstring
130+
Sets the default ``encoding`` to use for newly created files.
131+
.. seealso:: defaultEncoding()
132+
%End
133+
121134
private:
122135
QgsProcessingContext( const QgsProcessingContext &other );
123136
};
137+
124138
QFlags<QgsProcessingContext::Flag> operator|(QgsProcessingContext::Flag f1, QFlags<QgsProcessingContext::Flag> f2);
125139

126140

python/core/processing/qgsprocessingutils.sip

+32
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,38 @@ class QgsProcessingUtils
122122
:rtype: list of QVariant
123123
%End
124124

125+
126+
static void createFeatureSinkPython(
127+
QgsFeatureSink **sink /Out,TransferBack/,
128+
QString &destination /In,Out/,
129+
const QString &encoding,
130+
const QgsFields &fields,
131+
QgsWkbTypes::Type geometryType,
132+
const QgsCoordinateReferenceSystem &crs,
133+
QgsProcessingContext &context,
134+
QgsVectorLayer **outputLayer /Out/ ) /PyName=createFeatureSink/;
135+
%Docstring
136+
Creates a feature sink ready for adding features. The ``destination`` specifies a destination
137+
URI for the resultant layer. It may be updated in place to reflect the actual destination
138+
for the layer.
139+
140+
Sink parameters such as desired ``encoding``, ``fields``, ``geometryType`` and ``crs`` must be specified.
141+
142+
If the ``encoding`` is not specified, the default encoding from the ``context`` will be used.
143+
144+
If a layer is created for the feature sink, the layer will automatically be added to the ``context``'s
145+
temporary layer store, and the ``outputLayer`` argument updated to point at this newly created layer.
146+
147+
.. note::
148+
149+
this version of the createFeatureSink() function has an API designed around use from the
150+
SIP bindings. c++ code should call the other createFeatureSink() version.
151+
.. note::
152+
153+
available in Python bindings as createFeatureSink()
154+
%End
155+
156+
125157
};
126158

127159

python/plugins/processing/tools/dataobjects.py

+3
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def raise_error(f):
8585

8686
context.setInvalidGeometryCallback(raise_error)
8787

88+
settings = QgsSettings()
89+
context.setDefaultEncoding(settings.value("/Processing/encoding", "System"))
90+
8891
return context
8992

9093

python/plugins/processing/tools/vector.py

+1
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ def ogrLayerName(uri):
460460

461461

462462
def createVectorWriter(destination, encoding, fields, geometryType, crs, context):
463+
return QgsProcessingUtils.createFeatureSink(destination, encoding, fields, geometryType, crs, context)
463464
layer = None
464465
sink = None
465466

src/core/processing/qgsprocessingcontext.h

+14
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ class CORE_EXPORT QgsProcessingContext
142142
*/
143143
SIP_SKIP std::function< void( const QgsFeature & ) > invalidGeometryCallback() const { return mInvalidGeometryCallback; }
144144

145+
/**
146+
* Returns the default encoding to use for newly created files.
147+
* \see setDefaultEncoding()
148+
*/
149+
QString defaultEncoding() const { return mDefaultEncoding; }
150+
151+
/**
152+
* Sets the default \a encoding to use for newly created files.
153+
* \see defaultEncoding()
154+
*/
155+
void setDefaultEncoding( const QString &encoding ) { mDefaultEncoding = encoding; }
156+
145157
private:
146158

147159
QgsProcessingContext::Flags mFlags = 0;
@@ -151,11 +163,13 @@ class CORE_EXPORT QgsProcessingContext
151163
QgsExpressionContext mExpressionContext;
152164
QgsFeatureRequest::InvalidGeometryCheck mInvalidGeometryCheck = QgsFeatureRequest::GeometryNoCheck;
153165
std::function< void( const QgsFeature & ) > mInvalidGeometryCallback;
166+
QString mDefaultEncoding;
154167

155168
#ifdef SIP_RUN
156169
QgsProcessingContext( const QgsProcessingContext &other );
157170
#endif
158171
};
172+
159173
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsProcessingContext::Flags )
160174

161175
#endif // QGSPROCESSINGPARAMETERS_H

src/core/processing/qgsprocessingutils.cpp

+115
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include "qgsproject.h"
2020
#include "qgssettings.h"
2121
#include "qgsprocessingcontext.h"
22+
#include "qgsvectorlayerimport.h"
23+
#include "qgsvectorfilewriter.h"
24+
#include "qgsmemoryproviderutils.h"
2225

2326
QList<QgsRasterLayer *> QgsProcessingUtils::compatibleRasterLayers( QgsProject *project, bool sort )
2427
{
@@ -289,4 +292,116 @@ QList<QVariant> QgsProcessingUtils::uniqueValues( QgsVectorLayer *layer, int fie
289292
}
290293
}
291294

295+
void parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &format, QMap<QString, QVariant> &options )
296+
{
297+
QRegularExpression splitRx( "^(.*?):(.*)$" );
298+
QRegularExpressionMatch match = splitRx.match( destination );
299+
if ( match.hasMatch() )
300+
{
301+
providerKey = match.captured( 1 );
302+
if ( providerKey == QStringLiteral( "postgis" ) ) // older processing used "postgis" instead of "postgres"
303+
{
304+
providerKey = QStringLiteral( "postgres" );
305+
}
306+
uri = match.captured( 2 );
307+
}
308+
else
309+
{
310+
providerKey = QStringLiteral( "ogr" );
311+
QRegularExpression splitRx( "^(.*)\\.(.*?)$" );
312+
QRegularExpressionMatch match = splitRx.match( destination );
313+
QString extension;
314+
if ( match.hasMatch() )
315+
{
316+
extension = match.captured( 2 );
317+
format = QgsVectorFileWriter::driverForExtension( extension );
318+
}
319+
320+
if ( format.isEmpty() )
321+
{
322+
format = QStringLiteral( "ESRI Shapefile" );
323+
destination = destination + QStringLiteral( ".shp" );
324+
}
325+
326+
options.insert( QStringLiteral( "driverName" ), format );
327+
uri = destination;
328+
}
329+
}
330+
331+
QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, const QString &encoding, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context, QgsVectorLayer *&outputLayer )
332+
{
333+
outputLayer = nullptr;
334+
QgsVectorLayer *layer = nullptr;
335+
336+
QString destEncoding = encoding;
337+
if ( destEncoding.isEmpty() )
338+
{
339+
// no destination encoding specified, use default
340+
destEncoding = context.defaultEncoding().isEmpty() ? QStringLiteral( "system" ) : context.defaultEncoding();
341+
}
342+
343+
if ( destination.isEmpty() || destination.startsWith( QStringLiteral( "memory:" ) ) )
344+
{
345+
// memory provider cannot be used with QgsVectorLayerImport - so create layer manually
346+
layer = QgsMemoryProviderUtils::createMemoryLayer( destination, fields, geometryType, crs );
347+
if ( layer && layer->isValid() )
348+
destination = layer->id();
349+
}
350+
else
351+
{
352+
QMap<QString, QVariant> options;
353+
options.insert( QStringLiteral( "fileEncoding" ), destEncoding );
354+
355+
QString providerKey;
356+
QString uri;
357+
QString format;
358+
parseDestinationString( destination, providerKey, uri, format, options );
359+
360+
if ( providerKey == "ogr" )
361+
{
362+
// use QgsVectorFileWriter for OGR destinations instead of QgsVectorLayerImport, as that allows
363+
// us to use any OGR format which supports feature addition
364+
QString finalFileName;
365+
QgsVectorFileWriter *writer = new QgsVectorFileWriter( destination, destEncoding, fields, geometryType, crs, format, QgsVectorFileWriter::defaultDatasetOptions( format ),
366+
QgsVectorFileWriter::defaultLayerOptions( format ), &finalFileName );
367+
destination = finalFileName;
368+
return writer;
369+
}
370+
else
371+
{
372+
//create empty layer
373+
{
374+
QgsVectorLayerImport import( uri, providerKey, fields, geometryType, crs, false, &options );
375+
if ( import.hasError() )
376+
return nullptr;
377+
}
378+
379+
layer = new QgsVectorLayer( uri, destination, providerKey );
380+
}
381+
}
382+
383+
if ( !layer )
384+
return nullptr;
385+
386+
if ( !layer->isValid() )
387+
{
388+
delete layer;
389+
return nullptr;
390+
}
391+
392+
context.temporaryLayerStore()->addMapLayer( layer );
393+
394+
outputLayer = layer;
395+
// this is a factory, so we need to return a proxy
396+
return new QgsProxyFeatureSink( layer->dataProvider() );
397+
}
398+
399+
void QgsProcessingUtils::createFeatureSinkPython( QgsFeatureSink **sink, QString &destination, const QString &encoding, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context, QgsVectorLayer **outputLayer )
400+
{
401+
QgsVectorLayer *layer = nullptr;
402+
*sink = createFeatureSink( destination, encoding, fields, geometryType, crs, context, layer );
403+
if ( outputLayer )
404+
*outputLayer = layer;
405+
}
406+
292407

src/core/processing/qgsprocessingutils.h

+52
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,58 @@ class CORE_EXPORT QgsProcessingUtils
131131
*/
132132
static QList< QVariant > uniqueValues( QgsVectorLayer *layer, int fieldIndex, const QgsProcessingContext &context );
133133

134+
/**
135+
* Creates a feature sink ready for adding features. The \a destination specifies a destination
136+
* URI for the resultant layer. It may be updated in place to reflect the actual destination
137+
* for the layer.
138+
*
139+
* Sink parameters such as desired \a encoding, \a fields, \a geometryType and \a crs must be specified.
140+
*
141+
* If the \a encoding is not specified, the default encoding from the \a context will be used.
142+
*
143+
* If a layer is created for the feature sink, the layer will automatically be added to the \a context's
144+
* temporary layer store, and the \a outputLayer argument updated to point at this newly created layer.
145+
*
146+
* The caller takes responsibility for deleting the returned sink.
147+
*/
148+
#ifndef SIP_RUN
149+
static QgsFeatureSink *createFeatureSink(
150+
QString &destination,
151+
const QString &encoding,
152+
const QgsFields &fields,
153+
QgsWkbTypes::Type geometryType,
154+
const QgsCoordinateReferenceSystem &crs,
155+
QgsProcessingContext &context,
156+
QgsVectorLayer *&outputLayer ) SIP_FACTORY;
157+
#endif
158+
159+
/**
160+
* Creates a feature sink ready for adding features. The \a destination specifies a destination
161+
* URI for the resultant layer. It may be updated in place to reflect the actual destination
162+
* for the layer.
163+
*
164+
* Sink parameters such as desired \a encoding, \a fields, \a geometryType and \a crs must be specified.
165+
*
166+
* If the \a encoding is not specified, the default encoding from the \a context will be used.
167+
*
168+
* If a layer is created for the feature sink, the layer will automatically be added to the \a context's
169+
* temporary layer store, and the \a outputLayer argument updated to point at this newly created layer.
170+
*
171+
* \note this version of the createFeatureSink() function has an API designed around use from the
172+
* SIP bindings. c++ code should call the other createFeatureSink() version.
173+
* \note available in Python bindings as createFeatureSink()
174+
*/
175+
static void createFeatureSinkPython(
176+
QgsFeatureSink **sink SIP_OUT SIP_TRANSFERBACK,
177+
QString &destination SIP_INOUT,
178+
const QString &encoding,
179+
const QgsFields &fields,
180+
QgsWkbTypes::Type geometryType,
181+
const QgsCoordinateReferenceSystem &crs,
182+
QgsProcessingContext &context,
183+
QgsVectorLayer **outputLayer SIP_OUT ) SIP_PYNAME( createFeatureSink );
184+
185+
134186
private:
135187

136188
static bool canUseLayer( const QgsRasterLayer *layer );

0 commit comments

Comments
 (0)