Skip to content

Commit bcca7af

Browse files
authored
Merge pull request #4495 from nyalldawson/writer
[processing] Port vector.createVectorWriter to c++
2 parents 54e208b + 06c4dea commit bcca7af

File tree

12 files changed

+377
-183
lines changed

12 files changed

+377
-183
lines changed

python/core/processing/qgsprocessingcontext.sip

Lines changed: 14 additions & 0 deletions
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

Lines changed: 32 additions & 0 deletions
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/algs/qgis/VectorSplit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def processAlgorithm(self, context, feedback):
8686
for current, i in enumerate(uniqueValues):
8787
fName = u'{0}_{1}.shp'.format(baseName, str(i).strip())
8888

89-
writer, dest, _layer = vector.createVectorWriter(fName, None, fields, geomType, crs, context)
89+
writer, dest, _layer = QgsProcessingUtils.createFeatureSink(fName, None, fields, geomType, crs, context)
9090
for f in QgsProcessingUtils.getFeatures(layer, context):
9191
if f[fieldName] == i:
9292
writer.addFeature(f)

python/plugins/processing/algs/qgis/scripts/Number_of_unique_values_in_classes.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66

77
from qgis.PyQt.QtCore import QVariant
88
from qgis.core import QgsFeature, QgsField, QgsProcessingUtils
9-
from processing.tools.vector import createVectorWriter
109

1110
layer = QgsProcessingUtils.mapLayerFromString(input, context)
1211
fields = layer.fields()
1312
fields.append(QgsField('UNIQ_COUNT', QVariant.Int))
14-
writer, writer_dest, writer_layer = createVectorWriter(N_unique_values, None, fields, layer.wkbType(), layer.crs(),
15-
context)
13+
writer, writer_dest, writer_layer = QgsProcessingUtils.createFeatureSink(N_unique_values, None, fields, layer.wkbType(), layer.crs(),
14+
context)
1615

1716
class_field_index = layer.fields().lookupField(class_field)
1817
value_field_index = layer.fields().lookupField(value_field)

python/plugins/processing/core/outputs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from processing.core.ProcessingConfig import ProcessingConfig
3737
from processing.tools.system import isWindows, getTempFilenameInTempFolder, getTempDirInTempFolder
38-
from processing.tools.vector import createVectorWriter, TableWriter, NOGEOMETRY_EXTENSIONS
38+
from processing.tools.vector import TableWriter, NOGEOMETRY_EXTENSIONS
3939
from processing.tools import dataobjects
4040

4141
from qgis.core import (QgsExpressionContext,
@@ -44,7 +44,8 @@
4444
QgsExpressionContextScope,
4545
QgsProject,
4646
QgsSettings,
47-
QgsVectorFileWriter)
47+
QgsVectorFileWriter,
48+
QgsProcessingUtils)
4849

4950

5051
def _expressionContext(alg):
@@ -384,7 +385,7 @@ def getVectorWriter(self, fields, geomType, crs, context):
384385
settings = QgsSettings()
385386
self.encoding = settings.value('/Processing/encoding', 'System', str)
386387

387-
w, w_dest, w_layer = createVectorWriter(self.value, self.encoding, fields, geomType, crs, context)
388+
w, w_dest, w_layer = QgsProcessingUtils.createFeatureSink(self.value, self.encoding, fields, geomType, crs, context)
388389
self.layer = w_layer
389390
self.value = w_dest
390391
return w

python/plugins/processing/tests/testdata/scripts/centroids.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
##INPUT_LAYER=vector
44
##OUTPUT_LAYER=output vector
55

6-
from qgis.core import QgsWkbTypes, QgsGeometry, QgsProcessingUtils
7-
8-
from processing.tools.vector import createVectorWriter
6+
from qgis.core import QgsWkbTypes, QgsProcessingUtils
97

108
layer = QgsProcessingUtils.mapLayerFromString(INPUT_LAYER, context)
119
fields = layer.fields()
1210

13-
writer, writer_dest, writer_layer = createVectorWriter(OUTPUT_LAYER, 'utf-8', fields, QgsWkbTypes.Point, layer.crs(),
14-
context)
11+
writer, writer_dest, writer_layer = QgsProcessingUtils.createFeatureSink(OUTPUT_LAYER, 'utf-8', fields, QgsWkbTypes.Point, layer.crs(),
12+
context)
1513

1614
features = QgsProcessingUtils.getFeatures(layer, context)
1715
count = QgsProcessingUtils.featureCount(layer, context)

python/plugins/processing/tools/dataobjects.py

Lines changed: 3 additions & 0 deletions
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

