Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsprocessingutils.cpp
3 : : ------------------------
4 : : begin : April 2017
5 : : copyright : (C) 2017 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsprocessingutils.h"
19 : : #include "qgsproject.h"
20 : : #include "qgssettings.h"
21 : : #include "qgsexception.h"
22 : : #include "qgsprocessingcontext.h"
23 : : #include "qgsvectorlayerexporter.h"
24 : : #include "qgsvectorfilewriter.h"
25 : : #include "qgsmemoryproviderutils.h"
26 : : #include "qgsprocessingparameters.h"
27 : : #include "qgsprocessingalgorithm.h"
28 : : #include "qgsvectorlayerfeatureiterator.h"
29 : : #include "qgsexpressioncontextscopegenerator.h"
30 : : #include "qgsfileutils.h"
31 : : #include "qgsvectorlayer.h"
32 : : #include "qgsproviderregistry.h"
33 : : #include "qgsmeshlayer.h"
34 : : #include "qgsreferencedgeometry.h"
35 : : #include "qgsrasterfilewriter.h"
36 : : #include "qgsvectortilelayer.h"
37 : : #include <QRegularExpression>
38 : : #include <QUuid>
39 : :
40 : 0 : QList<QgsRasterLayer *> QgsProcessingUtils::compatibleRasterLayers( QgsProject *project, bool sort )
41 : : {
42 : 0 : if ( !project )
43 : 0 : return QList<QgsRasterLayer *>();
44 : :
45 : 0 : QList<QgsRasterLayer *> layers;
46 : :
47 : 0 : const auto rasterLayers = project->layers<QgsRasterLayer *>();
48 : 0 : for ( QgsRasterLayer *l : rasterLayers )
49 : : {
50 : 0 : if ( canUseLayer( l ) )
51 : 0 : layers << l;
52 : : }
53 : :
54 : 0 : if ( sort )
55 : : {
56 : 0 : std::sort( layers.begin(), layers.end(), []( const QgsRasterLayer * a, const QgsRasterLayer * b ) -> bool
57 : : {
58 : 0 : return QString::localeAwareCompare( a->name(), b->name() ) < 0;
59 : 0 : } );
60 : 0 : }
61 : 0 : return layers;
62 : 0 : }
63 : :
64 : 0 : QList<QgsVectorLayer *> QgsProcessingUtils::compatibleVectorLayers( QgsProject *project, const QList<int> &geometryTypes, bool sort )
65 : : {
66 : 0 : if ( !project )
67 : 0 : return QList<QgsVectorLayer *>();
68 : :
69 : 0 : QList<QgsVectorLayer *> layers;
70 : 0 : const auto vectorLayers = project->layers<QgsVectorLayer *>();
71 : 0 : for ( QgsVectorLayer *l : vectorLayers )
72 : : {
73 : 0 : if ( canUseLayer( l, geometryTypes ) )
74 : 0 : layers << l;
75 : : }
76 : :
77 : 0 : if ( sort )
78 : : {
79 : 0 : std::sort( layers.begin(), layers.end(), []( const QgsVectorLayer * a, const QgsVectorLayer * b ) -> bool
80 : : {
81 : 0 : return QString::localeAwareCompare( a->name(), b->name() ) < 0;
82 : 0 : } );
83 : 0 : }
84 : 0 : return layers;
85 : 0 : }
86 : :
87 : 0 : QList<QgsMeshLayer *> QgsProcessingUtils::compatibleMeshLayers( QgsProject *project, bool sort )
88 : : {
89 : 0 : if ( !project )
90 : 0 : return QList<QgsMeshLayer *>();
91 : :
92 : 0 : QList<QgsMeshLayer *> layers;
93 : 0 : const auto meshLayers = project->layers<QgsMeshLayer *>();
94 : 0 : for ( QgsMeshLayer *l : meshLayers )
95 : : {
96 : 0 : if ( canUseLayer( l ) )
97 : 0 : layers << l;
98 : : }
99 : :
100 : 0 : if ( sort )
101 : : {
102 : 0 : std::sort( layers.begin(), layers.end(), []( const QgsMeshLayer * a, const QgsMeshLayer * b ) -> bool
103 : : {
104 : 0 : return QString::localeAwareCompare( a->name(), b->name() ) < 0;
105 : 0 : } );
106 : 0 : }
107 : 0 : return layers;
108 : 0 : }
109 : :
110 : 0 : QList<QgsMapLayer *> QgsProcessingUtils::compatibleLayers( QgsProject *project, bool sort )
111 : : {
112 : 0 : if ( !project )
113 : 0 : return QList<QgsMapLayer *>();
114 : :
115 : 0 : QList<QgsMapLayer *> layers;
116 : :
117 : 0 : const auto rasterLayers = compatibleRasterLayers( project, false );
118 : 0 : for ( QgsRasterLayer *rl : rasterLayers )
119 : 0 : layers << rl;
120 : :
121 : 0 : const auto vectorLayers = compatibleVectorLayers( project, QList< int >(), false );
122 : 0 : for ( QgsVectorLayer *vl : vectorLayers )
123 : 0 : layers << vl;
124 : :
125 : 0 : const auto meshLayers = compatibleMeshLayers( project, false );
126 : 0 : for ( QgsMeshLayer *vl : meshLayers )
127 : 0 : layers << vl;
128 : :
129 : 0 : if ( sort )
130 : : {
131 : 0 : std::sort( layers.begin(), layers.end(), []( const QgsMapLayer * a, const QgsMapLayer * b ) -> bool
132 : : {
133 : 0 : return QString::localeAwareCompare( a->name(), b->name() ) < 0;
134 : 0 : } );
135 : 0 : }
136 : 0 : return layers;
137 : 0 : }
138 : :
139 : 0 : QString QgsProcessingUtils::encodeProviderKeyAndUri( const QString &providerKey, const QString &uri )
140 : : {
141 : 0 : return QStringLiteral( "%1://%2" ).arg( providerKey, uri );
142 : 0 : }
143 : :
144 : 0 : bool QgsProcessingUtils::decodeProviderKeyAndUri( const QString &string, QString &providerKey, QString &uri )
145 : : {
146 : 0 : QRegularExpression re( QStringLiteral( "^(\\w+?):\\/\\/(.+)$" ) );
147 : 0 : const QRegularExpressionMatch match = re.match( string );
148 : 0 : if ( !match.hasMatch() )
149 : 0 : return false;
150 : :
151 : 0 : providerKey = match.captured( 1 );
152 : 0 : uri = match.captured( 2 );
153 : :
154 : : // double check that provider is valid
155 : 0 : return QgsProviderRegistry::instance()->providerMetadata( providerKey );
156 : 0 : }
157 : :
158 : 0 : QgsMapLayer *QgsProcessingUtils::mapLayerFromStore( const QString &string, QgsMapLayerStore *store, QgsProcessingUtils::LayerHint typeHint )
159 : : {
160 : 0 : if ( !store || string.isEmpty() )
161 : 0 : return nullptr;
162 : :
163 : 0 : QList< QgsMapLayer * > layers = store->mapLayers().values();
164 : :
165 : 0 : layers.erase( std::remove_if( layers.begin(), layers.end(), []( QgsMapLayer * layer )
166 : : {
167 : 0 : switch ( layer->type() )
168 : : {
169 : : case QgsMapLayerType::VectorLayer:
170 : 0 : return !canUseLayer( qobject_cast< QgsVectorLayer * >( layer ) );
171 : : case QgsMapLayerType::RasterLayer:
172 : 0 : return !canUseLayer( qobject_cast< QgsRasterLayer * >( layer ) );
173 : : case QgsMapLayerType::PluginLayer:
174 : 0 : return true;
175 : : case QgsMapLayerType::MeshLayer:
176 : 0 : return !canUseLayer( qobject_cast< QgsMeshLayer * >( layer ) );
177 : : case QgsMapLayerType::VectorTileLayer:
178 : 0 : return !canUseLayer( qobject_cast< QgsVectorTileLayer * >( layer ) );
179 : : case QgsMapLayerType::AnnotationLayer:
180 : 0 : return true;
181 : : case QgsMapLayerType::PointCloudLayer:
182 : 0 : return true;
183 : : }
184 : 0 : return true;
185 : 0 : } ), layers.end() );
186 : :
187 : 0 : auto isCompatibleType = [typeHint]( QgsMapLayer * l ) -> bool
188 : : {
189 : 0 : switch ( typeHint )
190 : : {
191 : : case LayerHint::UnknownType:
192 : 0 : return true;
193 : :
194 : : case LayerHint::Vector:
195 : 0 : return l->type() == QgsMapLayerType::VectorLayer;
196 : :
197 : : case LayerHint::Raster:
198 : 0 : return l->type() == QgsMapLayerType::RasterLayer;
199 : :
200 : : case LayerHint::Mesh:
201 : 0 : return l->type() == QgsMapLayerType::MeshLayer;
202 : : }
203 : 0 : return true;
204 : 0 : };
205 : :
206 : 0 : for ( QgsMapLayer *l : std::as_const( layers ) )
207 : : {
208 : 0 : if ( isCompatibleType( l ) && l->id() == string )
209 : 0 : return l;
210 : : }
211 : 0 : for ( QgsMapLayer *l : std::as_const( layers ) )
212 : : {
213 : 0 : if ( isCompatibleType( l ) && l->name() == string )
214 : 0 : return l;
215 : : }
216 : 0 : for ( QgsMapLayer *l : std::as_const( layers ) )
217 : : {
218 : 0 : if ( isCompatibleType( l ) && normalizeLayerSource( l->source() ) == normalizeLayerSource( string ) )
219 : 0 : return l;
220 : : }
221 : 0 : return nullptr;
222 : 0 : }
223 : :
224 : 0 : QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, const QgsCoordinateTransformContext &transformContext, LayerHint typeHint )
225 : : {
226 : 0 : QString provider;
227 : 0 : QString uri;
228 : 0 : const bool useProvider = decodeProviderKeyAndUri( string, provider, uri );
229 : 0 : if ( !useProvider )
230 : 0 : uri = string;
231 : :
232 : 0 : QString name;
233 : : // for disk based sources, we use the filename to determine a layer name
234 : 0 : if ( !useProvider || ( provider == QLatin1String( "ogr" ) || provider == QLatin1String( "gdal" ) || provider == QLatin1String( "mdal" ) ) )
235 : : {
236 : 0 : QStringList components = uri.split( '|' );
237 : 0 : if ( components.isEmpty() )
238 : 0 : return nullptr;
239 : :
240 : 0 : QFileInfo fi;
241 : 0 : if ( QFileInfo::exists( uri ) )
242 : 0 : fi = QFileInfo( uri );
243 : 0 : else if ( QFileInfo::exists( components.at( 0 ) ) )
244 : 0 : fi = QFileInfo( components.at( 0 ) );
245 : : else
246 : 0 : return nullptr;
247 : 0 : name = fi.baseName();
248 : 0 : }
249 : : else
250 : : {
251 : 0 : name = QgsDataSourceUri( uri ).table();
252 : : }
253 : :
254 : : // brute force attempt to load a matching layer
255 : 0 : if ( typeHint == LayerHint::UnknownType || typeHint == LayerHint::Vector )
256 : : {
257 : 0 : QgsVectorLayer::LayerOptions options { transformContext };
258 : 0 : options.loadDefaultStyle = false;
259 : 0 : options.skipCrsValidation = true;
260 : :
261 : 0 : std::unique_ptr< QgsVectorLayer > layer;
262 : 0 : if ( useProvider )
263 : : {
264 : 0 : layer = std::make_unique<QgsVectorLayer>( uri, name, provider, options );
265 : 0 : }
266 : : else
267 : : {
268 : : // fallback to ogr
269 : 0 : layer = std::make_unique<QgsVectorLayer>( uri, name, QStringLiteral( "ogr" ), options );
270 : : }
271 : 0 : if ( layer->isValid() )
272 : : {
273 : 0 : return layer.release();
274 : : }
275 : 0 : }
276 : 0 : if ( typeHint == LayerHint::UnknownType || typeHint == LayerHint::Raster )
277 : : {
278 : 0 : QgsRasterLayer::LayerOptions rasterOptions;
279 : 0 : rasterOptions.loadDefaultStyle = false;
280 : 0 : rasterOptions.skipCrsValidation = true;
281 : :
282 : 0 : std::unique_ptr< QgsRasterLayer > rasterLayer;
283 : 0 : if ( useProvider )
284 : : {
285 : 0 : rasterLayer = std::make_unique< QgsRasterLayer >( uri, name, provider, rasterOptions );
286 : 0 : }
287 : : else
288 : : {
289 : : // fallback to gdal
290 : 0 : rasterLayer = std::make_unique< QgsRasterLayer >( uri, name, QStringLiteral( "gdal" ), rasterOptions );
291 : : }
292 : :
293 : 0 : if ( rasterLayer->isValid() )
294 : : {
295 : 0 : return rasterLayer.release();
296 : : }
297 : 0 : }
298 : 0 : if ( typeHint == LayerHint::UnknownType || typeHint == LayerHint::Mesh )
299 : : {
300 : 0 : QgsMeshLayer::LayerOptions meshOptions;
301 : 0 : meshOptions.skipCrsValidation = true;
302 : :
303 : 0 : std::unique_ptr< QgsMeshLayer > meshLayer;
304 : 0 : if ( useProvider )
305 : : {
306 : 0 : meshLayer = std::make_unique< QgsMeshLayer >( uri, name, provider, meshOptions );
307 : 0 : }
308 : : else
309 : : {
310 : 0 : meshLayer = std::make_unique< QgsMeshLayer >( uri, name, QStringLiteral( "mdal" ), meshOptions );
311 : : }
312 : 0 : if ( meshLayer->isValid() )
313 : : {
314 : 0 : return meshLayer.release();
315 : : }
316 : 0 : }
317 : 0 : return nullptr;
318 : 0 : }
319 : :
320 : 0 : QgsMapLayer *QgsProcessingUtils::mapLayerFromString( const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers, LayerHint typeHint )
321 : : {
322 : 0 : if ( string.isEmpty() )
323 : 0 : return nullptr;
324 : :
325 : : // prefer project layers
326 : 0 : QgsMapLayer *layer = nullptr;
327 : 0 : if ( auto *lProject = context.project() )
328 : : {
329 : 0 : QgsMapLayer *layer = mapLayerFromStore( string, lProject->layerStore(), typeHint );
330 : 0 : if ( layer )
331 : 0 : return layer;
332 : 0 : }
333 : :
334 : 0 : layer = mapLayerFromStore( string, context.temporaryLayerStore(), typeHint );
335 : 0 : if ( layer )
336 : 0 : return layer;
337 : :
338 : 0 : if ( !allowLoadingNewLayers )
339 : 0 : return nullptr;
340 : :
341 : 0 : layer = loadMapLayerFromString( string, context.transformContext(), typeHint );
342 : 0 : if ( layer )
343 : : {
344 : 0 : context.temporaryLayerStore()->addMapLayer( layer );
345 : 0 : return layer;
346 : : }
347 : : else
348 : : {
349 : 0 : return nullptr;
350 : : }
351 : 0 : }
352 : :
353 : 0 : QgsProcessingFeatureSource *QgsProcessingUtils::variantToSource( const QVariant &value, QgsProcessingContext &context, const QVariant &fallbackValue )
354 : : {
355 : 0 : QVariant val = value;
356 : 0 : bool selectedFeaturesOnly = false;
357 : 0 : long long featureLimit = -1;
358 : 0 : bool overrideGeometryCheck = false;
359 : 0 : QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid;
360 : 0 : if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
361 : : {
362 : : // input is a QgsProcessingFeatureSourceDefinition - get extra properties from it
363 : 0 : QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
364 : 0 : selectedFeaturesOnly = fromVar.selectedFeaturesOnly;
365 : 0 : featureLimit = fromVar.featureLimit;
366 : 0 : val = fromVar.source;
367 : 0 : overrideGeometryCheck = fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck;
368 : 0 : geometryCheck = fromVar.geometryCheck;
369 : 0 : }
370 : 0 : else if ( val.canConvert<QgsProcessingOutputLayerDefinition>() )
371 : : {
372 : : // input is a QgsProcessingOutputLayerDefinition (e.g. an output from earlier in a model) - get extra properties from it
373 : 0 : QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( val );
374 : 0 : val = fromVar.sink;
375 : 0 : }
376 : :
377 : 0 : if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( qvariant_cast<QObject *>( val ) ) )
378 : : {
379 : 0 : std::unique_ptr< QgsProcessingFeatureSource> source = std::make_unique< QgsProcessingFeatureSource >( layer, context, false, featureLimit );
380 : 0 : if ( overrideGeometryCheck )
381 : 0 : source->setInvalidGeometryCheck( geometryCheck );
382 : 0 : return source.release();
383 : 0 : }
384 : :
385 : 0 : QString layerRef;
386 : 0 : if ( val.canConvert<QgsProperty>() )
387 : : {
388 : 0 : layerRef = val.value< QgsProperty >().valueAsString( context.expressionContext(), fallbackValue.toString() );
389 : 0 : }
390 : 0 : else if ( !val.isValid() || val.toString().isEmpty() )
391 : : {
392 : : // fall back to default
393 : 0 : if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( qvariant_cast<QObject *>( fallbackValue ) ) )
394 : : {
395 : 0 : std::unique_ptr< QgsProcessingFeatureSource> source = std::make_unique< QgsProcessingFeatureSource >( layer, context, false, featureLimit );
396 : 0 : if ( overrideGeometryCheck )
397 : 0 : source->setInvalidGeometryCheck( geometryCheck );
398 : 0 : return source.release();
399 : 0 : }
400 : :
401 : 0 : layerRef = fallbackValue.toString();
402 : 0 : }
403 : : else
404 : : {
405 : 0 : layerRef = val.toString();
406 : : }
407 : :
408 : 0 : if ( layerRef.isEmpty() )
409 : 0 : return nullptr;
410 : :
411 : 0 : QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( layerRef, context, true, LayerHint::Vector ) );
412 : 0 : if ( !vl )
413 : 0 : return nullptr;
414 : :
415 : 0 : std::unique_ptr< QgsProcessingFeatureSource> source;
416 : 0 : if ( selectedFeaturesOnly )
417 : : {
418 : 0 : source = std::make_unique< QgsProcessingFeatureSource>( new QgsVectorLayerSelectedFeatureSource( vl ), context, true, featureLimit );
419 : 0 : }
420 : : else
421 : : {
422 : 0 : source = std::make_unique< QgsProcessingFeatureSource >( vl, context, false, featureLimit );
423 : : }
424 : :
425 : 0 : if ( overrideGeometryCheck )
426 : 0 : source->setInvalidGeometryCheck( geometryCheck );
427 : 0 : return source.release();
428 : 0 : }
429 : :
430 : 0 : QgsCoordinateReferenceSystem QgsProcessingUtils::variantToCrs( const QVariant &value, QgsProcessingContext &context, const QVariant &fallbackValue )
431 : : {
432 : 0 : QVariant val = value;
433 : :
434 : 0 : if ( val.canConvert<QgsCoordinateReferenceSystem>() )
435 : : {
436 : : // input is a QgsCoordinateReferenceSystem - done!
437 : 0 : return val.value< QgsCoordinateReferenceSystem >();
438 : : }
439 : 0 : else if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
440 : : {
441 : : // input is a QgsProcessingFeatureSourceDefinition - get extra properties from it
442 : 0 : QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
443 : 0 : val = fromVar.source;
444 : 0 : }
445 : 0 : else if ( val.canConvert<QgsProcessingOutputLayerDefinition>() )
446 : : {
447 : : // input is a QgsProcessingOutputLayerDefinition - get extra properties from it
448 : 0 : QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( val );
449 : 0 : val = fromVar.sink;
450 : 0 : }
451 : :
452 : 0 : if ( val.canConvert<QgsProperty>() && val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
453 : : {
454 : 0 : val = val.value< QgsProperty >().staticValue();
455 : 0 : }
456 : :
457 : : // maybe a map layer
458 : 0 : if ( QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( qvariant_cast<QObject *>( val ) ) )
459 : 0 : return layer->crs();
460 : :
461 : 0 : if ( val.canConvert<QgsProperty>() )
462 : 0 : val = val.value< QgsProperty >().valueAsString( context.expressionContext(), fallbackValue.toString() );
463 : :
464 : 0 : if ( !val.isValid() )
465 : : {
466 : : // fall back to default
467 : 0 : val = fallbackValue;
468 : 0 : }
469 : :
470 : 0 : QString crsText = val.toString();
471 : 0 : if ( crsText.isEmpty() )
472 : 0 : crsText = fallbackValue.toString();
473 : :
474 : 0 : if ( crsText.isEmpty() )
475 : 0 : return QgsCoordinateReferenceSystem();
476 : :
477 : : // maybe special string
478 : 0 : if ( context.project() && crsText.compare( QLatin1String( "ProjectCrs" ), Qt::CaseInsensitive ) == 0 )
479 : 0 : return context.project()->crs();
480 : :
481 : : // maybe a map layer reference
482 : 0 : if ( QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( crsText, context ) )
483 : 0 : return layer->crs();
484 : :
485 : : // else CRS from string
486 : 0 : QgsCoordinateReferenceSystem crs;
487 : 0 : crs.createFromString( crsText );
488 : 0 : return crs;
489 : 0 : }
490 : :
491 : 0 : bool QgsProcessingUtils::canUseLayer( const QgsMeshLayer *layer )
492 : : {
493 : 0 : return layer && layer->dataProvider();
494 : : }
495 : :
496 : 0 : bool QgsProcessingUtils::canUseLayer( const QgsVectorTileLayer *layer )
497 : : {
498 : 0 : return layer && layer->isValid();
499 : : }
500 : :
501 : 0 : bool QgsProcessingUtils::canUseLayer( const QgsRasterLayer *layer )
502 : : {
503 : 0 : return layer && layer->isValid();
504 : : }
505 : :
506 : 0 : bool QgsProcessingUtils::canUseLayer( const QgsVectorLayer *layer, const QList<int> &sourceTypes )
507 : : {
508 : 0 : return layer && layer->isValid() &&
509 : 0 : ( sourceTypes.isEmpty()
510 : 0 : || ( sourceTypes.contains( QgsProcessing::TypeVectorPoint ) && layer->geometryType() == QgsWkbTypes::PointGeometry )
511 : 0 : || ( sourceTypes.contains( QgsProcessing::TypeVectorLine ) && layer->geometryType() == QgsWkbTypes::LineGeometry )
512 : 0 : || ( sourceTypes.contains( QgsProcessing::TypeVectorPolygon ) && layer->geometryType() == QgsWkbTypes::PolygonGeometry )
513 : 0 : || ( sourceTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) && layer->isSpatial() )
514 : 0 : || sourceTypes.contains( QgsProcessing::TypeVector )
515 : : );
516 : : }
517 : :
518 : 0 : QString QgsProcessingUtils::normalizeLayerSource( const QString &source )
519 : : {
520 : 0 : QString normalized = source;
521 : 0 : normalized.replace( '\\', '/' );
522 : 0 : return normalized.trimmed();
523 : 0 : }
524 : :
525 : 0 : QString QgsProcessingUtils::variantToPythonLiteral( const QVariant &value )
526 : : {
527 : 0 : if ( !value.isValid() )
528 : 0 : return QStringLiteral( "None" );
529 : :
530 : 0 : if ( value.canConvert<QgsProperty>() )
531 : 0 : return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() );
532 : 0 : else if ( value.canConvert<QgsCoordinateReferenceSystem>() )
533 : : {
534 : 0 : if ( !value.value< QgsCoordinateReferenceSystem >().isValid() )
535 : 0 : return QStringLiteral( "QgsCoordinateReferenceSystem()" );
536 : : else
537 : 0 : return QStringLiteral( "QgsCoordinateReferenceSystem('%1')" ).arg( value.value< QgsCoordinateReferenceSystem >().authid() );
538 : : }
539 : 0 : else if ( value.canConvert< QgsRectangle >() )
540 : : {
541 : 0 : QgsRectangle r = value.value<QgsRectangle>();
542 : 0 : return QStringLiteral( "'%1, %3, %2, %4'" ).arg( qgsDoubleToString( r.xMinimum() ),
543 : 0 : qgsDoubleToString( r.yMinimum() ),
544 : 0 : qgsDoubleToString( r.xMaximum() ),
545 : 0 : qgsDoubleToString( r.yMaximum() ) );
546 : : }
547 : 0 : else if ( value.canConvert< QgsReferencedRectangle >() )
548 : : {
549 : 0 : QgsReferencedRectangle r = value.value<QgsReferencedRectangle>();
550 : 0 : return QStringLiteral( "'%1, %3, %2, %4 [%5]'" ).arg( qgsDoubleToString( r.xMinimum() ),
551 : 0 : qgsDoubleToString( r.yMinimum() ),
552 : 0 : qgsDoubleToString( r.xMaximum() ),
553 : 0 : qgsDoubleToString( r.yMaximum() ), r.crs().authid() );
554 : 0 : }
555 : 0 : else if ( value.canConvert< QgsPointXY >() )
556 : : {
557 : 0 : QgsPointXY r = value.value<QgsPointXY>();
558 : 0 : return QStringLiteral( "'%1,%2'" ).arg( qgsDoubleToString( r.x() ),
559 : 0 : qgsDoubleToString( r.y() ) );
560 : : }
561 : 0 : else if ( value.canConvert< QgsReferencedPointXY >() )
562 : : {
563 : 0 : QgsReferencedPointXY r = value.value<QgsReferencedPointXY>();
564 : 0 : return QStringLiteral( "'%1,%2 [%3]'" ).arg( qgsDoubleToString( r.x() ),
565 : 0 : qgsDoubleToString( r.y() ),
566 : 0 : r.crs().authid() );
567 : 0 : }
568 : :
569 : 0 : switch ( value.type() )
570 : : {
571 : : case QVariant::Bool:
572 : 0 : return value.toBool() ? QStringLiteral( "True" ) : QStringLiteral( "False" );
573 : :
574 : : case QVariant::Double:
575 : 0 : return QString::number( value.toDouble() );
576 : :
577 : : case QVariant::Int:
578 : : case QVariant::UInt:
579 : 0 : return QString::number( value.toInt() );
580 : :
581 : : case QVariant::LongLong:
582 : : case QVariant::ULongLong:
583 : 0 : return QString::number( value.toLongLong() );
584 : :
585 : : case QVariant::List:
586 : : {
587 : 0 : QStringList parts;
588 : 0 : const QVariantList vl = value.toList();
589 : 0 : for ( const QVariant &v : vl )
590 : : {
591 : 0 : parts << variantToPythonLiteral( v );
592 : : }
593 : 0 : return parts.join( ',' ).prepend( '[' ).append( ']' );
594 : 0 : }
595 : :
596 : : case QVariant::Map:
597 : : {
598 : 0 : const QVariantMap map = value.toMap();
599 : 0 : QStringList parts;
600 : 0 : parts.reserve( map.size() );
601 : 0 : for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
602 : : {
603 : 0 : parts << QStringLiteral( "%1: %2" ).arg( stringToPythonLiteral( it.key() ), variantToPythonLiteral( it.value() ) );
604 : 0 : }
605 : 0 : return parts.join( ',' ).prepend( '{' ).append( '}' );
606 : 0 : }
607 : :
608 : : case QVariant::DateTime:
609 : : {
610 : 0 : const QDateTime dateTime = value.toDateTime();
611 : 0 : return QStringLiteral( "QDateTime(QDate(%1, %2, %3), QTime(%4, %5, %6))" )
612 : 0 : .arg( dateTime.date().year() )
613 : 0 : .arg( dateTime.date().month() )
614 : 0 : .arg( dateTime.date().day() )
615 : 0 : .arg( dateTime.time().hour() )
616 : 0 : .arg( dateTime.time().minute() )
617 : 0 : .arg( dateTime.time().second() );
618 : 0 : }
619 : :
620 : : default:
621 : 0 : break;
622 : : }
623 : :
624 : 0 : return QgsProcessingUtils::stringToPythonLiteral( value.toString() );
625 : 0 : }
626 : :
627 : 0 : QString QgsProcessingUtils::stringToPythonLiteral( const QString &string )
628 : : {
629 : 0 : QString s = string;
630 : 0 : s.replace( '\\', QLatin1String( "\\\\" ) );
631 : 0 : s.replace( '\n', QLatin1String( "\\n" ) );
632 : 0 : s.replace( '\r', QLatin1String( "\\r" ) );
633 : 0 : s.replace( '\t', QLatin1String( "\\t" ) );
634 : 0 : s.replace( '"', QLatin1String( "\\\"" ) );
635 : 0 : s.replace( '\'', QLatin1String( "\\\'" ) );
636 : 0 : s = s.prepend( '\'' ).append( '\'' );
637 : 0 : return s;
638 : 0 : }
639 : :
640 : 0 : void QgsProcessingUtils::parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &layerName, QString &format, QMap<QString, QVariant> &options, bool &useWriter, QString &extension )
641 : : {
642 : 0 : extension.clear();
643 : 0 : bool matched = decodeProviderKeyAndUri( destination, providerKey, uri );
644 : :
645 : 0 : if ( !matched )
646 : : {
647 : 0 : QRegularExpression splitRx( QStringLiteral( "^(.{3,}?):(.*)$" ) );
648 : 0 : QRegularExpressionMatch match = splitRx.match( destination );
649 : 0 : if ( match.hasMatch() )
650 : : {
651 : 0 : providerKey = match.captured( 1 );
652 : 0 : uri = match.captured( 2 );
653 : 0 : matched = true;
654 : 0 : }
655 : 0 : }
656 : :
657 : 0 : if ( matched )
658 : : {
659 : 0 : if ( providerKey == QLatin1String( "postgis" ) ) // older processing used "postgis" instead of "postgres"
660 : : {
661 : 0 : providerKey = QStringLiteral( "postgres" );
662 : 0 : }
663 : 0 : if ( providerKey == QLatin1String( "ogr" ) )
664 : : {
665 : 0 : QgsDataSourceUri dsUri( uri );
666 : 0 : if ( !dsUri.database().isEmpty() )
667 : : {
668 : 0 : if ( !dsUri.table().isEmpty() )
669 : : {
670 : 0 : layerName = dsUri.table();
671 : 0 : options.insert( QStringLiteral( "layerName" ), layerName );
672 : 0 : }
673 : 0 : uri = dsUri.database();
674 : 0 : extension = QFileInfo( uri ).completeSuffix();
675 : 0 : format = QgsVectorFileWriter::driverForExtension( extension );
676 : 0 : options.insert( QStringLiteral( "driverName" ), format );
677 : 0 : }
678 : : else
679 : : {
680 : 0 : extension = QFileInfo( uri ).completeSuffix();
681 : 0 : options.insert( QStringLiteral( "driverName" ), QgsVectorFileWriter::driverForExtension( extension ) );
682 : : }
683 : 0 : options.insert( QStringLiteral( "update" ), true );
684 : 0 : }
685 : 0 : useWriter = false;
686 : 0 : }
687 : : else
688 : : {
689 : 0 : useWriter = true;
690 : 0 : providerKey = QStringLiteral( "ogr" );
691 : :
692 : 0 : QRegularExpression splitRx( QStringLiteral( "^(.*)\\.(.*?)$" ) );
693 : 0 : QRegularExpressionMatch match = splitRx.match( destination );
694 : 0 : if ( match.hasMatch() )
695 : : {
696 : 0 : extension = match.captured( 2 );
697 : 0 : format = QgsVectorFileWriter::driverForExtension( extension );
698 : 0 : }
699 : :
700 : 0 : if ( format.isEmpty() )
701 : : {
702 : 0 : format = QStringLiteral( "GPKG" );
703 : 0 : destination = destination + QStringLiteral( ".gpkg" );
704 : 0 : }
705 : :
706 : 0 : options.insert( QStringLiteral( "driverName" ), format );
707 : 0 : uri = destination;
708 : 0 : }
709 : 0 : }
710 : :
711 : 0 : QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions, const QStringList &datasourceOptions, const QStringList &layerOptions, QgsFeatureSink::SinkFlags sinkFlags, QgsRemappingSinkDefinition *remappingDefinition )
712 : : {
713 : 0 : QVariantMap options = createOptions;
714 : 0 : if ( !options.contains( QStringLiteral( "fileEncoding" ) ) )
715 : : {
716 : : // no destination encoding specified, use default
717 : 0 : options.insert( QStringLiteral( "fileEncoding" ), context.defaultEncoding().isEmpty() ? QStringLiteral( "system" ) : context.defaultEncoding() );
718 : 0 : }
719 : :
720 : 0 : if ( destination.isEmpty() || destination.startsWith( QLatin1String( "memory:" ) ) )
721 : : {
722 : : // strip "memory:" from start of destination
723 : 0 : if ( destination.startsWith( QLatin1String( "memory:" ) ) )
724 : 0 : destination = destination.mid( 7 );
725 : :
726 : 0 : if ( destination.isEmpty() )
727 : 0 : destination = QStringLiteral( "output" );
728 : :
729 : : // memory provider cannot be used with QgsVectorLayerImport - so create layer manually
730 : 0 : std::unique_ptr< QgsVectorLayer > layer( QgsMemoryProviderUtils::createMemoryLayer( destination, fields, geometryType, crs ) );
731 : 0 : if ( !layer || !layer->isValid() )
732 : : {
733 : 0 : throw QgsProcessingException( QObject::tr( "Could not create memory layer" ) );
734 : : }
735 : :
736 : 0 : layer->setCustomProperty( QStringLiteral( "OnConvertFormatRegeneratePrimaryKey" ), static_cast< bool >( sinkFlags & QgsFeatureSink::RegeneratePrimaryKey ) );
737 : :
738 : : // update destination to layer ID
739 : 0 : destination = layer->id();
740 : :
741 : : // this is a factory, so we need to return a proxy
742 : 0 : std::unique_ptr< QgsProcessingFeatureSink > sink( new QgsProcessingFeatureSink( layer->dataProvider(), destination, context ) );
743 : 0 : context.temporaryLayerStore()->addMapLayer( layer.release() );
744 : :
745 : 0 : return sink.release();
746 : 0 : }
747 : : else
748 : : {
749 : 0 : QString providerKey;
750 : 0 : QString uri;
751 : 0 : QString layerName;
752 : 0 : QString format;
753 : 0 : QString extension;
754 : 0 : bool useWriter = false;
755 : 0 : parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
756 : :
757 : 0 : QgsFields newFields = fields;
758 : 0 : if ( useWriter && providerKey == QLatin1String( "ogr" ) )
759 : : {
760 : : // use QgsVectorFileWriter for OGR destinations instead of QgsVectorLayerImport, as that allows
761 : : // us to use any OGR format which supports feature addition
762 : 0 : QString finalFileName;
763 : 0 : QString finalLayerName;
764 : 0 : QgsVectorFileWriter::SaveVectorOptions saveOptions;
765 : 0 : saveOptions.fileEncoding = options.value( QStringLiteral( "fileEncoding" ) ).toString();
766 : 0 : saveOptions.layerName = !layerName.isEmpty() ? layerName : options.value( QStringLiteral( "layerName" ) ).toString();
767 : 0 : saveOptions.driverName = format;
768 : 0 : saveOptions.datasourceOptions = !datasourceOptions.isEmpty() ? datasourceOptions : QgsVectorFileWriter::defaultDatasetOptions( format );
769 : 0 : saveOptions.layerOptions = !layerOptions.isEmpty() ? layerOptions : QgsVectorFileWriter::defaultLayerOptions( format );
770 : 0 : saveOptions.symbologyExport = QgsVectorFileWriter::NoSymbology;
771 : 0 : if ( remappingDefinition )
772 : : {
773 : 0 : saveOptions.actionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
774 : : // sniff destination file to get correct wkb type and crs
775 : 0 : std::unique_ptr< QgsVectorLayer > vl = std::make_unique< QgsVectorLayer >( destination );
776 : 0 : if ( vl->isValid() )
777 : : {
778 : 0 : remappingDefinition->setDestinationWkbType( vl->wkbType() );
779 : 0 : remappingDefinition->setDestinationCrs( vl->crs() );
780 : 0 : newFields = vl->fields();
781 : 0 : remappingDefinition->setDestinationFields( newFields );
782 : 0 : }
783 : 0 : context.expressionContext().setFields( fields );
784 : 0 : }
785 : : else
786 : : {
787 : 0 : saveOptions.actionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
788 : : }
789 : 0 : std::unique_ptr< QgsVectorFileWriter > writer( QgsVectorFileWriter::create( destination, newFields, geometryType, crs, context.transformContext(), saveOptions, sinkFlags, &finalFileName, &finalLayerName ) );
790 : 0 : if ( writer->hasError() )
791 : : {
792 : 0 : throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, writer->errorMessage() ) );
793 : : }
794 : 0 : destination = finalFileName;
795 : 0 : if ( !saveOptions.layerName.isEmpty() && !finalLayerName.isEmpty() )
796 : 0 : destination += QStringLiteral( "|layername=%1" ).arg( finalLayerName );
797 : :
798 : 0 : if ( remappingDefinition )
799 : : {
800 : 0 : std::unique_ptr< QgsRemappingProxyFeatureSink > remapSink = std::make_unique< QgsRemappingProxyFeatureSink >( *remappingDefinition, writer.release(), true );
801 : 0 : remapSink->setExpressionContext( context.expressionContext() );
802 : 0 : remapSink->setTransformContext( context.transformContext() );
803 : 0 : return new QgsProcessingFeatureSink( remapSink.release(), destination, context, true );
804 : 0 : }
805 : : else
806 : 0 : return new QgsProcessingFeatureSink( writer.release(), destination, context, true );
807 : 0 : }
808 : : else
809 : : {
810 : 0 : const QgsVectorLayer::LayerOptions layerOptions { context.transformContext() };
811 : 0 : if ( remappingDefinition )
812 : : {
813 : : //write to existing layer
814 : :
815 : : // use destination string as layer name (eg "postgis:..." )
816 : 0 : if ( !layerName.isEmpty() )
817 : : {
818 : 0 : QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( providerKey, uri );
819 : 0 : parts.insert( QStringLiteral( "layerName" ), layerName );
820 : 0 : uri = QgsProviderRegistry::instance()->encodeUri( providerKey, parts );
821 : 0 : }
822 : :
823 : 0 : std::unique_ptr< QgsVectorLayer > layer = std::make_unique<QgsVectorLayer>( uri, destination, providerKey, layerOptions );
824 : : // update destination to layer ID
825 : 0 : destination = layer->id();
826 : 0 : if ( layer->isValid() )
827 : : {
828 : 0 : remappingDefinition->setDestinationWkbType( layer->wkbType() );
829 : 0 : remappingDefinition->setDestinationCrs( layer->crs() );
830 : 0 : remappingDefinition->setDestinationFields( layer->fields() );
831 : 0 : }
832 : :
833 : 0 : std::unique_ptr< QgsRemappingProxyFeatureSink > remapSink = std::make_unique< QgsRemappingProxyFeatureSink >( *remappingDefinition, layer->dataProvider(), false );
834 : 0 : context.temporaryLayerStore()->addMapLayer( layer.release() );
835 : 0 : remapSink->setExpressionContext( context.expressionContext() );
836 : 0 : remapSink->setTransformContext( context.transformContext() );
837 : 0 : context.expressionContext().setFields( fields );
838 : 0 : return new QgsProcessingFeatureSink( remapSink.release(), destination, context, true );
839 : 0 : }
840 : : else
841 : : {
842 : : //create empty layer
843 : 0 : std::unique_ptr< QgsVectorLayerExporter > exporter = std::make_unique<QgsVectorLayerExporter>( uri, providerKey, newFields, geometryType, crs, true, options, sinkFlags );
844 : 0 : if ( exporter->errorCode() )
845 : : {
846 : 0 : throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, exporter->errorMessage() ) );
847 : : }
848 : :
849 : : // use destination string as layer name (eg "postgis:..." )
850 : 0 : if ( !layerName.isEmpty() )
851 : : {
852 : 0 : uri += QStringLiteral( "|layername=%1" ).arg( layerName );
853 : : // update destination to generated URI
854 : 0 : destination = uri;
855 : 0 : }
856 : :
857 : 0 : return new QgsProcessingFeatureSink( exporter.release(), destination, context, true );
858 : 0 : }
859 : 0 : }
860 : 0 : }
861 : 0 : }
862 : :
863 : 0 : void QgsProcessingUtils::createFeatureSinkPython( QgsFeatureSink **sink, QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &options )
864 : : {
865 : 0 : *sink = createFeatureSink( destination, context, fields, geometryType, crs, options );
866 : 0 : }
867 : :
868 : :
869 : 0 : QgsRectangle QgsProcessingUtils::combineLayerExtents( const QList<QgsMapLayer *> &layers, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context )
870 : : {
871 : 0 : QgsRectangle extent;
872 : 0 : for ( const QgsMapLayer *layer : layers )
873 : : {
874 : 0 : if ( !layer )
875 : 0 : continue;
876 : :
877 : 0 : if ( crs.isValid() )
878 : : {
879 : : //transform layer extent to target CRS
880 : 0 : QgsCoordinateTransform ct( layer->crs(), crs, context.transformContext() );
881 : : try
882 : : {
883 : 0 : QgsRectangle reprojExtent = ct.transformBoundingBox( layer->extent() );
884 : 0 : extent.combineExtentWith( reprojExtent );
885 : 0 : }
886 : : catch ( QgsCsException & )
887 : : {
888 : : // can't reproject... what to do here? hmmm?
889 : : // let's ignore this layer for now, but maybe we should just use the original extent?
890 : 0 : }
891 : 0 : }
892 : : else
893 : : {
894 : 0 : extent.combineExtentWith( layer->extent() );
895 : : }
896 : :
897 : : }
898 : 0 : return extent;
899 : 0 : }
900 : :
901 : : // Deprecated
902 : 0 : QgsRectangle QgsProcessingUtils::combineLayerExtents( const QList<QgsMapLayer *> &layers, const QgsCoordinateReferenceSystem &crs )
903 : : {
904 : 0 : QgsProcessingContext context;
905 : 0 : return QgsProcessingUtils::combineLayerExtents( layers, crs, context );
906 : 0 : }
907 : :
908 : 0 : QVariant QgsProcessingUtils::generateIteratingDestination( const QVariant &input, const QVariant &id, QgsProcessingContext &context )
909 : : {
910 : 0 : if ( !input.isValid() )
911 : 0 : return QStringLiteral( "memory:%1" ).arg( id.toString() );
912 : :
913 : 0 : if ( input.canConvert<QgsProcessingOutputLayerDefinition>() )
914 : : {
915 : 0 : QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( input );
916 : 0 : QVariant newSink = generateIteratingDestination( fromVar.sink, id, context );
917 : 0 : fromVar.sink = QgsProperty::fromValue( newSink );
918 : 0 : return fromVar;
919 : 0 : }
920 : 0 : else if ( input.canConvert<QgsProperty>() )
921 : : {
922 : 0 : QString res = input.value< QgsProperty>().valueAsString( context.expressionContext() );
923 : 0 : return generateIteratingDestination( res, id, context );
924 : 0 : }
925 : : else
926 : : {
927 : 0 : QString res = input.toString();
928 : 0 : if ( res == QgsProcessing::TEMPORARY_OUTPUT )
929 : : {
930 : : // temporary outputs map to temporary outputs!
931 : 0 : return QgsProcessing::TEMPORARY_OUTPUT;
932 : : }
933 : 0 : else if ( res.startsWith( QLatin1String( "memory:" ) ) )
934 : : {
935 : 0 : return QString( res + '_' + id.toString() );
936 : : }
937 : : else
938 : : {
939 : : // assume a filename type output for now
940 : : // TODO - uris?
941 : 0 : int lastIndex = res.lastIndexOf( '.' );
942 : 0 : return QString( res.left( lastIndex ) + '_' + id.toString() + res.mid( lastIndex ) );
943 : : }
944 : 0 : }
945 : 0 : }
946 : :
947 : 0 : QString QgsProcessingUtils::tempFolder()
948 : : {
949 : : // we maintain a list of temporary folders -- this allows us to append additional
950 : : // folders when a setting change causes the base temp folder to change, while deferring
951 : : // cleanup of ALL these temp folders until session end (we can't cleanup older folders immediately,
952 : : // because we don't know whether they have data in them which is still wanted)
953 : 0 : static std::vector< std::unique_ptr< QTemporaryDir > > sTempFolders;
954 : 0 : static QString sFolder;
955 : 0 : static QMutex sMutex;
956 : 0 : QMutexLocker locker( &sMutex );
957 : 0 : const QString basePath = QgsSettings().value( QStringLiteral( "Processing/Configuration/TEMP_PATH2" ) ).toString();
958 : 0 : if ( basePath.isEmpty() )
959 : : {
960 : : // default setting -- automatically create a temp folder
961 : 0 : if ( sTempFolders.empty() )
962 : : {
963 : 0 : const QString templatePath = QStringLiteral( "%1/processing_XXXXXX" ).arg( QDir::tempPath() );
964 : 0 : std::unique_ptr< QTemporaryDir > tempFolder = std::make_unique< QTemporaryDir >( templatePath );
965 : 0 : sFolder = tempFolder->path();
966 : 0 : sTempFolders.emplace_back( std::move( tempFolder ) );
967 : 0 : }
968 : 0 : }
969 : 0 : else if ( sFolder.isEmpty() || !sFolder.startsWith( basePath ) || sTempFolders.empty() )
970 : : {
971 : 0 : if ( !QDir().exists( basePath ) )
972 : 0 : QDir().mkpath( basePath );
973 : :
974 : 0 : const QString templatePath = QStringLiteral( "%1/processing_XXXXXX" ).arg( basePath );
975 : 0 : std::unique_ptr< QTemporaryDir > tempFolder = std::make_unique< QTemporaryDir >( templatePath );
976 : 0 : sFolder = tempFolder->path();
977 : 0 : sTempFolders.emplace_back( std::move( tempFolder ) );
978 : 0 : }
979 : 0 : return sFolder;
980 : 0 : }
981 : :
982 : 0 : QString QgsProcessingUtils::generateTempFilename( const QString &basename )
983 : : {
984 : 0 : QString subPath = QUuid::createUuid().toString().remove( '-' ).remove( '{' ).remove( '}' );
985 : 0 : QString path = tempFolder() + '/' + subPath;
986 : 0 : if ( !QDir( path ).exists() ) //make sure the directory exists - it shouldn't, but lets be safe...
987 : : {
988 : 0 : QDir tmpDir;
989 : 0 : tmpDir.mkdir( path );
990 : 0 : }
991 : 0 : return path + '/' + QgsFileUtils::stringToSafeFilename( basename );
992 : 0 : }
993 : :
994 : 0 : QString QgsProcessingUtils::formatHelpMapAsHtml( const QVariantMap &map, const QgsProcessingAlgorithm *algorithm )
995 : : {
996 : 0 : auto getText = [map]( const QString & key )->QString
997 : : {
998 : 0 : if ( map.contains( key ) )
999 : 0 : return map.value( key ).toString();
1000 : 0 : return QString();
1001 : 0 : };
1002 : :
1003 : 0 : QString s = QObject::tr( "<html><body><h2>Algorithm description</h2>\n" );
1004 : 0 : s += QStringLiteral( "<p>" ) + getText( QStringLiteral( "ALG_DESC" ) ) + QStringLiteral( "</p>\n" );
1005 : :
1006 : 0 : QString inputs;
1007 : :
1008 : 0 : const auto parameterDefinitions = algorithm->parameterDefinitions();
1009 : 0 : for ( const QgsProcessingParameterDefinition *def : parameterDefinitions )
1010 : : {
1011 : 0 : inputs += QStringLiteral( "<h3>" ) + def->description() + QStringLiteral( "</h3>\n" );
1012 : 0 : inputs += QStringLiteral( "<p>" ) + getText( def->name() ) + QStringLiteral( "</p>\n" );
1013 : : }
1014 : 0 : if ( !inputs.isEmpty() )
1015 : 0 : s += QObject::tr( "<h2>Input parameters</h2>\n" ) + inputs;
1016 : :
1017 : 0 : QString outputs;
1018 : 0 : const auto outputDefinitions = algorithm->outputDefinitions();
1019 : 0 : for ( const QgsProcessingOutputDefinition *def : outputDefinitions )
1020 : : {
1021 : 0 : outputs += QStringLiteral( "<h3>" ) + def->description() + QStringLiteral( "</h3>\n" );
1022 : 0 : outputs += QStringLiteral( "<p>" ) + getText( def->name() ) + QStringLiteral( "</p>\n" );
1023 : : }
1024 : 0 : if ( !outputs.isEmpty() )
1025 : 0 : s += QObject::tr( "<h2>Outputs</h2>\n" ) + outputs;
1026 : :
1027 : 0 : s += QLatin1String( "<br>" );
1028 : 0 : if ( !map.value( QStringLiteral( "ALG_CREATOR" ) ).toString().isEmpty() )
1029 : 0 : s += QObject::tr( "<p align=\"right\">Algorithm author: %1</p>" ).arg( getText( QStringLiteral( "ALG_CREATOR" ) ) );
1030 : 0 : if ( !map.value( QStringLiteral( "ALG_HELP_CREATOR" ) ).toString().isEmpty() )
1031 : 0 : s += QObject::tr( "<p align=\"right\">Help author: %1</p>" ).arg( getText( QStringLiteral( "ALG_HELP_CREATOR" ) ) );
1032 : 0 : if ( !map.value( QStringLiteral( "ALG_VERSION" ) ).toString().isEmpty() )
1033 : 0 : s += QObject::tr( "<p align=\"right\">Algorithm version: %1</p>" ).arg( getText( QStringLiteral( "ALG_VERSION" ) ) );
1034 : :
1035 : 0 : s += QLatin1String( "</body></html>" );
1036 : 0 : return s;
1037 : 0 : }
1038 : :
1039 : 0 : QString convertToCompatibleFormatInternal( const QgsVectorLayer *vl, bool selectedFeaturesOnly, const QString &baseName, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QString *layerName,
1040 : : long long featureLimit )
1041 : : {
1042 : 0 : bool requiresTranslation = false;
1043 : :
1044 : : // if we are only looking for selected features then we have to export back to disk,
1045 : : // as we need to subset only selected features, a concept which doesn't exist outside QGIS!
1046 : 0 : requiresTranslation = requiresTranslation || selectedFeaturesOnly;
1047 : :
1048 : : // if we are limiting the feature count, we better export
1049 : 0 : requiresTranslation = requiresTranslation || featureLimit != -1;
1050 : :
1051 : : // if the data provider is NOT ogr, then we HAVE to convert. Otherwise we run into
1052 : : // issues with data providers like spatialite, delimited text where the format can be
1053 : : // opened outside of QGIS, but with potentially very different behavior!
1054 : 0 : requiresTranslation = requiresTranslation || vl->providerType() != QLatin1String( "ogr" );
1055 : :
1056 : : // if the layer has a feature filter set, then we HAVE to convert. Feature filters are
1057 : : // a purely QGIS concept.
1058 : 0 : requiresTranslation = requiresTranslation || !vl->subsetString().isEmpty();
1059 : :
1060 : : // if the layer opened using GDAL's virtual I/O mechanism (/vsizip/, etc.), then
1061 : : // we HAVE to convert as other tools may not work with it
1062 : 0 : requiresTranslation = requiresTranslation || vl->source().startsWith( QLatin1String( "/vsi" ) );
1063 : :
1064 : : // Check if layer is a disk based format and if so if the layer's path has a compatible filename suffix
1065 : 0 : QString diskPath;
1066 : 0 : if ( !requiresTranslation )
1067 : : {
1068 : 0 : const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( vl->providerType(), vl->source() );
1069 : 0 : if ( parts.contains( QStringLiteral( "path" ) ) )
1070 : : {
1071 : 0 : diskPath = parts.value( QStringLiteral( "path" ) ).toString();
1072 : 0 : QFileInfo fi( diskPath );
1073 : 0 : requiresTranslation = !compatibleFormats.contains( fi.suffix(), Qt::CaseInsensitive );
1074 : :
1075 : : // if the layer name doesn't match the filename, we need to convert the layer. This method can only return
1076 : : // a filename, and cannot handle layernames as well as file paths
1077 : 0 : const QString srcLayerName = parts.value( QStringLiteral( "layerName" ) ).toString();
1078 : 0 : if ( layerName )
1079 : : {
1080 : : // differing layer names are acceptable
1081 : 0 : *layerName = srcLayerName;
1082 : 0 : }
1083 : : else
1084 : : {
1085 : : // differing layer names are NOT acceptable
1086 : 0 : requiresTranslation = requiresTranslation || ( !srcLayerName.isEmpty() && srcLayerName != fi.baseName() );
1087 : : }
1088 : 0 : }
1089 : : else
1090 : : {
1091 : 0 : requiresTranslation = true; // not a disk-based format
1092 : : }
1093 : 0 : }
1094 : :
1095 : 0 : if ( requiresTranslation )
1096 : : {
1097 : 0 : QString temp = QgsProcessingUtils::generateTempFilename( baseName + '.' + preferredFormat );
1098 : :
1099 : 0 : QgsVectorFileWriter::SaveVectorOptions saveOptions;
1100 : 0 : saveOptions.fileEncoding = context.defaultEncoding();
1101 : 0 : saveOptions.driverName = QgsVectorFileWriter::driverForExtension( preferredFormat );
1102 : 0 : std::unique_ptr< QgsVectorFileWriter > writer( QgsVectorFileWriter::create( temp, vl->fields(), vl->wkbType(), vl->crs(), context.transformContext(), saveOptions ) );
1103 : 0 : QgsFeature f;
1104 : 0 : QgsFeatureIterator it;
1105 : 0 : if ( featureLimit != -1 )
1106 : : {
1107 : 0 : if ( selectedFeaturesOnly )
1108 : 0 : it = vl->getSelectedFeatures( QgsFeatureRequest().setLimit( featureLimit ) );
1109 : : else
1110 : 0 : it = vl->getFeatures( QgsFeatureRequest().setLimit( featureLimit ) );
1111 : 0 : }
1112 : : else
1113 : : {
1114 : 0 : if ( selectedFeaturesOnly )
1115 : 0 : it = vl->getSelectedFeatures( QgsFeatureRequest().setLimit( featureLimit ) );
1116 : : else
1117 : 0 : it = vl->getFeatures();
1118 : : }
1119 : :
1120 : 0 : while ( it.nextFeature( f ) )
1121 : : {
1122 : 0 : if ( feedback->isCanceled() )
1123 : 0 : return QString();
1124 : 0 : writer->addFeature( f, QgsFeatureSink::FastInsert );
1125 : : }
1126 : 0 : return temp;
1127 : 0 : }
1128 : : else
1129 : : {
1130 : 0 : return diskPath;
1131 : : }
1132 : 0 : }
1133 : :
1134 : 0 : QString QgsProcessingUtils::convertToCompatibleFormat( const QgsVectorLayer *vl, bool selectedFeaturesOnly, const QString &baseName, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingContext &context, QgsProcessingFeedback *feedback, long long featureLimit )
1135 : : {
1136 : 0 : return convertToCompatibleFormatInternal( vl, selectedFeaturesOnly, baseName, compatibleFormats, preferredFormat, context, feedback, nullptr, featureLimit );
1137 : : }
1138 : :
1139 : 0 : QString QgsProcessingUtils::convertToCompatibleFormatAndLayerName( const QgsVectorLayer *layer, bool selectedFeaturesOnly, const QString &baseName, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QString &layerName, long long featureLimit )
1140 : : {
1141 : 0 : layerName.clear();
1142 : 0 : return convertToCompatibleFormatInternal( layer, selectedFeaturesOnly, baseName, compatibleFormats, preferredFormat, context, feedback, &layerName, featureLimit );
1143 : : }
1144 : :
1145 : 0 : QgsFields QgsProcessingUtils::combineFields( const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix )
1146 : : {
1147 : 0 : QgsFields outFields = fieldsA;
1148 : 0 : QSet< QString > usedNames;
1149 : 0 : for ( const QgsField &f : fieldsA )
1150 : : {
1151 : 0 : usedNames.insert( f.name().toLower() );
1152 : : }
1153 : :
1154 : 0 : for ( const QgsField &f : fieldsB )
1155 : : {
1156 : 0 : QgsField newField = f;
1157 : 0 : newField.setName( fieldsBPrefix + f.name() );
1158 : 0 : if ( usedNames.contains( newField.name().toLower() ) )
1159 : : {
1160 : 0 : int idx = 2;
1161 : 0 : QString newName = newField.name() + '_' + QString::number( idx );
1162 : 0 : while ( usedNames.contains( newName.toLower() ) )
1163 : : {
1164 : 0 : idx++;
1165 : 0 : newName = newField.name() + '_' + QString::number( idx );
1166 : : }
1167 : 0 : newField.setName( newName );
1168 : 0 : outFields.append( newField );
1169 : 0 : }
1170 : : else
1171 : : {
1172 : 0 : outFields.append( newField );
1173 : : }
1174 : 0 : usedNames.insert( newField.name() );
1175 : 0 : }
1176 : :
1177 : 0 : return outFields;
1178 : 0 : }
1179 : :
1180 : :
1181 : 0 : QList<int> QgsProcessingUtils::fieldNamesToIndices( const QStringList &fieldNames, const QgsFields &fields )
1182 : : {
1183 : 0 : QList<int> indices;
1184 : 0 : if ( !fieldNames.isEmpty() )
1185 : : {
1186 : 0 : indices.reserve( fieldNames.count() );
1187 : 0 : for ( const QString &f : fieldNames )
1188 : : {
1189 : 0 : int idx = fields.lookupField( f );
1190 : 0 : if ( idx >= 0 )
1191 : 0 : indices.append( idx );
1192 : : }
1193 : 0 : }
1194 : : else
1195 : : {
1196 : 0 : indices.reserve( fields.count() );
1197 : 0 : for ( int i = 0; i < fields.count(); ++i )
1198 : 0 : indices.append( i );
1199 : : }
1200 : 0 : return indices;
1201 : 0 : }
1202 : :
1203 : :
1204 : 0 : QgsFields QgsProcessingUtils::indicesToFields( const QList<int> &indices, const QgsFields &fields )
1205 : : {
1206 : 0 : QgsFields fieldsSubset;
1207 : 0 : for ( int i : indices )
1208 : 0 : fieldsSubset.append( fields.at( i ) );
1209 : 0 : return fieldsSubset;
1210 : 0 : }
1211 : :
1212 : 0 : QString QgsProcessingUtils::defaultVectorExtension()
1213 : : {
1214 : 0 : QgsSettings settings;
1215 : 0 : const int setting = settings.value( QStringLiteral( "Processing/Configuration/DefaultOutputVectorLayerExt" ), -1 ).toInt();
1216 : 0 : if ( setting == -1 )
1217 : 0 : return QStringLiteral( "gpkg" );
1218 : 0 : return QgsVectorFileWriter::supportedFormatExtensions().value( setting, QStringLiteral( "gpkg" ) );
1219 : 0 : }
1220 : :
1221 : 0 : QString QgsProcessingUtils::defaultRasterExtension()
1222 : : {
1223 : 0 : QgsSettings settings;
1224 : 0 : const int setting = settings.value( QStringLiteral( "Processing/Configuration/DefaultOutputRasterLayerExt" ), -1 ).toInt();
1225 : 0 : if ( setting == -1 )
1226 : 0 : return QStringLiteral( "tif" );
1227 : 0 : return QgsRasterFileWriter::supportedFormatExtensions().value( setting, QStringLiteral( "tif" ) );
1228 : 0 : }
1229 : :
1230 : : //
1231 : : // QgsProcessingFeatureSource
1232 : : //
1233 : :
1234 : 0 : QgsProcessingFeatureSource::QgsProcessingFeatureSource( QgsFeatureSource *originalSource, const QgsProcessingContext &context, bool ownsOriginalSource, long long featureLimit )
1235 : 0 : : mSource( originalSource )
1236 : 0 : , mOwnsSource( ownsOriginalSource )
1237 : 0 : , mInvalidGeometryCheck( QgsWkbTypes::geometryType( mSource->wkbType() ) == QgsWkbTypes::PointGeometry
1238 : : ? QgsFeatureRequest::GeometryNoCheck // never run geometry validity checks for point layers!
1239 : 0 : : context.invalidGeometryCheck() )
1240 : 0 : , mInvalidGeometryCallback( context.invalidGeometryCallback( originalSource ) )
1241 : 0 : , mTransformErrorCallback( context.transformErrorCallback() )
1242 : 0 : , mInvalidGeometryCallbackSkip( context.defaultInvalidGeometryCallbackForCheck( QgsFeatureRequest::GeometrySkipInvalid, originalSource ) )
1243 : 0 : , mInvalidGeometryCallbackAbort( context.defaultInvalidGeometryCallbackForCheck( QgsFeatureRequest::GeometryAbortOnInvalid, originalSource ) )
1244 : 0 : , mFeatureLimit( featureLimit )
1245 : 0 : {}
1246 : :
1247 : 0 : QgsProcessingFeatureSource::~QgsProcessingFeatureSource()
1248 : 0 : {
1249 : 0 : if ( mOwnsSource )
1250 : 0 : delete mSource;
1251 : 0 : }
1252 : :
1253 : 0 : QgsFeatureIterator QgsProcessingFeatureSource::getFeatures( const QgsFeatureRequest &request, Flags flags ) const
1254 : : {
1255 : 0 : QgsFeatureRequest req( request );
1256 : 0 : req.setTransformErrorCallback( mTransformErrorCallback );
1257 : :
1258 : 0 : if ( flags & FlagSkipGeometryValidityChecks )
1259 : 0 : req.setInvalidGeometryCheck( QgsFeatureRequest::GeometryNoCheck );
1260 : : else
1261 : : {
1262 : 0 : req.setInvalidGeometryCheck( mInvalidGeometryCheck );
1263 : 0 : req.setInvalidGeometryCallback( mInvalidGeometryCallback );
1264 : : }
1265 : :
1266 : 0 : if ( mFeatureLimit != -1 && req.limit() != -1 )
1267 : 0 : req.setLimit( std::min( static_cast< long long >( req.limit() ), mFeatureLimit ) );
1268 : 0 : else if ( mFeatureLimit != -1 )
1269 : 0 : req.setLimit( mFeatureLimit );
1270 : :
1271 : 0 : return mSource->getFeatures( req );
1272 : 0 : }
1273 : :
1274 : 0 : QgsFeatureSource::FeatureAvailability QgsProcessingFeatureSource::hasFeatures() const
1275 : : {
1276 : 0 : FeatureAvailability sourceAvailability = mSource->hasFeatures();
1277 : 0 : if ( sourceAvailability == NoFeaturesAvailable )
1278 : 0 : return NoFeaturesAvailable; // never going to be features if underlying source has no features
1279 : 0 : else if ( mInvalidGeometryCheck == QgsFeatureRequest::GeometryNoCheck )
1280 : 0 : return sourceAvailability;
1281 : : else
1282 : : // we don't know... source has features, but these may be filtered out by invalid geometry check
1283 : 0 : return FeaturesMaybeAvailable;
1284 : 0 : }
1285 : :
1286 : 0 : QgsFeatureIterator QgsProcessingFeatureSource::getFeatures( const QgsFeatureRequest &request ) const
1287 : : {
1288 : 0 : QgsFeatureRequest req( request );
1289 : 0 : req.setInvalidGeometryCheck( mInvalidGeometryCheck );
1290 : 0 : req.setInvalidGeometryCallback( mInvalidGeometryCallback );
1291 : 0 : req.setTransformErrorCallback( mTransformErrorCallback );
1292 : :
1293 : 0 : if ( mFeatureLimit != -1 && req.limit() != -1 )
1294 : 0 : req.setLimit( std::min( static_cast< long long >( req.limit() ), mFeatureLimit ) );
1295 : 0 : else if ( mFeatureLimit != -1 )
1296 : 0 : req.setLimit( mFeatureLimit );
1297 : :
1298 : 0 : return mSource->getFeatures( req );
1299 : 0 : }
1300 : :
1301 : 0 : QgsCoordinateReferenceSystem QgsProcessingFeatureSource::sourceCrs() const
1302 : : {
1303 : 0 : return mSource->sourceCrs();
1304 : : }
1305 : :
1306 : 0 : QgsFields QgsProcessingFeatureSource::fields() const
1307 : : {
1308 : 0 : return mSource->fields();
1309 : : }
1310 : :
1311 : 0 : QgsWkbTypes::Type QgsProcessingFeatureSource::wkbType() const
1312 : : {
1313 : 0 : return mSource->wkbType();
1314 : : }
1315 : :
1316 : 0 : long QgsProcessingFeatureSource::featureCount() const
1317 : : {
1318 : 0 : if ( mFeatureLimit == -1 )
1319 : 0 : return mSource->featureCount();
1320 : : else
1321 : 0 : return std::min( mFeatureLimit, static_cast< long long >( mSource->featureCount() ) );
1322 : 0 : }
1323 : :
1324 : 0 : QString QgsProcessingFeatureSource::sourceName() const
1325 : : {
1326 : 0 : return mSource->sourceName();
1327 : :
1328 : : }
1329 : :
1330 : 0 : QSet<QVariant> QgsProcessingFeatureSource::uniqueValues( int fieldIndex, int limit ) const
1331 : : {
1332 : 0 : return mSource->uniqueValues( fieldIndex, limit );
1333 : : }
1334 : :
1335 : 0 : QVariant QgsProcessingFeatureSource::minimumValue( int fieldIndex ) const
1336 : : {
1337 : 0 : return mSource->minimumValue( fieldIndex );
1338 : : }
1339 : :
1340 : 0 : QVariant QgsProcessingFeatureSource::maximumValue( int fieldIndex ) const
1341 : : {
1342 : 0 : return mSource->maximumValue( fieldIndex );
1343 : : }
1344 : :
1345 : 0 : QgsRectangle QgsProcessingFeatureSource::sourceExtent() const
1346 : : {
1347 : 0 : return mSource->sourceExtent();
1348 : : }
1349 : :
1350 : 0 : QgsFeatureIds QgsProcessingFeatureSource::allFeatureIds() const
1351 : : {
1352 : 0 : return mSource->allFeatureIds();
1353 : : }
1354 : :
1355 : 0 : QgsFeatureSource::SpatialIndexPresence QgsProcessingFeatureSource::hasSpatialIndex() const
1356 : : {
1357 : 0 : return mSource->hasSpatialIndex();
1358 : : }
1359 : :
1360 : 0 : QgsExpressionContextScope *QgsProcessingFeatureSource::createExpressionContextScope() const
1361 : : {
1362 : 0 : QgsExpressionContextScope *expressionContextScope = nullptr;
1363 : 0 : QgsExpressionContextScopeGenerator *generator = dynamic_cast<QgsExpressionContextScopeGenerator *>( mSource );
1364 : 0 : if ( generator )
1365 : : {
1366 : 0 : expressionContextScope = generator->createExpressionContextScope();
1367 : 0 : }
1368 : 0 : return expressionContextScope;
1369 : : }
1370 : :
1371 : 0 : void QgsProcessingFeatureSource::setInvalidGeometryCheck( QgsFeatureRequest::InvalidGeometryCheck method )
1372 : : {
1373 : 0 : mInvalidGeometryCheck = method;
1374 : 0 : switch ( mInvalidGeometryCheck )
1375 : : {
1376 : : case QgsFeatureRequest::GeometryNoCheck:
1377 : 0 : mInvalidGeometryCallback = nullptr;
1378 : 0 : break;
1379 : :
1380 : : case QgsFeatureRequest::GeometrySkipInvalid:
1381 : 0 : mInvalidGeometryCallback = mInvalidGeometryCallbackSkip;
1382 : 0 : break;
1383 : :
1384 : : case QgsFeatureRequest::GeometryAbortOnInvalid:
1385 : 0 : mInvalidGeometryCallback = mInvalidGeometryCallbackAbort;
1386 : 0 : break;
1387 : :
1388 : : }
1389 : 0 : }
1390 : :
1391 : :
1392 : : //
1393 : : // QgsProcessingFeatureSink
1394 : : //
1395 : 0 : QgsProcessingFeatureSink::QgsProcessingFeatureSink( QgsFeatureSink *originalSink, const QString &sinkName, QgsProcessingContext &context, bool ownsOriginalSink )
1396 : 0 : : QgsProxyFeatureSink( originalSink )
1397 : 0 : , mContext( context )
1398 : 0 : , mSinkName( sinkName )
1399 : 0 : , mOwnsSink( ownsOriginalSink )
1400 : 0 : {}
1401 : :
1402 : 0 : QgsProcessingFeatureSink::~QgsProcessingFeatureSink()
1403 : 0 : {
1404 : 0 : if ( mOwnsSink )
1405 : 0 : delete destinationSink();
1406 : 0 : }
1407 : :
1408 : 0 : bool QgsProcessingFeatureSink::addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags )
1409 : : {
1410 : 0 : bool result = QgsProxyFeatureSink::addFeature( feature, flags );
1411 : 0 : if ( !result && mContext.feedback() )
1412 : : {
1413 : 0 : const QString error = lastError();
1414 : 0 : if ( !error.isEmpty() )
1415 : 0 : mContext.feedback()->reportError( QObject::tr( "Feature could not be written to %1: %2" ).arg( mSinkName, error ) );
1416 : : else
1417 : 0 : mContext.feedback()->reportError( QObject::tr( "Feature could not be written to %1" ).arg( mSinkName ) );
1418 : 0 : }
1419 : 0 : return result;
1420 : 0 : }
1421 : :
1422 : 0 : bool QgsProcessingFeatureSink::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags )
1423 : : {
1424 : 0 : bool result = QgsProxyFeatureSink::addFeatures( features, flags );
1425 : 0 : if ( !result && mContext.feedback() )
1426 : : {
1427 : 0 : const QString error = lastError();
1428 : 0 : if ( !error.isEmpty() )
1429 : 0 : mContext.feedback()->reportError( QObject::tr( "%1 feature(s) could not be written to %2: %3" ).arg( features.count() ).arg( mSinkName, error ) );
1430 : : else
1431 : 0 : mContext.feedback()->reportError( QObject::tr( "%1 feature(s) could not be written to %2" ).arg( features.count() ).arg( mSinkName ) );
1432 : 0 : }
1433 : 0 : return result;
1434 : 0 : }
1435 : :
1436 : 0 : bool QgsProcessingFeatureSink::addFeatures( QgsFeatureIterator &iterator, QgsFeatureSink::Flags flags )
1437 : : {
1438 : 0 : bool result = QgsProxyFeatureSink::addFeatures( iterator, flags );
1439 : 0 : if ( !result && mContext.feedback() )
1440 : : {
1441 : 0 : const QString error = lastError();
1442 : 0 : if ( !error.isEmpty() )
1443 : 0 : mContext.feedback()->reportError( QObject::tr( "Features could not be written to %1: %2" ).arg( mSinkName, error ) );
1444 : : else
1445 : 0 : mContext.feedback()->reportError( QObject::tr( "Features could not be written to %1" ).arg( mSinkName ) );
1446 : 0 : }
1447 : 0 : return result;
1448 : 0 : }
|