Lines changed: 1 addition & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,7 @@
5555
QgsProcessingContext,
5656
QgsProcessingUtils)
5757

58-
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
59-
from processing.tools import dataobjects, spatialite, postgis
60-
61-
62-
TYPE_MAP = {
63-
str: QVariant.String,
64-
float: QVariant.Double,
65-
int: QVariant.Int,
66-
bool: QVariant.Bool
67-
}
68-
69-
TYPE_MAP_MEMORY_LAYER = {
70-
QVariant.String: "string",
71-
QVariant.Double: "double",
72-
QVariant.Int: "integer",
73-
QVariant.Date: "date",
74-
QVariant.DateTime: "datetime",
75-
QVariant.Time: "time"
76-
}
77-
78-
TYPE_MAP_POSTGIS_LAYER = {
79-
QVariant.String: "VARCHAR",
80-
QVariant.Double: "REAL",
81-
QVariant.Int: "INTEGER",
82-
QVariant.Bool: "BOOLEAN"
83-
}
84-
85-
TYPE_MAP_SPATIALITE_LAYER = {
86-
QVariant.String: "VARCHAR",
87-
QVariant.Double: "REAL",
88-
QVariant.Int: "INTEGER",
89-
QVariant.Bool: "INTEGER"
90-
}
58+
from processing.tools import dataobjects
9159

9260

9361
def resolveFieldIndex(layer, attr):
@@ -305,12 +273,6 @@ def checkMinDistance(point, index, distance, points):
305273
return True
306274

307275

308-
def _toQgsField(f):
309-
if isinstance(f, QgsField):
310-
return f
311-
return QgsField(f[0], TYPE_MAP.get(f[1], QVariant.String))
312-
313-
314276
def snapToPrecision(geom, precision):
315277
snapped = QgsGeometry(geom)
316278
if precision == 0.0:
@@ -447,10 +409,6 @@ def ogrLayerName(uri):
447409
return name
448410

449411

450-
MEMORY_LAYER_PREFIX = 'memory:'
451-
POSTGIS_LAYER_PREFIX = 'postgis:'
452-
SPATIALITE_LAYER_PREFIX = 'spatialite:'
453-
454412
NOGEOMETRY_EXTENSIONS = [
455413
u'csv',
456414
u'dbf',
@@ -459,134 +417,6 @@ def ogrLayerName(uri):
459417
]
460418

461419

462-
def createVectorWriter(destination, encoding, fields, geometryType, crs, context):
463-
layer = None
464-
sink = None
465-
466-
if encoding is None:
467-
settings = QgsSettings()
468-
encoding = settings.value('/Processing/encoding', 'System', str)
469-
470-
if destination.startswith(MEMORY_LAYER_PREFIX):
471-
uri = QgsWkbTypes.displayString(geometryType) + "?uuid=" + str(uuid.uuid4())
472-
if crs.isValid():
473-
uri += '&crs=' + crs.authid()
474-
fieldsdesc = []
475-
for f in fields:
476-
qgsfield = _toQgsField(f)
477-
fieldsdesc.append('field=%s:%s' % (qgsfield.name(),
478-
TYPE_MAP_MEMORY_LAYER.get(qgsfield.type(), "string")))
479-
if fieldsdesc:
480-
uri += '&' + '&'.join(fieldsdesc)
481-
482-
layer = QgsVectorLayer(uri, destination, 'memory')
483-
sink = layer.dataProvider()
484-
context.temporaryLayerStore().addMapLayer(layer)
485-
destination = layer.id()
486-
elif destination.startswith(POSTGIS_LAYER_PREFIX):
487-
uri = QgsDataSourceUri(destination[len(POSTGIS_LAYER_PREFIX):])
488-
connInfo = uri.connectionInfo()
489-
(success, user, passwd) = QgsCredentials.instance().get(connInfo, None, None)
490-
if success:
491-
QgsCredentials.instance().put(connInfo, user, passwd)
492-
else:
493-
raise GeoAlgorithmExecutionException("Couldn't connect to database")
494-
try:
495-
db = postgis.GeoDB(host=uri.host(), port=int(uri.port()),
496-
dbname=uri.database(), user=user, passwd=passwd)
497-
except postgis.DbError as e:
498-
raise GeoAlgorithmExecutionException(
499-
"Couldn't connect to database:\n%s" % e.message)
500-
501-
def _runSQL(sql):
502-
try:
503-
db._exec_sql_and_commit(str(sql))
504-
except postgis.DbError as e:
505-
raise GeoAlgorithmExecutionException(
506-
'Error creating output PostGIS table:\n%s' % e.message)
507-
508-
fields = [_toQgsField(f) for f in fields]
509-
fieldsdesc = ",".join('%s %s' % (f.name(),
510-
TYPE_MAP_POSTGIS_LAYER.get(f.type(), "VARCHAR"))
511-
for f in fields)
512-
513-
_runSQL("CREATE TABLE %s.%s (%s)" % (uri.schema(), uri.table().lower(), fieldsdesc))
514-
if geometryType != QgsWkbTypes.NullGeometry:
515-
_runSQL("SELECT AddGeometryColumn('{schema}', '{table}', 'the_geom', {srid}, '{typmod}', 2)".format(
516-
table=uri.table().lower(), schema=uri.schema(), srid=crs.authid().split(":")[-1],
517-
typmod=QgsWkbTypes.displayString(geometryType).upper()))
518-
519-
layer = QgsVectorLayer(uri.uri(), uri.table(), "postgres")
520-
sink = layer.dataProvider()
521-
context.temporaryLayerStore().addMapLayer(layer)
522-
elif destination.startswith(SPATIALITE_LAYER_PREFIX):
523-
uri = QgsDataSourceUri(destination[len(SPATIALITE_LAYER_PREFIX):])
524-
try:
525-
db = spatialite.GeoDB(uri=uri)
526-
except spatialite.DbError as e:
527-
raise GeoAlgorithmExecutionException(
528-
"Couldn't connect to database:\n%s" % e.message)
529-
530-
def _runSQL(sql):
531-
try:
532-
db._exec_sql_and_commit(str(sql))
533-
except spatialite.DbError as e:
534-
raise GeoAlgorithmExecutionException(
535-
'Error creating output Spatialite table:\n%s' % str(e))
536-
537-
fields = [_toQgsField(f) for f in fields]
538-
fieldsdesc = ",".join('%s %s' % (f.name(),
539-
TYPE_MAP_SPATIALITE_LAYER.get(f.type(), "VARCHAR"))
540-
for f in fields)
541-
542-
_runSQL("DROP TABLE IF EXISTS %s" % uri.table().lower())
543-
_runSQL("CREATE TABLE %s (%s)" % (uri.table().lower(), fieldsdesc))
544-
if geometryType != QgsWkbTypes.NullGeometry:
545-
_runSQL("SELECT AddGeometryColumn('{table}', 'the_geom', {srid}, '{typmod}', 2)".format(
546-
table=uri.table().lower(), srid=crs.authid().split(":")[-1],
547-
typmod=QgsWkbTypes.displayString(geometryType).upper()))
548-
549-
layer = QgsVectorLayer(uri.uri(), uri.table(), "spatialite")
550-
sink = layer.dataProvider()
551-
context.temporaryLayerStore().addMapLayer(layer)
552-
else:
553-
formats = QgsVectorFileWriter.supportedFiltersAndFormats()
554-
OGRCodes = {}
555-
for (key, value) in list(formats.items()):
556-
extension = str(key)
557-
extension = extension[extension.find('*.') + 2:]
558-
extension = extension[:extension.find(' ')]
559-
OGRCodes[extension] = value
560-
OGRCodes['dbf'] = "DBF file"
561-
562-
extension = destination[destination.rfind('.') + 1:]
563-
564-
if extension not in OGRCodes:
565-
extension = 'shp'
566-
destination = destination + '.shp'
567-
568-
if geometryType == QgsWkbTypes.NoGeometry:
569-
if extension == 'shp':
570-
extension = 'dbf'
571-
destination = destination[:destination.rfind('.')] + '.dbf'
572-
if extension not in NOGEOMETRY_EXTENSIONS:
573-
raise GeoAlgorithmExecutionException(
574-
"Unsupported format for tables with no geometry")
575-
576-
qgsfields = QgsFields()
577-
for field in fields:
578-
qgsfields.append(_toQgsField(field))
579-
580-
# use default dataset/layer options
581-
dataset_options = QgsVectorFileWriter.defaultDatasetOptions(OGRCodes[extension])
582-
layer_options = QgsVectorFileWriter.defaultLayerOptions(OGRCodes[extension])
583-
584-
sink = QgsVectorFileWriter(destination, encoding,
585-
qgsfields, geometryType, crs, OGRCodes[extension],
586-
dataset_options, layer_options)
587-
return sink, destination, layer
588-
589-
590420
class TableWriter(object):
591421

592422
def __init__(self, fileName, encoding, fields):

src/core/processing/qgsprocessingcontext.h

Lines changed: 14 additions & 0 deletions
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

0 commit comments

Comments
 (0)