1 /***************************************************************************
2                          testqgsprocessingalgs.cpp
3                          ---------------------
4     begin                : November 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 #include "limits"
18 
19 #include "qgstest.h"
20 #include "qgsprocessingregistry.h"
21 #include "qgsprocessingprovider.h"
22 #include "qgsprocessingutils.h"
23 #include "qgsprocessingalgorithm.h"
24 #include "qgsprocessingcontext.h"
25 #include "qgsprocessingmodelalgorithm.h"
26 #include "qgsnativealgorithms.h"
27 #include "qgsalgorithmfillnodata.h"
28 #include "qgsalgorithmlinedensity.h"
29 #include "qgsalgorithmimportphotos.h"
30 #include "qgsalgorithmtransform.h"
31 #include "qgsalgorithmkmeansclustering.h"
32 #include "qgsvectorlayer.h"
33 #include "qgscategorizedsymbolrenderer.h"
34 #include "qgssinglesymbolrenderer.h"
35 #include "qgsmultipolygon.h"
36 #include "qgsrasteranalysisutils.h"
37 #include "qgsrasteranalysisutils.cpp"
38 #include "qgsrasterfilewriter.h"
39 #include "qgsreclassifyutils.h"
40 #include "qgsalgorithmrasterlogicalop.h"
41 #include "qgsprintlayout.h"
42 #include "qgslayertree.h"
43 #include "qgslayoutmanager.h"
44 #include "qgslayoutitemmap.h"
45 #include "qgsmarkersymbollayer.h"
46 #include "qgsrulebasedrenderer.h"
47 #include "qgspallabeling.h"
48 #include "qgsrastershader.h"
49 #include "qgssinglebandpseudocolorrenderer.h"
50 #include "qgslayoutitemscalebar.h"
51 #include "annotations/qgstextannotation.h"
52 #include "qgsfontutils.h"
53 #include "annotations/qgsannotationmanager.h"
54 #include "qgsvectorlayerlabeling.h"
55 #include "qgsstyle.h"
56 #include "qgsbookmarkmanager.h"
57 #include "qgsexpressioncontextutils.h"
58 #include "qgsrenderchecker.h"
59 #include "qgsrelationmanager.h"
60 #include "qgsmeshlayer.h"
61 #include "qgsmarkersymbol.h"
62 #include "qgsfillsymbol.h"
63 #include "qgsalgorithmgpsbabeltools.h"
64 #include "qgsannotationlayer.h"
65 #include "qgsannotationmarkeritem.h"
66 
67 class TestQgsProcessingAlgs: public QObject
68 {
69     Q_OBJECT
70 
71   private:
72 
73     /**
74      * Helper function to get a feature based algorithm.
75      */
76     std::unique_ptr<QgsProcessingFeatureBasedAlgorithm> featureBasedAlg( const QString &id );
77 
78     QgsFeature runForFeature( const std::unique_ptr<QgsProcessingFeatureBasedAlgorithm> &alg, QgsFeature feature, const QString &layerType, QVariantMap parameters = QVariantMap() );
79 
80   private slots:
81     void initTestCase();// will be called before the first testfunction is executed.
82     void cleanupTestCase(); // will be called after the last testfunction was executed.
init()83     void init() {} // will be called before each testfunction is executed.
cleanup()84     void cleanup() {} // will be called after every testfunction.
85     void saveFeaturesAlg();
86     void packageAlg();
87     void rasterLayerProperties();
88     void exportToSpreadsheetXlsx();
89     void exportToSpreadsheetOds();
90     void exportToSpreadsheetOptions();
91     void renameLayerAlg();
92     void loadLayerAlg();
93     void parseGeoTags();
94     void featureFilterAlg();
95     void transformAlg();
96     void kmeansCluster();
97     void categorizeByStyle();
98     void extractBinary();
99     void exportLayersInformationAlg();
100     void createDirectory();
101     void flattenRelations();
102 
103     void polygonsToLines_data();
104     void polygonsToLines();
105 
106     void createConstantRaster_data();
107     void createConstantRaster();
108 
109     void densifyGeometries_data();
110     void densifyGeometries();
111 
112     void fillNoData_data();
113     void fillNoData();
114     void lineDensity_data();
115     void lineDensity();
116 
117     void rasterLogicOp_data();
118     void rasterLogicOp();
119     void cellStatistics_data();
120     void cellStatistics();
121     void percentileFunctions_data();
122     void percentileFunctions();
123     void percentileRaster_data();
124     void percentileRaster();
125     void percentrankFunctions_data();
126     void percentrankFunctions();
127     void percentrankByRaster_data();
128     void percentrankByRaster();
129     void percentrankByValue_data();
130     void percentrankByValue();
131     void rasterFrequencyByComparisonOperator_data();
132     void rasterFrequencyByComparisonOperator();
133     void rasterLocalPosition_data();
134     void rasterLocalPosition();
135     void roundRasterValues_data();
136     void roundRasterValues();
137 
138     void layoutMapExtent();
139 
140     void styleFromProject();
141     void combineStyles();
142 
143     void bookmarksToLayer();
144     void layerToBookmarks();
145 
146     void repairShapefile();
147     void renameField();
148 
149     void compareDatasets();
150     void shapefileEncoding();
151     void setLayerEncoding();
152 
153     void raiseException();
154     void raiseWarning();
155 
156     void randomFloatingPointDistributionRaster_data();
157     void randomFloatingPointDistributionRaster();
158     void randomIntegerDistributionRaster_data();
159     void randomIntegerDistributionRaster();
160     void randomRaster_data();
161     void randomRaster();
162 
163     void filterByLayerType();
164     void conditionalBranch();
165 
166     void saveLog();
167     void setProjectVariable();
168 
169 #ifndef QT_NO_PRINTER
170     void exportLayoutPdf();
171     void exportLayoutPng();
172     void exportAtlasLayoutPdf();
173     void exportAtlasLayoutPng();
174 #endif
175 
176     void tinMeshCreation();
177     void exportMeshVertices();
178     void exportMeshFaces();
179     void exportMeshEdges();
180     void exportMeshOnGrid();
181     void rasterizeMesh();
182     void exportMeshContours();
183     void exportMeshCrossSection();
184     void exportMeshTimeSeries();
185 
186     void fileDownloader();
187 
188     void rasterize();
189 
190     void convertGpxFeatureType();
191     void convertGpsData();
192     void downloadGpsData();
193     void uploadGpsData();
194     void transferMainAnnotationLayer();
195 
196   private:
197 
198     bool imageCheck( const QString &testName, const QString &renderedImage );
199 
200     QString mPointLayerPath;
201     QgsVectorLayer *mPointsLayer = nullptr;
202     QgsVectorLayer *mPolygonLayer = nullptr;
203 
204     void exportToSpreadsheet( const QString &outputPath );
205 };
206 
featureBasedAlg(const QString & id)207 std::unique_ptr<QgsProcessingFeatureBasedAlgorithm> TestQgsProcessingAlgs::featureBasedAlg( const QString &id )
208 {
209   return std::unique_ptr<QgsProcessingFeatureBasedAlgorithm>( static_cast<QgsProcessingFeatureBasedAlgorithm *>( QgsApplication::processingRegistry()->createAlgorithmById( id ) ) );
210 }
211 
runForFeature(const std::unique_ptr<QgsProcessingFeatureBasedAlgorithm> & alg,QgsFeature feature,const QString & layerType,QVariantMap parameters)212 QgsFeature TestQgsProcessingAlgs::runForFeature( const std::unique_ptr< QgsProcessingFeatureBasedAlgorithm > &alg, QgsFeature feature, const QString &layerType, QVariantMap parameters )
213 {
214   Q_ASSERT( alg.get() );
215   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
216   QgsProject p;
217   context->setProject( &p );
218 
219   QgsProcessingFeedback feedback;
220   context->setFeedback( &feedback );
221 
222   std::unique_ptr<QgsVectorLayer> inputLayer( std::make_unique<QgsVectorLayer>( layerType, QStringLiteral( "layer" ), QStringLiteral( "memory" ) ) );
223   inputLayer->dataProvider()->addFeature( feature );
224 
225   parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue<QgsMapLayer *>( inputLayer.get() ) );
226 
227   parameters.insert( QStringLiteral( "OUTPUT" ), QStringLiteral( "memory:" ) );
228 
229   bool ok = false;
230   const auto res = alg->run( parameters, *context, &feedback, &ok );
231   QgsFeature result;
232 
233   std::unique_ptr<QgsVectorLayer> outputLayer( qobject_cast< QgsVectorLayer * >( context->getMapLayer( res.value( QStringLiteral( "OUTPUT" ) ).toString() ) ) );
234   outputLayer->getFeatures().nextFeature( result );
235 
236   return result;
237 }
238 
initTestCase()239 void TestQgsProcessingAlgs::initTestCase()
240 {
241   QgsApplication::init();
242   QgsApplication::initQgis();
243 
244   // Set up the QgsSettings environment
245   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
246   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
247   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
248 
249   QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) );
250 
251   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
252 
253   const QString pointsFileName = dataDir + "/points.shp";
254   const QFileInfo pointFileInfo( pointsFileName );
255   mPointLayerPath = pointFileInfo.filePath();
256   mPointsLayer = new QgsVectorLayer( mPointLayerPath,
257                                      QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
258   QVERIFY( mPointsLayer->isValid() );
259   // Register the layer with the registry
260   QgsProject::instance()->addMapLayers(
261     QList<QgsMapLayer *>() << mPointsLayer );
262 
263   //
264   //create a poly layer that will be used in all tests...
265   //
266   const QString polysFileName = dataDir + "/polys.shp";
267   const QFileInfo polyFileInfo( polysFileName );
268   mPolygonLayer = new QgsVectorLayer( polyFileInfo.filePath(),
269                                       QStringLiteral( "polygons" ), QStringLiteral( "ogr" ) );
270   // Register the layer with the registry
271   QgsProject::instance()->addMapLayers(
272     QList<QgsMapLayer *>() << mPolygonLayer );
273   QVERIFY( mPolygonLayer->isValid() );
274 
275   //add a mesh layer
276   const QString uri( dataDir + "/mesh/quad_and_triangle.2dm" );
277   const QString meshLayerName = QStringLiteral( "mesh layer" );
278   QgsMeshLayer *meshLayer = new QgsMeshLayer( uri, meshLayerName, QStringLiteral( "mdal" ) );
279   // Register the layer with the registry
280   QgsProject::instance()->addMapLayer( meshLayer );
281   QVERIFY( meshLayer->isValid() );
282   meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_vertex_scalar.dat" );
283   meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_vertex_vector.dat" );
284   meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_els_face_scalar.dat" );
285   meshLayer->addDatasets( dataDir + "/mesh/quad_and_triangle_els_face_vector.dat" );
286   QCOMPARE( meshLayer->datasetGroupCount(), 5 );
287 
288   //add a 1D mesh layer
289   const QString uri1d( dataDir + "/mesh/lines.2dm" );
290   const QString meshLayer1dName = QStringLiteral( "mesh layer 1D" );
291   QgsMeshLayer *meshLayer1d = new QgsMeshLayer( uri1d, meshLayer1dName, QStringLiteral( "mdal" ) );
292   // Register the layer with the registry
293   QgsProject::instance()->addMapLayer( meshLayer1d );
294   QVERIFY( meshLayer1d->isValid() );
295   meshLayer1d->addDatasets( dataDir + "/mesh/lines_els_scalar.dat" );
296   meshLayer1d->addDatasets( dataDir + "/mesh/lines_els_vector.dat" );
297   QCOMPARE( meshLayer1d->datasetGroupCount(), 3 );
298 }
299 
cleanupTestCase()300 void TestQgsProcessingAlgs::cleanupTestCase()
301 {
302   QgsApplication::exitQgis();
303 }
304 
pkgAlg(const QStringList & layers,const QString & outputGpkg,bool overwrite,bool selectedFeaturesOnly,bool saveMetadata,bool * ok)305 QVariantMap pkgAlg( const QStringList &layers, const QString &outputGpkg, bool overwrite, bool selectedFeaturesOnly, bool saveMetadata, bool *ok )
306 {
307   const QgsProcessingAlgorithm *package( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:package" ) ) );
308 
309   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
310   context->setProject( QgsProject::instance() );
311 
312   QgsProcessingFeedback feedback;
313 
314   QVariantMap parameters;
315   parameters.insert( QStringLiteral( "LAYERS" ), layers );
316   parameters.insert( QStringLiteral( "OUTPUT" ), outputGpkg );
317   parameters.insert( QStringLiteral( "OVERWRITE" ), overwrite );
318   parameters.insert( QStringLiteral( "SELECTED_FEATURES_ONLY" ), selectedFeaturesOnly );
319   parameters.insert( QStringLiteral( "SAVE_METADATA" ), saveMetadata );
320   return package->run( parameters, *context, &feedback, ok );
321 }
322 
saveFeaturesAlg()323 void TestQgsProcessingAlgs::saveFeaturesAlg()
324 {
325   const QString outputGeoJson = QDir::tempPath() + "/savefeatures_alg.geojson";
326   const QString layerName = QStringLiteral( "custom_layer" );
327 
328   if ( QFile::exists( outputGeoJson ) )
329     QFile::remove( outputGeoJson );
330 
331   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
332 
333   QVariantMap parameters;
334   parameters.insert( QStringLiteral( "INPUT" ), QString( dataDir + "/points.shp" ) );
335   parameters.insert( QStringLiteral( "LAYER_NAME" ), layerName );
336   parameters.insert( QStringLiteral( "LAYER_OPTIONS" ), QStringLiteral( "COORDINATE_PRECISION=1" ) );
337   parameters.insert( QStringLiteral( "OUTPUT" ), outputGeoJson );
338 
339   const QgsProcessingAlgorithm *saveFeatures( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:savefeatures" ) ) );
340   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
341   context->setProject( QgsProject::instance() );
342 
343   QgsProcessingFeedback feedback;
344   bool ok = false;
345   const QVariantMap outputs = saveFeatures->run( parameters, *context, &feedback, &ok );
346   QCOMPARE( ok, true );
347   QCOMPARE( outputs.value( QStringLiteral( "OUTPUT" ) ).toString(), QStringLiteral( "%1|layername=%2" ).arg( outputGeoJson, layerName ) );
348   QCOMPARE( outputs.value( QStringLiteral( "FILE_PATH" ) ).toString(), outputGeoJson );
349   QCOMPARE( outputs.value( QStringLiteral( "LAYER_NAME" ) ).toString(), layerName );
350 
351   std::unique_ptr< QgsVectorLayer > savedLayer = std::make_unique< QgsVectorLayer >( outputs.value( QStringLiteral( "OUTPUT" ) ).toString(), "points", "ogr" );
352   QVERIFY( savedLayer->isValid() );
353   QCOMPARE( savedLayer->getFeature( 1 ).geometry().asPoint().x(), -83.3 );
354 }
355 
exportLayersInformationAlg()356 void TestQgsProcessingAlgs::exportLayersInformationAlg()
357 {
358   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
359   const QString gpkgFileName = dataDir + "/humanbeings.gpkg";
360   const QFileInfo gpkgFileInfo( gpkgFileName );
361   const std::unique_ptr< QgsVectorLayer > gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgFileInfo.filePath() + QStringLiteral( "|layername=person" ),
362       QStringLiteral( "person" ), QStringLiteral( "ogr" ) );
363 
364   const QgsProcessingAlgorithm *exportLayersInformation( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:exportlayersinformation" ) ) );
365 
366   QgsProcessingContext context;;
367   context.setProject( QgsProject::instance() );
368   QgsProcessingFeedback feedback;
369 
370   QVariantMap parameters;
371   parameters.insert( QStringLiteral( "LAYERS" ), QVariantList() << QVariant::fromValue( gpkgLayer.get() ) );
372   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
373   bool ok = false;
374   QVariantMap results = exportLayersInformation->run( parameters, context, &feedback, &ok );
375   QVERIFY( ok );
376   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
377 
378   QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( context.getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
379   QVERIFY( vlayer );
380   QCOMPARE( vlayer->featureCount(), 1L );
381   QCOMPARE( vlayer->crs().authid(), QStringLiteral( "EPSG:2056" ) );
382 
383   parameters.insert( QStringLiteral( "LAYERS" ), QVariantList() << QVariant::fromValue( gpkgLayer.get() ) << QVariant::fromValue( mPolygonLayer ) );
384   ok = false;
385   results = exportLayersInformation->run( parameters, context, &feedback, &ok );
386   QVERIFY( ok );
387   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
388 
389   vlayer = qobject_cast< QgsVectorLayer * >( context.getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
390   QVERIFY( vlayer );
391   QCOMPARE( vlayer->featureCount(), 2L );
392   // when layers have mixed CRSes, the algorithm uses WGS84
393   QCOMPARE( vlayer->crs().authid(), QStringLiteral( "EPSG:4326" ) );
394 }
395 
packageAlg()396 void TestQgsProcessingAlgs::packageAlg()
397 {
398   const QString outputGpkg = QDir::tempPath() + "/package_alg.gpkg";
399 
400   if ( QFile::exists( outputGpkg ) )
401     QFile::remove( outputGpkg );
402 
403   const QVariantMap parameters;
404   const QStringList layers = QStringList() << mPointsLayer->id() << mPolygonLayer->id();
405   bool ok = false;
406   const QVariantMap results = pkgAlg( layers, outputGpkg, true, false, false, &ok );
407   QVERIFY( ok );
408 
409   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
410   std::unique_ptr< QgsVectorLayer > pointLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=points", "points", "ogr" );
411   QVERIFY( pointLayer->isValid() );
412   QCOMPARE( pointLayer->wkbType(), mPointsLayer->wkbType() );
413   QCOMPARE( pointLayer->featureCount(), mPointsLayer->featureCount() );
414   pointLayer.reset();
415   std::unique_ptr< QgsVectorLayer > polygonLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=polygons", "polygons", "ogr" );
416   QVERIFY( polygonLayer->isValid() );
417   QCOMPARE( polygonLayer->wkbType(), mPolygonLayer->wkbType() );
418   QCOMPARE( polygonLayer->featureCount(), mPolygonLayer->featureCount() );
419   polygonLayer.reset();
420 
421   std::unique_ptr<QgsVectorLayer> rectangles = std::make_unique<QgsVectorLayer>( QStringLiteral( TEST_DATA_DIR ) + "/rectangles.shp",
422       QStringLiteral( "rectangles" ), QStringLiteral( "ogr" ) );
423   QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << rectangles.get() );
424   QgsLayerMetadata metadata;
425   metadata.setFees( QStringLiteral( "lots of dogecoin" ) );
426   rectangles->setMetadata( metadata );
427 
428   // Test adding an additional layer (overwrite disabled)
429   const QVariantMap results2 = pkgAlg( QStringList() << rectangles->id(), outputGpkg, false, false, false, &ok );
430   QVERIFY( ok );
431 
432   QVERIFY( !results2.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
433   std::unique_ptr< QgsVectorLayer > rectanglesPackagedLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=rectangles", "points", "ogr" );
434   QVERIFY( rectanglesPackagedLayer->isValid() );
435   QCOMPARE( rectanglesPackagedLayer->wkbType(), rectanglesPackagedLayer->wkbType() );
436   QCOMPARE( rectanglesPackagedLayer->featureCount(), rectangles->featureCount() );
437   rectanglesPackagedLayer.reset();
438 
439   pointLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=points", "points", "ogr" );
440   QVERIFY( pointLayer->isValid() );
441   pointLayer.reset();
442 
443   // And finally, test with overwrite enabled
444   QVariantMap results3 = pkgAlg( QStringList() << rectangles->id(), outputGpkg, true, false, false, &ok );
445   QVERIFY( ok );
446 
447   QVERIFY( !results2.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
448   rectanglesPackagedLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=rectangles", "points", "ogr" );
449   QVERIFY( rectanglesPackagedLayer->isValid() );
450   QCOMPARE( rectanglesPackagedLayer->wkbType(), rectanglesPackagedLayer->wkbType() );
451   QCOMPARE( rectanglesPackagedLayer->featureCount(), rectangles->featureCount() );
452 
453   pointLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=points", "points", "ogr" );
454   QVERIFY( !pointLayer->isValid() ); // It's gone -- the gpkg was recreated with a single layer
455 
456   QCOMPARE( rectanglesPackagedLayer->metadata().fees(), QString() );
457   rectanglesPackagedLayer.reset();
458 
459   // save layer metadata
460   results3 = pkgAlg( QStringList() << rectangles->id(), outputGpkg, true, false, true, &ok );
461   QVERIFY( ok );
462   rectanglesPackagedLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=rectangles", "points", "ogr" );
463   QVERIFY( rectanglesPackagedLayer->isValid() );
464   QCOMPARE( rectanglesPackagedLayer->wkbType(), rectanglesPackagedLayer->wkbType() );
465   QCOMPARE( rectanglesPackagedLayer->featureCount(), rectangles->featureCount() );
466   QCOMPARE( rectanglesPackagedLayer->metadata().fees(), QStringLiteral( "lots of dogecoin" ) );
467 
468   // Test saving of selected features only
469   mPolygonLayer->selectByIds( QgsFeatureIds() << 1 << 2 << 3 );
470   const QVariantMap results4 = pkgAlg( QStringList() << mPolygonLayer->id(), outputGpkg, false, true, false, &ok );
471   QVERIFY( ok );
472 
473   QVERIFY( !results4.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
474   std::unique_ptr< QgsVectorLayer > selectedPolygonsPackagedLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=polygons", "polygons", "ogr" );
475   QVERIFY( selectedPolygonsPackagedLayer->isValid() );
476   QCOMPARE( selectedPolygonsPackagedLayer->wkbType(), mPolygonLayer->wkbType() );
477   QCOMPARE( selectedPolygonsPackagedLayer->featureCount(), 3 );
478   selectedPolygonsPackagedLayer.reset();
479 
480   mPolygonLayer->removeSelection();
481   const QVariantMap results5 = pkgAlg( QStringList() << mPolygonLayer->id(), outputGpkg, false, true, false, &ok );
482   QVERIFY( ok );
483 
484   QVERIFY( !results5.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
485   selectedPolygonsPackagedLayer = std::make_unique< QgsVectorLayer >( outputGpkg + "|layername=polygons", "polygons", "ogr" );
486   QVERIFY( selectedPolygonsPackagedLayer->isValid() );
487   QCOMPARE( selectedPolygonsPackagedLayer->wkbType(), mPolygonLayer->wkbType() );
488   QCOMPARE( selectedPolygonsPackagedLayer->featureCount(), 10 ); // With enabled SELECTED_FEATURES_ONLY all features should be saved when there is no selection
489 }
490 
rasterLayerProperties()491 void TestQgsProcessingAlgs::rasterLayerProperties()
492 {
493   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:rasterlayerproperties" ) ) );
494 
495   const QString myDataPath( TEST_DATA_DIR ); //defined in CMakeLists.txt
496 
497   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
498 
499   QVariantMap parameters;
500 
501   parameters.insert( QStringLiteral( "INPUT" ), QVariant( myDataPath + "/landsat.tif" ) );
502 
503   //run alg...
504   bool ok = false;
505   QgsProcessingFeedback feedback;
506   QVariantMap results;
507 
508   results = alg->run( parameters, *context, &feedback, &ok );
509   QVERIFY( ok );
510 
511   QCOMPARE( results.value( QStringLiteral( "X_MIN" ) ).toDouble(), 781662.375 );
512   QCOMPARE( results.value( QStringLiteral( "X_MAX" ) ).toDouble(), 793062.375 );
513   QCOMPARE( results.value( QStringLiteral( "Y_MIN" ) ).toDouble(), 3339523.125 );
514   QCOMPARE( results.value( QStringLiteral( "Y_MAX" ) ).toDouble(), 3350923.125 );
515   QCOMPARE( results.value( QStringLiteral( "EXTENT" ) ).toString(), QStringLiteral( "781662.3750000000000000,3339523.1250000000000000 : 793062.3750000000000000,3350923.1250000000000000" ) );
516   QCOMPARE( results.value( QStringLiteral( "PIXEL_WIDTH" ) ).toDouble(), 57.0 );
517   QCOMPARE( results.value( QStringLiteral( "PIXEL_HEIGHT" ) ).toDouble(), 57.0 );
518   QCOMPARE( results.value( QStringLiteral( "CRS_AUTHID" ) ).toString(), QStringLiteral( "EPSG:32633" ) );
519   QCOMPARE( results.value( QStringLiteral( "WIDTH_IN_PIXELS" ) ).toInt(), 200 );
520   QCOMPARE( results.value( QStringLiteral( "HEIGHT_IN_PIXELS" ) ).toInt(), 200 );
521   QCOMPARE( results.value( QStringLiteral( "BAND_COUNT" ) ).toInt(), 9 );
522 
523   parameters.insert( QStringLiteral( "INPUT" ), QVariant( myDataPath + "/raster/valueRas3_float64.asc" ) );
524   parameters.insert( QStringLiteral( "BAND" ), 1 );
525   ok = false;
526   results = alg->run( parameters, *context, &feedback, &ok );
527   QVERIFY( ok );
528 
529   QCOMPARE( results.value( QStringLiteral( "X_MIN" ) ).toDouble(), 0.0 );
530   QCOMPARE( results.value( QStringLiteral( "X_MAX" ) ).toDouble(), 4.0 );
531   QCOMPARE( results.value( QStringLiteral( "Y_MIN" ) ).toDouble(), 0.0 );
532   QCOMPARE( results.value( QStringLiteral( "Y_MAX" ) ).toDouble(), 4.0 );
533   QCOMPARE( results.value( QStringLiteral( "EXTENT" ) ).toString(), QStringLiteral( "0.0000000000000000,0.0000000000000000 : 4.0000000000000000,4.0000000000000000" ) );
534   QCOMPARE( results.value( QStringLiteral( "PIXEL_WIDTH" ) ).toDouble(), 1.0 );
535   QCOMPARE( results.value( QStringLiteral( "PIXEL_HEIGHT" ) ).toDouble(), 1.0 );
536   QCOMPARE( results.value( QStringLiteral( "CRS_AUTHID" ) ).toString(), QStringLiteral( "" ) );
537   QCOMPARE( results.value( QStringLiteral( "WIDTH_IN_PIXELS" ) ).toInt(), 4 );
538   QCOMPARE( results.value( QStringLiteral( "HEIGHT_IN_PIXELS" ) ).toInt(), 4 );
539   QCOMPARE( results.value( QStringLiteral( "BAND_COUNT" ) ).toInt(), 1 );
540   QCOMPARE( results.value( QStringLiteral( "HAS_NODATA_VALUE" ) ).toInt(), 1 );
541   QCOMPARE( results.value( QStringLiteral( "NODATA_VALUE" ) ).toInt(), -9999 );
542 }
543 
exportToSpreadsheetXlsx()544 void TestQgsProcessingAlgs::exportToSpreadsheetXlsx()
545 {
546   if ( QgsTest::isCIRun() )
547   {
548     QSKIP( "XLSX driver not working on Travis" );
549   }
550 
551   const QString outputPath = QDir::tempPath() + "/spreadsheet.xlsx";
552   exportToSpreadsheet( outputPath );
553 }
554 
exportToSpreadsheetOds()555 void TestQgsProcessingAlgs::exportToSpreadsheetOds()
556 {
557   const QString outputPath = QDir::tempPath() + "/spreadsheet.ods";
558   exportToSpreadsheet( outputPath );
559 }
560 
exportToSpreadsheetOptions()561 void TestQgsProcessingAlgs::exportToSpreadsheetOptions()
562 {
563   const QString outputPath = QDir::tempPath() + "/spreadsheet.ods";
564   if ( QFile::exists( outputPath ) )
565     QFile::remove( outputPath );
566 
567   QVariantMap parameters;
568   const QStringList layers = QStringList() << mPointsLayer->id();
569   bool ok = false;
570 
571   mPointsLayer->setFieldAlias( 1, QStringLiteral( "my heading" ) );
572 
573   const QgsProcessingAlgorithm *alg( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:exporttospreadsheet" ) ) );
574 
575   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
576   context->setProject( QgsProject::instance() );
577 
578   QgsProcessingFeedback feedback;
579 
580   parameters.insert( QStringLiteral( "LAYERS" ), layers );
581   parameters.insert( QStringLiteral( "OUTPUT" ), outputPath );
582   parameters.insert( QStringLiteral( "OVERWRITE" ), true );
583   parameters.insert( QStringLiteral( "USE_ALIAS" ), false );
584   QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
585   QVERIFY( ok );
586 
587   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
588   std::unique_ptr< QgsVectorLayer > pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
589   QCOMPARE( pointLayer->fields().at( 0 ).name(), QStringLiteral( "Class" ) );
590   QCOMPARE( pointLayer->fields().at( 1 ).name(), QStringLiteral( "Heading" ) );
591   QCOMPARE( pointLayer->fields().at( 2 ).name(), QStringLiteral( "Importance" ) );
592   QCOMPARE( pointLayer->fields().at( 3 ).name(), QStringLiteral( "Pilots" ) );
593   QCOMPARE( pointLayer->fields().at( 4 ).name(), QStringLiteral( "Cabin Crew" ) );
594 
595   pointLayer.reset();
596 
597 
598   mPointsLayer->setEditorWidgetSetup( 2, QgsEditorWidgetSetup( QStringLiteral( "ValueMap" ),
599   QVariantMap{{"map", QVariantMap{{"High", "1"},
600         {"Medium", "10"},
601         {"Low", "20"},
602         {"VLow", "3"},
603         {"VHigh", "4"}}
604     }} ) );
605 
606   parameters.insert( QStringLiteral( "USE_ALIAS" ), true );
607   parameters.insert( QStringLiteral( "FORMATTED_VALUES" ), false );
608   results = alg->run( parameters, *context, &feedback, &ok );
609   QVERIFY( ok );
610 
611   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
612   pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
613   QCOMPARE( pointLayer->fields().at( 0 ).name(), QStringLiteral( "Class" ) );
614   QCOMPARE( pointLayer->fields().at( 1 ).name(), QStringLiteral( "my heading" ) );
615   QCOMPARE( pointLayer->fields().at( 2 ).name(), QStringLiteral( "Importance" ) );
616   QCOMPARE( pointLayer->fields().at( 3 ).name(), QStringLiteral( "Pilots" ) );
617   QCOMPARE( pointLayer->fields().at( 4 ).name(), QStringLiteral( "Cabin Crew" ) );
618 
619   QSet< QString > values;
620   QgsFeature f;
621   QgsFeatureIterator it = pointLayer->getFeatures();
622   while ( it.nextFeature( f ) )
623     values.insert( f.attribute( QStringLiteral( "Importance" ) ).toString() );
624 
625   QCOMPARE( values.size(), 5 );
626   QVERIFY( values.contains( "1" ) );
627   QVERIFY( values.contains( "3" ) );
628   QVERIFY( values.contains( "4" ) );
629   QVERIFY( values.contains( "10" ) );
630   QVERIFY( values.contains( "20" ) );
631 
632   parameters.insert( QStringLiteral( "FORMATTED_VALUES" ), true );
633   results = alg->run( parameters, *context, &feedback, &ok );
634   QVERIFY( ok );
635 
636   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
637   pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
638   values.clear();
639   it = pointLayer->getFeatures();
640   while ( it.nextFeature( f ) )
641     values.insert( f.attribute( QStringLiteral( "Importance" ) ).toString() );
642 
643   QCOMPARE( values.size(), 5 );
644   QVERIFY( values.contains( "High" ) );
645   QVERIFY( values.contains( "Medium" ) );
646   QVERIFY( values.contains( "Low" ) );
647   QVERIFY( values.contains( "VLow" ) );
648   QVERIFY( values.contains( "VHigh" ) );
649 }
650 
exportToSpreadsheet(const QString & outputPath)651 void TestQgsProcessingAlgs::exportToSpreadsheet( const QString &outputPath )
652 {
653   if ( QFile::exists( outputPath ) )
654     QFile::remove( outputPath );
655 
656   QVariantMap parameters;
657   const QStringList layers = QStringList() << mPointsLayer->id() << mPolygonLayer->id();
658   bool ok = false;
659 
660   const QgsProcessingAlgorithm *alg( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:exporttospreadsheet" ) ) );
661 
662   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
663   context->setProject( QgsProject::instance() );
664 
665   QgsProcessingFeedback feedback;
666 
667   parameters.insert( QStringLiteral( "LAYERS" ), layers );
668   parameters.insert( QStringLiteral( "OUTPUT" ), outputPath );
669   parameters.insert( QStringLiteral( "OVERWRITE" ), false );
670   const QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
671   QVERIFY( ok );
672 
673   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
674   std::unique_ptr< QgsVectorLayer > pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
675   QVERIFY( pointLayer->isValid() );
676   QCOMPARE( pointLayer->featureCount(), mPointsLayer->featureCount() );
677   pointLayer.reset();
678   std::unique_ptr< QgsVectorLayer > polygonLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=polygons", "polygons", "ogr" );
679   QVERIFY( polygonLayer->isValid() );
680   QCOMPARE( polygonLayer->featureCount(), mPolygonLayer->featureCount() );
681   polygonLayer.reset();
682 
683   std::unique_ptr<QgsVectorLayer> rectangles = std::make_unique<QgsVectorLayer>( QStringLiteral( TEST_DATA_DIR ) + "/rectangles.shp",
684       QStringLiteral( "rectangles" ), QStringLiteral( "ogr" ) );
685   QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << rectangles.get() );
686 
687   // Test adding an additional layer (overwrite disabled)
688   parameters.insert( QStringLiteral( "LAYERS" ), QStringList() << rectangles->id() );
689   const QVariantMap results2 = alg->run( parameters, *context, &feedback, &ok );
690   QVERIFY( ok );
691 
692   QVERIFY( !results2.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
693   std::unique_ptr< QgsVectorLayer > rectanglesPackagedLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=rectangles", "points", "ogr" );
694   QVERIFY( rectanglesPackagedLayer->isValid() );
695   QCOMPARE( rectanglesPackagedLayer->featureCount(), rectangles->featureCount() );
696   rectanglesPackagedLayer.reset();
697 
698   pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
699   QVERIFY( pointLayer->isValid() );
700   pointLayer.reset();
701 
702   // And finally, test with overwrite enabled
703   parameters.insert( QStringLiteral( "OVERWRITE" ), true );
704   const QVariantMap results3 = alg->run( parameters, *context, &feedback, &ok );
705   QVERIFY( ok );
706 
707   QVERIFY( !results3.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
708   rectanglesPackagedLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=rectangles", "points", "ogr" );
709   QVERIFY( rectanglesPackagedLayer->isValid() );
710   QCOMPARE( rectanglesPackagedLayer->featureCount(), rectangles->featureCount() );
711 
712   pointLayer = std::make_unique< QgsVectorLayer >( outputPath + "|layername=points", "points", "ogr" );
713   QVERIFY( !pointLayer->isValid() ); // It's gone -- the xlsx was recreated with a single layer
714 }
715 
716 
renameLayerAlg()717 void TestQgsProcessingAlgs::renameLayerAlg()
718 {
719   const QgsProcessingAlgorithm *package( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:renamelayer" ) ) );
720   QVERIFY( package );
721 
722   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
723   context->setProject( QgsProject::instance() );
724 
725   QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:real" ), QStringLiteral( "layer" ), QStringLiteral( "memory" ) );
726   QVERIFY( layer->isValid() );
727   QgsProject::instance()->addMapLayer( layer );
728 
729   QgsProcessingFeedback feedback;
730 
731   QVariantMap parameters;
732 
733   // bad layer
734   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "bad layer" ) );
735   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "new name" ) );
736   bool ok = false;
737   ( void )package->run( parameters, *context, &feedback, &ok );
738   QVERIFY( !ok );
739   QCOMPARE( layer->name(), QStringLiteral( "layer" ) );
740 
741   //invalid name
742   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "layer" ) );
743   parameters.insert( QStringLiteral( "NAME" ), QString() );
744   ok = false;
745   ( void )package->run( parameters, *context, &feedback, &ok );
746   QVERIFY( !ok );
747   QCOMPARE( layer->name(), QStringLiteral( "layer" ) );
748 
749   //good params
750   parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( layer ) );
751   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "new name" ) );
752   ok = false;
753   QVariantMap results = package->run( parameters, *context, &feedback, &ok );
754   QVERIFY( ok );
755   QCOMPARE( layer->name(), QStringLiteral( "new name" ) );
756   QCOMPARE( results.value( "OUTPUT" ), QVariant::fromValue( layer ) );
757 
758   // with input layer name as parameter
759   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "new name" ) );
760   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "new name2" ) );
761   ok = false;
762   results = package->run( parameters, *context, &feedback, &ok );
763   QVERIFY( ok );
764   QCOMPARE( layer->name(), QStringLiteral( "new name2" ) );
765   // result should use new name as value
766   QCOMPARE( results.value( "OUTPUT" ).toString(), QStringLiteral( "new name2" ) );
767 }
768 
loadLayerAlg()769 void TestQgsProcessingAlgs::loadLayerAlg()
770 {
771   const QgsProcessingAlgorithm *package( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:loadlayer" ) ) );
772   QVERIFY( package );
773 
774   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
775   QgsProject p;
776   context->setProject( &p );
777 
778   QgsProcessingFeedback feedback;
779 
780   QVariantMap parameters;
781 
782   // bad layer
783   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "bad layer" ) );
784   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "new name" ) );
785   bool ok = false;
786   ( void )package->run( parameters, *context, &feedback, &ok );
787   QVERIFY( !ok );
788   QVERIFY( context->layersToLoadOnCompletion().empty() );
789 
790   //invalid name
791   parameters.insert( QStringLiteral( "INPUT" ), mPointLayerPath );
792   parameters.insert( QStringLiteral( "NAME" ), QString() );
793   ok = false;
794   ( void )package->run( parameters, *context, &feedback, &ok );
795   QVERIFY( !ok );
796   QVERIFY( context->layersToLoadOnCompletion().empty() );
797 
798   //good params
799   parameters.insert( QStringLiteral( "INPUT" ), mPointLayerPath );
800   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "my layer" ) );
801   ok = false;
802   const QVariantMap results = package->run( parameters, *context, &feedback, &ok );
803   QVERIFY( ok );
804   QVERIFY( !context->layersToLoadOnCompletion().empty() );
805   const QString layerId = context->layersToLoadOnCompletion().keys().at( 0 );
806   QCOMPARE( results.value( QStringLiteral( "OUTPUT" ) ).toString(), layerId );
807   QVERIFY( !layerId.isEmpty() );
808   QVERIFY( context->temporaryLayerStore()->mapLayer( layerId ) );
809   QCOMPARE( context->layersToLoadOnCompletion().value( layerId, QgsProcessingContext::LayerDetails( QString(), nullptr, QString() ) ).name, QStringLiteral( "my layer" ) );
810   QCOMPARE( context->layersToLoadOnCompletion().value( layerId, QgsProcessingContext::LayerDetails( QString(), nullptr, QString() ) ).project, &p );
811   QCOMPARE( context->layersToLoadOnCompletion().value( layerId, QgsProcessingContext::LayerDetails( QString(), nullptr, QString() ) ).outputName, QStringLiteral( "my layer" ) );
812 }
813 
parseGeoTags()814 void TestQgsProcessingAlgs::parseGeoTags()
815 {
816   // parseCoord
817   QVERIFY( !QgsImportPhotosAlgorithm::parseCoord( "" ).isValid() );
818   QVERIFY( !QgsImportPhotosAlgorithm::parseCoord( "x" ).isValid() );
819   QVERIFY( !QgsImportPhotosAlgorithm::parseCoord( "1 2 3" ).isValid() );
820   QGSCOMPARENEAR( QgsImportPhotosAlgorithm::parseCoord( "(36) (13) (15.21)" ).toDouble(), 36.220892, 0.000001 );
821   QGSCOMPARENEAR( QgsImportPhotosAlgorithm::parseCoord( "(3) (1) (5.21)" ).toDouble(), 3.018114, 0.000001 );
822   QGSCOMPARENEAR( QgsImportPhotosAlgorithm::parseCoord( "(149) (7) (54.76)" ).toDouble(), 149.131878, 0.000001 );
823   QVERIFY( !QgsImportPhotosAlgorithm::parseCoord( "(149) (7) (c)" ).isValid() );
824   QVERIFY( !QgsImportPhotosAlgorithm::parseCoord( "(149) (7) ()" ).isValid() );
825 
826   // parseMetadataValue
827   QCOMPARE( QgsImportPhotosAlgorithm::parseMetadataValue( "abc" ).toString(), QStringLiteral( "abc" ) );
828   QCOMPARE( QgsImportPhotosAlgorithm::parseMetadataValue( "(abc)" ).toString(), QStringLiteral( "(abc)" ) );
829   QCOMPARE( QgsImportPhotosAlgorithm::parseMetadataValue( "abc (123)" ).toString(), QStringLiteral( "abc (123)" ) );
830   QCOMPARE( QgsImportPhotosAlgorithm::parseMetadataValue( "(123)" ).toDouble(), 123.0 );
831 
832   // parseMetadataList
833   QVariantMap md = QgsImportPhotosAlgorithm::parseMetadataList( QStringList() << "EXIF_Contrast=(1)"
834                    << "EXIF_ExposureTime=(0.008339)"
835                    << "EXIF_Model=Pixel"
836                    << "EXIF_GPSLatitude=(36) (13) (15.21)"
837                    << "EXIF_GPSLongitude=(149) (7) (54.76)" );
838   QCOMPARE( md.count(), 5 );
839   QCOMPARE( md.value( "EXIF_Contrast" ).toInt(), 1 );
840   QCOMPARE( md.value( "EXIF_ExposureTime" ).toDouble(), 0.008339 );
841   QCOMPARE( md.value( "EXIF_Model" ).toString(), QStringLiteral( "Pixel" ) );
842   QGSCOMPARENEAR( md.value( "EXIF_GPSLatitude" ).toDouble(), 36.220892, 0.000001 );
843   QGSCOMPARENEAR( md.value( "EXIF_GPSLongitude" ).toDouble(), 149.131878, 0.000001 );
844 
845   // test extractGeoTagFromMetadata
846   md = QVariantMap();
847   QgsPointXY point;
848   QVERIFY( !QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
849   md.insert( QStringLiteral( "EXIF_GPSLongitude" ), 142.0 );
850   QVERIFY( !QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
851   md.insert( QStringLiteral( "EXIF_GPSLatitude" ), 37.0 );
852   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
853   QCOMPARE( point.x(), 142.0 );
854   QCOMPARE( point.y(), 37.0 );
855   md.insert( QStringLiteral( "EXIF_GPSLongitude" ), QStringLiteral( "x" ) );
856   QVERIFY( !QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
857   md.insert( QStringLiteral( "EXIF_GPSLongitude" ), 142.0 );
858   md.insert( QStringLiteral( "EXIF_GPSLatitude" ), QStringLiteral( "x" ) );
859   QVERIFY( !QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
860   md.insert( QStringLiteral( "EXIF_GPSLatitude" ), 37.0 );
861   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QStringLiteral( "E" ) );
862   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
863   QCOMPARE( point.x(), 142.0 );
864   QCOMPARE( point.y(), 37.0 );
865   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QStringLiteral( "W" ) );
866   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
867   QCOMPARE( point.x(), -142.0 );
868   QCOMPARE( point.y(), 37.0 );
869   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QStringLiteral( "w" ) );
870   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
871   QCOMPARE( point.x(), -142.0 );
872   QCOMPARE( point.y(), 37.0 );
873   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QStringLiteral( "...W" ) ); // apparently any string ENDING in W is acceptable
874   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
875   QCOMPARE( point.x(), -142.0 );
876   QCOMPARE( point.y(), 37.0 );
877   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QString() );
878   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
879   QCOMPARE( point.x(), 142.0 );
880   QCOMPARE( point.y(), 37.0 );
881   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), -1 );
882   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
883   QCOMPARE( point.x(), -142.0 );
884   QCOMPARE( point.y(), 37.0 );
885   md.insert( QStringLiteral( "EXIF_GPSLongitudeRef" ), QString() );
886   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), QStringLiteral( "N" ) );
887   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
888   QCOMPARE( point.x(), 142.0 );
889   QCOMPARE( point.y(), 37.0 );
890   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), QStringLiteral( "S" ) );
891   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
892   QCOMPARE( point.x(), 142.0 );
893   QCOMPARE( point.y(), -37.0 );
894   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), QStringLiteral( "s" ) );
895   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
896   QCOMPARE( point.x(), 142.0 );
897   QCOMPARE( point.y(), -37.0 );
898   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), QStringLiteral( "...S" ) ); // any string ending in s is acceptable
899   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
900   QCOMPARE( point.x(), 142.0 );
901   QCOMPARE( point.y(), -37.0 );
902   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), QString() );
903   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
904   QCOMPARE( point.x(), 142.0 );
905   QCOMPARE( point.y(), 37.0 );
906   md.insert( QStringLiteral( "EXIF_GPSLatitudeRef" ), -1 );
907   QVERIFY( QgsImportPhotosAlgorithm::extractGeoTagFromMetadata( md, point ) );
908   QCOMPARE( point.x(), 142.0 );
909   QCOMPARE( point.y(), -37.0 );
910 
911   // extractAltitudeFromMetadata
912   QVERIFY( !QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).isValid() );
913   md.insert( QStringLiteral( "EXIF_GPSAltitude" ), 10.5 );
914   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), 10.5 );
915   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), QStringLiteral( "0" ) );
916   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), 10.5 );
917   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), QStringLiteral( "00" ) );
918   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), 10.5 );
919   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), QStringLiteral( "1" ) );
920   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), -10.5 );
921   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), QStringLiteral( "01" ) );
922   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), -10.5 );
923   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), 1 );
924   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), 10.5 );
925   md.insert( QStringLiteral( "EXIF_GPSAltitudeRef" ), -1 );
926   QCOMPARE( QgsImportPhotosAlgorithm::extractAltitudeFromMetadata( md ).toDouble(), -10.5 );
927 
928   // extractDirectionFromMetadata
929   QVERIFY( !QgsImportPhotosAlgorithm::extractDirectionFromMetadata( md ).isValid() );
930   md.insert( QStringLiteral( "EXIF_GPSImgDirection" ), 15 );
931   QCOMPARE( QgsImportPhotosAlgorithm::extractDirectionFromMetadata( md ).toDouble(), 15.0 );
932 
933   // extractOrientationFromMetadata
934   QVERIFY( !QgsImportPhotosAlgorithm::extractOrientationFromMetadata( md ).isValid() );
935   md.insert( QStringLiteral( "EXIF_Orientation" ), 3 );
936   QCOMPARE( QgsImportPhotosAlgorithm::extractOrientationFromMetadata( md ).toInt(), 180 );
937 
938   // extractTimestampFromMetadata
939   QVERIFY( !QgsImportPhotosAlgorithm::extractTimestampFromMetadata( md ).isValid() );
940   md.insert( QStringLiteral( "EXIF_DateTimeOriginal" ), QStringLiteral( "xx" ) );
941   QVERIFY( !QgsImportPhotosAlgorithm::extractTimestampFromMetadata( md ).isValid() );
942   md.insert( QStringLiteral( "EXIF_DateTimeOriginal" ), QStringLiteral( "2017:12:27 19:20:52" ) );
943   QCOMPARE( QgsImportPhotosAlgorithm::extractTimestampFromMetadata( md ).toDateTime(), QDateTime( QDate( 2017, 12, 27 ), QTime( 19, 20, 52 ) ) );
944   md.remove( QStringLiteral( "EXIF_DateTimeOriginal" ) );
945   md.insert( QStringLiteral( "EXIF_DateTimeDigitized" ), QStringLiteral( "2017:12:27 19:20:52" ) );
946   QCOMPARE( QgsImportPhotosAlgorithm::extractTimestampFromMetadata( md ).toDateTime(), QDateTime( QDate( 2017, 12, 27 ), QTime( 19, 20, 52 ) ) );
947   md.remove( QStringLiteral( "EXIF_DateTimeDigitized" ) );
948   md.insert( QStringLiteral( "EXIF_DateTime" ), QStringLiteral( "2017:12:27 19:20:52" ) );
949   QCOMPARE( QgsImportPhotosAlgorithm::extractTimestampFromMetadata( md ).toDateTime(), QDateTime( QDate( 2017, 12, 27 ), QTime( 19, 20, 52 ) ) );
950 
951 }
952 
featureFilterAlg()953 void TestQgsProcessingAlgs::featureFilterAlg()
954 {
955   const QgsProcessingAlgorithm *filterAlgTemplate = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:filter" ) );
956 
957   Q_ASSERT( filterAlgTemplate->outputDefinitions().isEmpty() );
958 
959   QVariantList outputs;
960   QVariantMap output1;
961   output1.insert( QStringLiteral( "name" ), QStringLiteral( "test" ) );
962   output1.insert( QStringLiteral( "expression" ), QStringLiteral( "TRUE" ) );
963   output1.insert( QStringLiteral( "isModelOutput" ), true );
964 
965   outputs.append( output1 );
966 
967   QVariantMap config1;
968   config1.insert( QStringLiteral( "outputs" ), outputs );
969 
970   std::unique_ptr<QgsProcessingAlgorithm> filterAlg( filterAlgTemplate->create( config1 ) );
971 
972   QCOMPARE( filterAlg->outputDefinitions().size(), 1 );
973 
974   auto outputDef = filterAlg->outputDefinition( QStringLiteral( "OUTPUT_test" ) );
975   QCOMPARE( outputDef->type(), QStringLiteral( "outputVector" ) );
976 
977   auto outputParamDef = filterAlg->parameterDefinition( "OUTPUT_test" );
978   Q_ASSERT( outputParamDef->flags() & QgsProcessingParameterDefinition::FlagIsModelOutput );
979   Q_ASSERT( outputParamDef->flags() & QgsProcessingParameterDefinition::FlagHidden );
980 
981   QVariantMap output2;
982   output2.insert( QStringLiteral( "name" ), QStringLiteral( "nonmodeloutput" ) );
983   output2.insert( QStringLiteral( "expression" ), QStringLiteral( "TRUE" ) );
984   output2.insert( QStringLiteral( "isModelOutput" ), false );
985 
986   outputs.append( output2 );
987 
988   QVariantMap config2;
989   config2.insert( QStringLiteral( "outputs" ), outputs );
990 
991   std::unique_ptr<QgsProcessingAlgorithm> filterAlg2( filterAlgTemplate->create( config2 ) );
992 
993   QCOMPARE( filterAlg2->outputDefinitions().size(), 2 );
994 
995   auto outputDef2 = filterAlg2->outputDefinition( QStringLiteral( "OUTPUT_nonmodeloutput" ) );
996   QCOMPARE( outputDef2->type(), QStringLiteral( "outputVector" ) );
997 
998   auto outputParamDef2 = filterAlg2->parameterDefinition( "OUTPUT_nonmodeloutput" );
999   Q_ASSERT( !outputParamDef2->flags().testFlag( QgsProcessingParameterDefinition::FlagIsModelOutput ) );
1000   Q_ASSERT( outputParamDef2->flags() & QgsProcessingParameterDefinition::FlagHidden );
1001 }
1002 
transformAlg()1003 void TestQgsProcessingAlgs::transformAlg()
1004 {
1005   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:reprojectlayer" ) ) );
1006   QVERIFY( alg != nullptr );
1007 
1008   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1009   QgsProject p;
1010   context->setProject( &p );
1011 
1012   QgsProcessingFeedback feedback;
1013 
1014   QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?crs=EPSG:4326field=col1:integer" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
1015   QVERIFY( layer->isValid() );
1016   QgsFeature f;
1017   // add a point with a bad geometry - this should result in a transform exception!
1018   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -96215069, 41.673559 ) ) );
1019   QVERIFY( layer->dataProvider()->addFeature( f ) );
1020   p.addMapLayer( layer );
1021 
1022   QVariantMap parameters;
1023   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "test" ) );
1024   parameters.insert( QStringLiteral( "OUTPUT" ), QStringLiteral( "memory:" ) );
1025   parameters.insert( QStringLiteral( "TARGET_CRS" ), QStringLiteral( "EPSG:2163" ) );
1026   bool ok = false;
1027   const QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
1028   Q_UNUSED( results )
1029   QVERIFY( ok );
1030 }
1031 
kmeansCluster()1032 void TestQgsProcessingAlgs::kmeansCluster()
1033 {
1034   // make some features
1035   std::vector< QgsKMeansClusteringAlgorithm::Feature > features;
1036   std::vector< QgsPointXY > centers( 2 );
1037 
1038   // no features, no crash
1039   int k = 2;
1040   QgsKMeansClusteringAlgorithm::initClusters( features, centers, k, nullptr );
1041   QgsKMeansClusteringAlgorithm::calculateKMeans( features, centers, k, nullptr );
1042 
1043   // features < clusters
1044   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 1, 5 ) ) );
1045   QgsKMeansClusteringAlgorithm::initClusters( features, centers, k, nullptr );
1046   QgsKMeansClusteringAlgorithm::calculateKMeans( features, centers, k, nullptr );
1047   QCOMPARE( features[ 0 ].cluster, 0 );
1048 
1049   // features == clusters
1050   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 11, 5 ) ) );
1051   QgsKMeansClusteringAlgorithm::initClusters( features, centers, k, nullptr );
1052   QgsKMeansClusteringAlgorithm::calculateKMeans( features, centers, k, nullptr );
1053   QCOMPARE( features[ 0 ].cluster, 1 );
1054   QCOMPARE( features[ 1 ].cluster, 0 );
1055 
1056   // features > clusters
1057   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 13, 3 ) ) );
1058   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 13, 13 ) ) );
1059   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 23, 6 ) ) );
1060   k = 2;
1061   QgsKMeansClusteringAlgorithm::initClusters( features, centers, k, nullptr );
1062   QgsKMeansClusteringAlgorithm::calculateKMeans( features, centers, k, nullptr );
1063   QCOMPARE( features[ 0 ].cluster, 1 );
1064   QCOMPARE( features[ 1 ].cluster, 1 );
1065   QCOMPARE( features[ 2 ].cluster, 0 );
1066   QCOMPARE( features[ 3 ].cluster, 0 );
1067   QCOMPARE( features[ 4 ].cluster, 0 );
1068 
1069   // repeat above, with 3 clusters
1070   k = 3;
1071   centers.resize( 3 );
1072   QgsKMeansClusteringAlgorithm::initClusters( features, centers, k, nullptr );
1073   QgsKMeansClusteringAlgorithm::calculateKMeans( features, centers, k, nullptr );
1074   QCOMPARE( features[ 0 ].cluster, 1 );
1075   QCOMPARE( features[ 1 ].cluster, 2 );
1076   QCOMPARE( features[ 2 ].cluster, 2 );
1077   QCOMPARE( features[ 3 ].cluster, 2 );
1078   QCOMPARE( features[ 4 ].cluster, 0 );
1079 
1080   // with identical points
1081   features.clear();
1082   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 1, 5 ) ) );
1083   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 1, 5 ) ) );
1084   features.emplace_back( QgsKMeansClusteringAlgorithm::Feature( QgsPointXY( 1, 5 ) ) );
1085   QCOMPARE( features[ 0 ].cluster, -1 );
1086   QCOMPARE( features[ 1 ].cluster, -1 );
1087   QCOMPARE( features[ 2 ].cluster, -1 );
1088 }
1089 
categorizeByStyle()1090 void TestQgsProcessingAlgs::categorizeByStyle()
1091 {
1092   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:categorizeusingstyle" ) ) );
1093   QVERIFY( alg != nullptr );
1094 
1095   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1096   QgsProject p;
1097   context->setProject( &p );
1098 
1099   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
1100   const QString styleFileName = dataDir + "/categorized.xml";
1101 
1102 
1103   QgsProcessingFeedback feedback;
1104 
1105   QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?crs=EPSG:4326&field=col1:string" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
1106   QVERIFY( layer->isValid() );
1107   QgsFeature f, f2, f3;
1108   f.setAttributes( QgsAttributes() << "a" );
1109   QVERIFY( layer->dataProvider()->addFeature( f ) );
1110   f2.setAttributes( QgsAttributes() << "b" );
1111   QVERIFY( layer->dataProvider()->addFeature( f2 ) );
1112   f3.setAttributes( QgsAttributes() << "c " );
1113   QVERIFY( layer->dataProvider()->addFeature( f3 ) );
1114   p.addMapLayer( layer );
1115 
1116   QVariantMap parameters;
1117   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "test" ) );
1118   parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "col1" ) );
1119   parameters.insert( QStringLiteral( "STYLE" ), styleFileName );
1120   parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), true );
1121   parameters.insert( QStringLiteral( "TOLERANT" ), false );
1122   parameters.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QStringLiteral( "memory:" ) );
1123   parameters.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QStringLiteral( "memory:" ) );
1124 
1125   bool ok = false;
1126   QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
1127   QVERIFY( ok );
1128   context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
1129   QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
1130   QVERIFY( catRenderer );
1131 
1132   auto allValues = []( QgsVectorLayer * layer )->QStringList
1133   {
1134     QStringList all;
1135     QgsFeature f;
1136     QgsFeatureIterator it = layer->getFeatures();
1137     while ( it.nextFeature( f ) )
1138     {
1139       all.append( f.attribute( 0 ).toString() );
1140     }
1141     return all;
1142   };
1143   QgsVectorLayer *nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
1144   QCOMPARE( allValues( nonMatchingCats ), QStringList() << "b" << "c " );
1145   QgsVectorLayer *nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
1146   QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << " ----c/- " << "B " );
1147 
1148   QCOMPARE( catRenderer->categories().count(), 3 );
1149   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
1150   QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name() != QLatin1String( "#00ff00" ) );
1151   QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name() != QLatin1String( "#0000ff" ) );
1152   // reset renderer
1153   layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
1154 
1155   // case insensitive
1156   parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), false );
1157   ok = false;
1158   results = alg->run( parameters, *context, &feedback, &ok );
1159   QVERIFY( ok );
1160   context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
1161   catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
1162   QVERIFY( catRenderer );
1163 
1164   nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
1165   QCOMPARE( allValues( nonMatchingCats ), QStringList() << "c " );
1166   nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
1167   QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << " ----c/- " );
1168 
1169   QCOMPARE( catRenderer->categories().count(), 3 );
1170   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
1171   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name(), QStringLiteral( "#00ff00" ) );
1172   QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name() != QLatin1String( "#0000ff" ) );
1173   // reset renderer
1174   layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
1175 
1176   // tolerant
1177   parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), true );
1178   parameters.insert( QStringLiteral( "TOLERANT" ), true );
1179 
1180   ok = false;
1181   results = alg->run( parameters, *context, &feedback, &ok );
1182   QVERIFY( ok );
1183   context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
1184   catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
1185   QVERIFY( catRenderer );
1186 
1187   nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
1188   QCOMPARE( allValues( nonMatchingCats ), QStringList() << "b" );
1189   nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
1190   QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << "B " );
1191 
1192   QCOMPARE( catRenderer->categories().count(), 3 );
1193   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
1194   QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name() != QLatin1String( "#00ff00" ) );
1195   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name(), QStringLiteral( "#0000ff" ) );
1196   // reset renderer
1197   layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
1198 
1199   // no optional sinks
1200   parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), false );
1201   parameters.remove( QStringLiteral( "NON_MATCHING_CATEGORIES" ) );
1202   parameters.remove( QStringLiteral( "NON_MATCHING_SYMBOLS" ) );
1203   ok = false;
1204   results = alg->run( parameters, *context, &feedback, &ok );
1205   QVERIFY( ok );
1206   context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
1207   catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
1208   QVERIFY( catRenderer );
1209 
1210   QVERIFY( !context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
1211   QVERIFY( !context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
1212 
1213   QCOMPARE( catRenderer->categories().count(), 3 );
1214   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
1215   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name(), QStringLiteral( "#00ff00" ) );
1216   QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name(), QStringLiteral( "#0000ff" ) );
1217 }
1218 
extractBinary()1219 void TestQgsProcessingAlgs::extractBinary()
1220 {
1221   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:extractbinary" ) ) );
1222   QVERIFY( alg != nullptr );
1223 
1224   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1225   QgsProject p;
1226   context->setProject( &p );
1227 
1228   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
1229   const QString source = dataDir + QStringLiteral( "/attachments.gdb|layername=points__ATTACH" );
1230 
1231   QVariantMap parameters;
1232   parameters.insert( QStringLiteral( "INPUT" ), source );
1233   parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "DATA" ) );
1234   parameters.insert( QStringLiteral( "FILENAME" ), QStringLiteral( "'test' || \"ATTACHMENTID\" || '.jpg'" ) );
1235   const QString folder = QDir::tempPath();
1236   parameters.insert( QStringLiteral( "FOLDER" ), folder );
1237 
1238   bool ok = false;
1239   QgsProcessingFeedback feedback;
1240   const QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
1241   QVERIFY( ok );
1242 
1243   QCOMPARE( results.count(), 1 );
1244   QCOMPARE( results.value( QStringLiteral( "FOLDER" ) ).toString(), folder );
1245 
1246   QFile file( folder + "/test1.jpg" );
1247   QVERIFY( file.open( QIODevice::ReadOnly ) );
1248   QByteArray d = file.readAll();
1249   QCOMPARE( QString( QCryptographicHash::hash( d, QCryptographicHash::Md5 ).toHex() ), QStringLiteral( "ef3dbc530cc39a545832a6c82aac57b6" ) );
1250 
1251   QFile file2( folder + "/test2.jpg" );
1252   QVERIFY( file2.open( QIODevice::ReadOnly ) );
1253   d = file2.readAll();
1254   QCOMPARE( QString( QCryptographicHash::hash( d, QCryptographicHash::Md5 ).toHex() ), QStringLiteral( "4b952b80e4288ca5111be2f6dd5d6809" ) );
1255 }
1256 
createDirectory()1257 void TestQgsProcessingAlgs::createDirectory()
1258 {
1259   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:createdirectory" ) ) );
1260   QVERIFY( alg != nullptr );
1261 
1262   // first make a path to an existing file
1263   QString outputPath = QDir::tempPath() + "/test.txt";
1264   if ( QFile::exists( outputPath ) )
1265     QFile::remove( outputPath );
1266   QFile file( outputPath );
1267   file.open( QIODevice::ReadWrite );
1268   file.close();
1269 
1270   QVariantMap parameters;
1271   parameters.insert( QStringLiteral( "PATH" ), outputPath );
1272 
1273   bool ok = false;
1274   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1275   QgsProcessingFeedback feedback;
1276   QVariantMap results;
1277   results = alg->run( parameters, *context, &feedback, &ok );
1278   // path is an existing file
1279   QVERIFY( !ok );
1280 
1281   outputPath = QDir::tempPath() + "/createdir/test/test2";
1282   QDir().rmdir( QDir::tempPath() + "/createdir/test/test2" );
1283   QDir().rmdir( QDir::tempPath() + "/createdir/test" );
1284   QDir().rmdir( QDir::tempPath() + "/createdir" );
1285 
1286   parameters.insert( QStringLiteral( "PATH" ), outputPath );
1287   results = alg->run( parameters, *context, &feedback, &ok );
1288   QVERIFY( ok );
1289 
1290   QVERIFY( QFile::exists( outputPath ) );
1291   QVERIFY( QFileInfo( outputPath ).isDir() );
1292 
1293   // run a second time -- should be OK, no exception raised
1294   results = alg->run( parameters, *context, &feedback, &ok );
1295   QVERIFY( ok );
1296 
1297   QVERIFY( QFile::exists( outputPath ) );
1298   QVERIFY( QFileInfo( outputPath ).isDir() );
1299 }
1300 
flattenRelations()1301 void TestQgsProcessingAlgs::flattenRelations()
1302 {
1303   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:flattenrelationships" ) ) );
1304   QVERIFY( alg != nullptr );
1305 
1306   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1307   QgsProject p;
1308   context->setProject( &p );
1309 
1310   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
1311 
1312   QgsProcessingFeedback feedback;
1313 
1314   QgsVectorLayer *parent = new QgsVectorLayer( dataDir + QStringLiteral( "/points_relations.shp" ), QStringLiteral( "parent" ) );
1315   QgsVectorLayer *child = new QgsVectorLayer( dataDir + QStringLiteral( "/points.shp" ), QStringLiteral( "child" ) );
1316   QVERIFY( parent->isValid() );
1317   QVERIFY( child->isValid() );
1318   p.addMapLayer( parent );
1319   p.addMapLayer( child );
1320 
1321   QVariantMap parameters;
1322   parameters.insert( QStringLiteral( "INPUT" ), parent->id() );
1323   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
1324 
1325   bool ok = false;
1326   QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
1327   // no relations!
1328   QVERIFY( !ok );
1329 
1330   // create relationship
1331   const QgsRelationContext relationContext( &p );
1332   QgsRelation relation( relationContext );
1333   relation.setId( QStringLiteral( "rel" ) );
1334   relation.setName( QStringLiteral( "my relation" ) );
1335   relation.setReferencedLayer( parent->id() );
1336   relation.setReferencingLayer( child->id() );
1337   relation.addFieldPair( QStringLiteral( "class" ), QStringLiteral( "class" ) );
1338   QVERIFY( relation.isValid() );
1339   p.relationManager()->addRelation( relation );
1340 
1341   results = alg->run( parameters, *context, &feedback, &ok );
1342   // one relation - should be ok!
1343   QVERIFY( ok );
1344 
1345   QgsVectorLayer *outputLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
1346   QVERIFY( outputLayer );
1347   QCOMPARE( outputLayer->fields().count(), 8 );
1348   QCOMPARE( outputLayer->fields().at( 0 ).name(), QStringLiteral( "Class" ) );
1349   QCOMPARE( outputLayer->fields().at( 1 ).name(), QStringLiteral( "id" ) );
1350   QCOMPARE( outputLayer->fields().at( 2 ).name(), QStringLiteral( "Class_2" ) );
1351   QCOMPARE( outputLayer->fields().at( 3 ).name(), QStringLiteral( "Heading" ) );
1352   QCOMPARE( outputLayer->fields().at( 4 ).name(), QStringLiteral( "Importance" ) );
1353   QCOMPARE( outputLayer->fields().at( 5 ).name(), QStringLiteral( "Pilots" ) );
1354   QCOMPARE( outputLayer->fields().at( 6 ).name(), QStringLiteral( "Cabin Crew" ) );
1355   QCOMPARE( outputLayer->fields().at( 7 ).name(), QStringLiteral( "Staff" ) );
1356 
1357   QCOMPARE( outputLayer->featureCount(), 17L );
1358 
1359   QSet< QgsAttributes > res;
1360   QgsFeature f;
1361   QgsFeatureIterator it = outputLayer->getFeatures();
1362   while ( it.nextFeature( f ) )
1363   {
1364     QgsAttributes created = f.attributes();
1365     created << f.geometry().asWkt( 0 );
1366     res.insert( created );
1367   }
1368 
1369   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "B52" ) << 3 << QStringLiteral( "B52" ) << 0LL << 10.0 << 2 << 1 << 3 << QStringLiteral( "Point (-103 23)" ) ) );
1370   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "B52" ) << 3 << QStringLiteral( "B52" ) << 12LL << 10.0 << 1 << 1 << 2 << QStringLiteral( "Point (-103 23)" ) ) );
1371   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "B52" ) << 3 << QStringLiteral( "B52" ) << 34LL << 10.0 << 2 << 1 << 3 << QStringLiteral( "Point (-103 23)" ) ) );
1372   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "B52" ) << 3 << QStringLiteral( "B52" ) << 80LL << 10.0 << 2 << 1 << 3 << QStringLiteral( "Point (-103 23)" ) ) );
1373   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 90LL << 3.0 << 2 << 0 << 2 << QStringLiteral( "Point (-117 37)" ) ) );
1374   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 85LL << 3.0 << 1 << 1 << 2 << QStringLiteral( "Point (-117 37)" ) ) );
1375   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 95LL << 3.0 << 1 << 1 << 2 << QStringLiteral( "Point (-117 37)" ) ) );
1376   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 90LL << 3.0 << 1 << 0 << 1 << QStringLiteral( "Point (-117 37)" ) ) );
1377   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 160LL << 4.0 << 2 << 0 << 2 << QStringLiteral( "Point (-117 37)" ) ) );
1378   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 180LL << 3.0 << 1 << 0 << 1 << QStringLiteral( "Point (-117 37)" ) ) );
1379   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 140LL << 10.0 << 1 << 1 << 2 << QStringLiteral( "Point (-117 37)" ) ) );
1380   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Jet" ) << 1 << QStringLiteral( "Jet" ) << 100LL << 20.0 << 3 << 0 << 3 << QStringLiteral( "Point (-117 37)" ) ) );
1381   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Biplane" ) << 2 << QStringLiteral( "Biplane" ) << 0LL << 1.0 << 3 << 3 << 6 << QStringLiteral( "Point (-83 34)" ) ) );
1382   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Biplane" ) << 2 << QStringLiteral( "Biplane" ) << 340LL << 1.0 << 3 << 3 << 6 << QStringLiteral( "Point (-83 34)" ) ) );
1383   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Biplane" ) << 2 << QStringLiteral( "Biplane" ) << 300LL << 1.0 << 3 << 2 << 5 << QStringLiteral( "Point (-83 34)" ) ) );
1384   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Biplane" ) << 2 << QStringLiteral( "Biplane" ) << 270LL << 1.0 << 3 << 4 << 7 << QStringLiteral( "Point (-83 34)" ) ) );
1385   QVERIFY( res.contains( QgsAttributes() << QStringLiteral( "Biplane" ) << 2 << QStringLiteral( "Biplane" ) << 240LL << 1.0 << 3 << 2 << 5 << QStringLiteral( "Point (-83 34)" ) ) );
1386 
1387   QgsRelation relation2( relationContext );
1388   relation2.setId( QStringLiteral( "rel2" ) );
1389   relation2.setName( QStringLiteral( "my relation2" ) );
1390   relation2.setReferencedLayer( parent->id() );
1391   relation2.setReferencingLayer( child->id() );
1392   relation2.addFieldPair( QStringLiteral( "class" ), QStringLiteral( "class" ) );
1393   QVERIFY( relation2.isValid() );
1394   p.relationManager()->addRelation( relation2 );
1395   results = alg->run( parameters, *context, &feedback, &ok );
1396   // two relations - should not run
1397   QVERIFY( !ok );
1398 }
1399 
1400 
polygonsToLines_data()1401 void TestQgsProcessingAlgs::polygonsToLines_data()
1402 {
1403   QTest::addColumn<QgsGeometry>( "sourceGeometry" );
1404   QTest::addColumn<QgsGeometry>( "expectedGeometry" );
1405 
1406   QTest::newRow( "Simple Polygon" )
1407       << QgsGeometry::fromWkt( "Polygon((1 1, 2 2, 1 3, 1 1))" )
1408       << QgsGeometry::fromWkt( "MultiLineString ((1 1, 2 2, 1 3, 1 1))" );
1409 
1410   const QgsGeometry geomNoRing( std::make_unique<QgsMultiPolygon>() );
1411 
1412   QTest::newRow( "Polygon without exterior ring" )
1413       << geomNoRing
1414       << QgsGeometry::fromWkt( "MultiLineString ()" );
1415 
1416   QTest::newRow( "MultiPolygon" )
1417       << QgsGeometry::fromWkt( "MultiPolygon(((1 1, 2 2, 1 3, 1 1)), ((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 3 6, 6 6, 6 3, 3 3)))" )
1418       << QgsGeometry::fromWkt( "MultiLineString ((1 1, 2 2, 1 3, 1 1),(0 0, 0 10, 10 10, 10 0, 0 0),(3 3, 3 6, 6 6, 6 3, 3 3))" );
1419 
1420   QTest::newRow( "Polygon with inner ring" )
1421       << QgsGeometry::fromWkt( "Polygon((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 3 6, 6 6, 6 3, 3 3))" )
1422       << QgsGeometry::fromWkt( "MultiLineString ((0 0, 0 10, 10 10, 10 0, 0 0),(3 3, 3 6, 6 6, 6 3, 3 3))" );
1423 }
1424 
1425 
polygonsToLines()1426 void TestQgsProcessingAlgs::polygonsToLines()
1427 {
1428   QFETCH( QgsGeometry, sourceGeometry );
1429   QFETCH( QgsGeometry, expectedGeometry );
1430 
1431   const std::unique_ptr< QgsProcessingFeatureBasedAlgorithm > alg( featureBasedAlg( "native:polygonstolines" ) );
1432 
1433   QgsFeature feature;
1434   feature.setGeometry( sourceGeometry );
1435 
1436   const QgsFeature result = runForFeature( alg, feature, QStringLiteral( "Polygon" ) );
1437 
1438   QVERIFY2( result.geometry().equals( expectedGeometry ), QStringLiteral( "Result: %1, Expected: %2" ).arg( result.geometry().asWkt(), expectedGeometry.asWkt() ).toUtf8().constData() );
1439 }
1440 
Q_DECLARE_METATYPE(Qgis::DataType)1441 Q_DECLARE_METATYPE( Qgis::DataType )
1442 void TestQgsProcessingAlgs::createConstantRaster_data()
1443 {
1444   QTest::addColumn<QString>( "inputExtent" );
1445   QTest::addColumn<QString>( "expectedRaster" );
1446   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
1447   QTest::addColumn<QString>( "crs" );
1448   QTest::addColumn<double>( "pixelSize" );
1449   QTest::addColumn<double>( "constantValue" );
1450   QTest::addColumn<int>( "typeId" );
1451 
1452   /*
1453    * Testcase 1
1454    *
1455    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1456    * crs = EPSG:4326
1457    * pixelSize = 1.0
1458    * constantValue = 12
1459    * Byte Raster Layer
1460    *
1461    */
1462   QTest::newRow( "testcase 1" )
1463       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1464       << QStringLiteral( "/createConstantRaster_testcase1.tif" )
1465       << Qgis::DataType::Byte
1466       << "EPSG:4326"
1467       << 1.0
1468       << 12.0
1469       << 0;
1470 
1471   /*
1472    * Testcase 2
1473    *
1474    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1475    * crs = EPSG:4326
1476    * pixelSize = 1.0
1477    * constantValue = -1
1478    * Byte Raster Layer
1479    *
1480    */
1481   QTest::newRow( "testcase 2" )
1482       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1483       << QStringLiteral( "" )
1484       << Qgis::DataType::Byte
1485       << "EPSG:4326"
1486       << 1.0
1487       << -1.0 //fails --> value too small for byte
1488       << 0;
1489 
1490 
1491   /*
1492    * Testcase 3
1493    *
1494    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1495    * crs = EPSG:4326
1496    * pixelSize = 1.0
1497    * constantValue = -1
1498    * Byte Raster Layer
1499    *
1500    */
1501   QTest::newRow( "testcase 3" )
1502       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1503       << QStringLiteral( "" )
1504       << Qgis::DataType::Byte
1505       << "EPSG:4326"
1506       << 1.0
1507       << 256.0 //fails --> value too big for byte
1508       << 0;
1509 
1510   /*
1511    * Testcase 4
1512    *
1513    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1514    * crs = EPSG:4326
1515    * pixelSize = 1.0
1516    * constantValue = 12
1517    * Int16 Raster Layer
1518    *
1519    */
1520   QTest::newRow( "testcase 4" )
1521       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1522       << QStringLiteral( "/createConstantRaster_testcase4.tif" )
1523       << Qgis::DataType::Int16
1524       << "EPSG:4326"
1525       << 1.0
1526       << 12.0
1527       << 1;
1528 
1529   /*
1530    * Testcase 5
1531    *
1532    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1533    * crs = EPSG:4326
1534    * pixelSize = 1.0
1535    * constantValue = 12
1536    * Int16 Raster Layer
1537    *
1538    */
1539   QTest::newRow( "testcase 5" )
1540       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1541       << QStringLiteral( "" )
1542       << Qgis::DataType::Int16
1543       << "EPSG:4326"
1544       << 1.0
1545       << -32769.0
1546       << 1;
1547 
1548   /*
1549    * Testcase 6
1550    *
1551    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1552    * crs = EPSG:4326
1553    * pixelSize = 1.0
1554    * constantValue = 12
1555    * Int16 Raster Layer
1556    *
1557    */
1558   QTest::newRow( "testcase 6" )
1559       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1560       << QStringLiteral( "" )
1561       << Qgis::DataType::Int16
1562       << "EPSG:4326"
1563       << 1.0
1564       << 32769.0
1565       << 1;
1566 
1567   /*
1568    * Testcase 7
1569    *
1570    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1571    * crs = EPSG:4326
1572    * pixelSize = 1.0
1573    * constantValue = 12
1574    * UInt16 Raster Layer
1575    *
1576    */
1577   QTest::newRow( "testcase 7" )
1578       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1579       << QStringLiteral( "/createConstantRaster_testcase7.tif" )
1580       << Qgis::DataType::UInt16
1581       << "EPSG:4326"
1582       << 1.0
1583       << 12.0
1584       << 2;
1585 
1586   /*
1587    * Testcase 8
1588    *
1589    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1590    * crs = EPSG:4326
1591    * pixelSize = 1.0
1592    * constantValue = 12
1593    * UInt16 Raster Layer
1594    *
1595    */
1596   QTest::newRow( "testcase 8" )
1597       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1598       << QStringLiteral( "" )
1599       << Qgis::DataType::UInt16
1600       << "EPSG:4326"
1601       << 1.0
1602       << -1.0
1603       << 2;
1604 
1605   /*
1606    * Testcase 9
1607    *
1608    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1609    * crs = EPSG:4326
1610    * pixelSize = 1.0
1611    * constantValue = 12
1612    * UInt16 Raster Layer
1613    *
1614    */
1615   QTest::newRow( "testcase 9" )
1616       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1617       << QStringLiteral( "" )
1618       << Qgis::DataType::UInt16
1619       << "EPSG:4326"
1620       << 1.0
1621       << 65536.0
1622       << 2;
1623 
1624   /*
1625    * Testcase 10
1626    *
1627    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1628    * crs = EPSG:4326
1629    * pixelSize = 1.0
1630    * constantValue = 12
1631    * UInt16 Raster Layer
1632    *
1633    */
1634   QTest::newRow( "testcase 10" )
1635       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1636       << QStringLiteral( "/createConstantRaster_testcase10.tif" )
1637       << Qgis::DataType::Int32
1638       << "EPSG:4326"
1639       << 1.0
1640       << 12.0
1641       << 3;
1642 
1643   /*
1644    * Testcase 10
1645    *
1646    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1647    * crs = EPSG:4326
1648    * pixelSize = 1.0
1649    * constantValue = 12
1650    * Int32 Raster Layer
1651    *
1652    */
1653   QTest::newRow( "testcase 10" )
1654       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1655       << QStringLiteral( "/createConstantRaster_testcase10.tif" )
1656       << Qgis::DataType::Int32
1657       << "EPSG:4326"
1658       << 1.0
1659       << 12.0
1660       << 3;
1661 
1662   /*
1663    * Testcase 11
1664    *
1665    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1666    * crs = EPSG:4326
1667    * pixelSize = 1.0
1668    * constantValue = -2147483649.0
1669    * Int32 Raster Layer
1670    *
1671    */
1672   QTest::newRow( "testcase 11" )
1673       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1674       << QStringLiteral( "" )
1675       << Qgis::DataType::Int32
1676       << "EPSG:4326"
1677       << 1.0
1678       << -2147483649.0
1679       << 3;
1680 
1681   /*
1682    * Testcase 12
1683    *
1684    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1685    * crs = EPSG:4326
1686    * pixelSize = 1.0
1687    * constantValue = 2147483649.0
1688    * Int32 Raster Layer
1689    *
1690    */
1691   QTest::newRow( "testcase 12" )
1692       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1693       << QStringLiteral( "" )
1694       << Qgis::DataType::Int32
1695       << "EPSG:4326"
1696       << 1.0
1697       << 2147483649.0
1698       << 3;
1699 
1700   /*
1701    * Testcase 13
1702    *
1703    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1704    * crs = EPSG:4326
1705    * pixelSize = 1.0
1706    * constantValue = 12.0
1707    * UInt32 Raster Layer
1708    *
1709    */
1710   QTest::newRow( "testcase 13" )
1711       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1712       << QStringLiteral( "/createConstantRaster_testcase13.tif" )
1713       << Qgis::DataType::UInt32
1714       << "EPSG:4326"
1715       << 1.0
1716       << 12.0
1717       << 4;
1718 
1719   /*
1720    * Testcase 14
1721    *
1722    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1723    * crs = EPSG:4326
1724    * pixelSize = 1.0
1725    * constantValue = 4294967296.0
1726    * UInt32 Raster Layer
1727    *
1728    */
1729   QTest::newRow( "testcase 14" )
1730       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1731       << QStringLiteral( "" )
1732       << Qgis::DataType::UInt32
1733       << "EPSG:4326"
1734       << 1.0
1735       << 4294967296.0
1736       << 4;
1737 
1738   /*
1739    * Testcase 15
1740    *
1741    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1742    * crs = EPSG:4326
1743    * pixelSize = 1.0
1744    * constantValue = -1.0
1745    * UInt32 Raster Layer
1746    *
1747    */
1748   QTest::newRow( "testcase 14" )
1749       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1750       << QStringLiteral( "" )
1751       << Qgis::DataType::UInt32
1752       << "EPSG:4326"
1753       << 1.0
1754       << -1.0
1755       << 4;
1756 
1757   /*
1758    * Testcase 16
1759    *
1760    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1761    * crs = EPSG:4326
1762    * pixelSize = 1.0
1763    * constantValue = 12.0
1764    * Float32 Raster Layer
1765    *
1766    */
1767   QTest::newRow( "testcase 16" )
1768       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1769       << QStringLiteral( "/createConstantRaster_testcase16.tif" )
1770       << Qgis::DataType::Float32
1771       << "EPSG:4326"
1772       << 1.0
1773       << 12.12
1774       << 5;
1775 
1776   /*
1777    * Testcase 17
1778    *
1779    * inputExtent = from "/raster/band1_int16_noct_epsg4326.tif"
1780    * crs = EPSG:4326
1781    * pixelSize = 1.0
1782    * constantValue = 12.0
1783    * Float64 Raster Layer
1784    *
1785    */
1786   QTest::newRow( "testcase 17" )
1787       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
1788       << QStringLiteral( "/createConstantRaster_testcase17.tif" )
1789       << Qgis::DataType::Float64
1790       << "EPSG:4326"
1791       << 1.0
1792       << 12.125789212532487
1793       << 6;
1794 
1795 
1796 }
1797 
createConstantRaster()1798 void TestQgsProcessingAlgs::createConstantRaster()
1799 {
1800   QFETCH( QString, inputExtent );
1801   QFETCH( QString, expectedRaster );
1802   QFETCH( Qgis::DataType, expectedDataType );
1803   QFETCH( QString, crs );
1804   QFETCH( double, pixelSize );
1805   QFETCH( double, constantValue );
1806   QFETCH( int, typeId );
1807 
1808   //prepare input params
1809   QgsProject p;
1810   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:createconstantrasterlayer" ) ) );
1811 
1812   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
1813 
1814   //set project crs and ellipsoid from input layer
1815   p.setCrs( QgsCoordinateReferenceSystem( crs ), true );
1816 
1817   //set project after layer has been added so that transform context/ellipsoid from crs is also set
1818   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
1819   context->setProject( &p );
1820 
1821   QVariantMap parameters;
1822 
1823   parameters.insert( QStringLiteral( "EXTENT" ), inputExtent );
1824   parameters.insert( QStringLiteral( "TARGET_CRS" ), QgsCoordinateReferenceSystem( crs ) );
1825   parameters.insert( QStringLiteral( "PIXEL_SIZE" ), pixelSize );
1826   parameters.insert( QStringLiteral( "NUMBER" ), constantValue );
1827   parameters.insert( QStringLiteral( "OUTPUT_TYPE" ), typeId );
1828   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
1829 
1830   bool ok = false;
1831   QgsProcessingFeedback feedback;
1832   QVariantMap results;
1833 
1834   if ( expectedRaster.isEmpty() )
1835   {
1836     //verify if user feedback for unacceptable values are thrown
1837     alg->run( parameters, *context, &feedback, &ok );
1838     QVERIFY( !ok );
1839   }
1840   else
1841   {
1842     //prepare expectedRaster
1843     std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_constantRaster" + expectedRaster, "expectedDataset", "gdal" );
1844     std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
1845     QgsRasterIterator expectedIter( expectedInterface.get() );
1846     expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
1847 
1848     //run alg...
1849     results = alg->run( parameters, *context, &feedback, &ok );
1850     QVERIFY( ok );
1851 
1852     //...and check results with expected datasets
1853     std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
1854     std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
1855 
1856     QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
1857     QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
1858     QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
1859 
1860     QgsRasterIterator outputIter( outputInterface.get() );
1861     outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
1862     int outputIterLeft = 0;
1863     int outputIterTop = 0;
1864     int outputIterCols = 0;
1865     int outputIterRows = 0;
1866     int expectedIterLeft = 0;
1867     int expectedIterTop = 0;
1868     int expectedIterCols = 0;
1869     int expectedIterRows = 0;
1870 
1871     std::unique_ptr< QgsRasterBlock > outputRasterBlock;
1872     std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
1873 
1874     while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
1875             expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
1876     {
1877       for ( int row = 0; row < expectedIterRows; row++ )
1878       {
1879         for ( int column = 0; column < expectedIterCols; column++ )
1880         {
1881           const double expectedValue = expectedRasterBlock->value( row, column );
1882           const double outputValue = outputRasterBlock->value( row, column );
1883           QCOMPARE( outputValue, expectedValue );
1884         }
1885       }
1886     }
1887   }
1888 }
1889 
1890 
densifyGeometries_data()1891 void TestQgsProcessingAlgs::densifyGeometries_data()
1892 {
1893   QTest::addColumn<QgsGeometry>( "sourceGeometry" );
1894   QTest::addColumn<QgsGeometry>( "expectedGeometry" );
1895   QTest::addColumn<double>( "interval" );
1896   QTest::addColumn<QString>( "geometryType" );
1897 
1898   QTest::newRow( "Null geometry" )
1899       << QgsGeometry()
1900       << QgsGeometry()
1901       << 0.1
1902       << "Point";
1903 
1904   QTest::newRow( "PointZ" )
1905       << QgsGeometry::fromWkt( "PointZ( 1 2 3 )" )
1906       << QgsGeometry::fromWkt( "PointZ( 1 2 3 )" )
1907       << 0.1
1908       << "Point";
1909 
1910   QTest::newRow( "MultiPoint" )
1911       << QgsGeometry::fromWkt( "MULTIPOINT ((155 271), (150 360), (260 360), (271 265), (280 260), (270 370), (154 354), (150 260))" )
1912       << QgsGeometry::fromWkt( "MULTIPOINT ((155 271), (150 360), (260 360), (271 265), (280 260), (270 370), (154 354), (150 260))" )
1913       << 0.1
1914       << "Point";
1915 
1916   QTest::newRow( "LineString big distance" )
1917       << QgsGeometry::fromWkt( "LineString( 0 0, 10 0, 10 10 )" )
1918       << QgsGeometry::fromWkt( "LineString( 0 0, 10 0, 10 10 )" )
1919       << 100.
1920       << "LineString";
1921 
1922   QTest::newRow( "LineString small distance" )
1923       << QgsGeometry::fromWkt( "LineString( 0 0, 10 0, 10 10 )" )
1924       << QgsGeometry::fromWkt( "LineString (0 0, 2.5 0, 5 0, 7.5 0, 10 0, 10 2.5, 10 5, 10 7.5, 10 10)" )
1925       << 3.
1926       << "LineString";
1927 
1928   QTest::newRow( "LineStringZ" )
1929       << QgsGeometry::fromWkt( "LineStringZ( 0 0 1, 10 0 2, 10 10 0)" )
1930       << QgsGeometry::fromWkt( "LineStringZ (0 0 1, 5 0 1.5, 10 0 2, 10 5 1, 10 10 0)" )
1931       << 6.
1932       << "LineString";
1933 
1934   QTest::newRow( "LineStringM" )
1935       << QgsGeometry::fromWkt( "LineStringM( 0 0 0, 10 0 2, 10 10 0)" )
1936       << QgsGeometry::fromWkt( "LineStringM (0 0 0, 2.5 0 0.5, 5 0 1, 7.5 0 1.5, 10 0 2, 10 2.5 1.5, 10 5 1, 10 7.5 0.5, 10 10 0)" )
1937       << 3.
1938       << "LineString";
1939 
1940   QTest::newRow( "LineStringZM" )
1941       << QgsGeometry::fromWkt( "LineStringZM( 0 0 1 10, 10 0 2 8, 10 10 0 4)" )
1942       << QgsGeometry::fromWkt( "LineStringZM (0 0 1 10, 5 0 1.5 9, 10 0 2 8, 10 5 1 6, 10 10 0 4)" )
1943       << 6.
1944       << "LineString";
1945 
1946   QTest::newRow( "Polygon" )
1947       << QgsGeometry::fromWkt( "Polygon(( 0 0, 20 0, 20 20, 0 0 ))" )
1948       << QgsGeometry::fromWkt( "Polygon ((0 0, 5 0, 10 0, 15 0, 20 0, 20 5, 20 10, 20 15, 20 20, 16 16, 12 12, 7.99999999999999822 7.99999999999999822, 4 4, 0 0))" )
1949       << 6.
1950       << "Polygon";
1951 }
1952 
densifyGeometries()1953 void TestQgsProcessingAlgs::densifyGeometries()
1954 {
1955   QFETCH( QgsGeometry, sourceGeometry );
1956   QFETCH( QgsGeometry, expectedGeometry );
1957   QFETCH( double, interval );
1958   QFETCH( QString, geometryType );
1959 
1960   const std::unique_ptr< QgsProcessingFeatureBasedAlgorithm > alg( featureBasedAlg( "native:densifygeometriesgivenaninterval" ) );
1961 
1962   QVariantMap parameters;
1963   parameters.insert( QStringLiteral( "INTERVAL" ), interval );
1964 
1965   QgsFeature feature;
1966   feature.setGeometry( sourceGeometry );
1967 
1968   const QgsFeature result = runForFeature( alg, feature, geometryType, parameters );
1969 
1970   if ( expectedGeometry.isNull() )
1971     QVERIFY( result.geometry().isNull() );
1972   else
1973     QVERIFY2( result.geometry().equals( expectedGeometry ), QStringLiteral( "Result: %1, Expected: %2" ).arg( result.geometry().asWkt(), expectedGeometry.asWkt() ).toUtf8().constData() );
1974 }
1975 
fillNoData_data()1976 void TestQgsProcessingAlgs::fillNoData_data()
1977 {
1978   QTest::addColumn<QString>( "inputRaster" );
1979   QTest::addColumn<QString>( "expectedRaster" );
1980   QTest::addColumn<int>( "inputBand" );
1981   QTest::addColumn<double>( "fillValue" );
1982 
1983   /*
1984    * Testcase 1
1985    *
1986    * NoData raster layer
1987    * band = 1
1988    * fillValue = 2.0
1989    */
1990   QTest::newRow( "testcase 1" )
1991       << "/raster/band1_int16_noct_epsg4326.tif"
1992       << QStringLiteral( "/fillnodata_testcase1.tif" )
1993       << 1
1994       << 2.0;
1995 
1996   /*
1997    * Testcase 2
1998    *
1999    * WGS84 data without weights
2000    * searchRadius = 3
2001    * pixelSize = 1.8
2002    */
2003   QTest::newRow( "testcase 2" )
2004       << "/raster/band1_int16_noct_epsg4326.tif"
2005       << QStringLiteral( "/fillnodata_testcase2.tif" )
2006       << 1
2007       << 1.8;
2008 
2009   /*
2010    * Testcase 3
2011    *
2012    * WGS84 data without weights
2013    * searchRadius = 3
2014    * pixelSize = 1.8
2015    */
2016   QTest::newRow( "testcase 2" )
2017       << "/raster/band1_float32_noct_epsg4326.tif"
2018       << QStringLiteral( "/fillnodata_testcase3.tif" )
2019       << 1
2020       << 1.8;
2021 
2022 }
2023 
fillNoData()2024 void TestQgsProcessingAlgs::fillNoData()
2025 {
2026   QFETCH( QString, inputRaster );
2027   QFETCH( QString, expectedRaster );
2028   QFETCH( int, inputBand );
2029   QFETCH( double, fillValue );
2030 
2031   //prepare input params
2032   QgsProject p;
2033   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:fillnodata" ) ) );
2034 
2035   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
2036 
2037   std::unique_ptr<QgsRasterLayer> inputRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + inputRaster, "inputDataset", "gdal" );
2038 
2039   //set project crs and ellipsoid from input layer
2040   p.setCrs( inputRasterLayer->crs(), true );
2041 
2042   //set project after layer has been added so that transform context/ellipsoid from crs is also set
2043   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
2044   context->setProject( &p );
2045 
2046   QVariantMap parameters;
2047 
2048   parameters.insert( QStringLiteral( "INPUT" ), QString( myDataPath + inputRaster ) );
2049   parameters.insert( QStringLiteral( "BAND" ), inputBand );
2050   parameters.insert( QStringLiteral( "FILL_VALUE" ), fillValue );
2051   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
2052 
2053   //prepare expectedRaster
2054   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/fillNoData/" + expectedRaster, "expectedDataset", "gdal" );
2055   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
2056   QgsRasterIterator expectedIter( expectedInterface.get() );
2057   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
2058 
2059   //run alg...
2060 
2061   bool ok = false;
2062   QgsProcessingFeedback feedback;
2063   QVariantMap results;
2064 
2065   results = alg->run( parameters, *context, &feedback, &ok );
2066   QVERIFY( ok );
2067 
2068   //...and check results with expected datasets
2069   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
2070   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
2071 
2072   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
2073   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
2074 
2075   QgsRasterIterator outputIter( outputInterface.get() );
2076   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
2077   int outputIterLeft = 0;
2078   int outputIterTop = 0;
2079   int outputIterCols = 0;
2080   int outputIterRows = 0;
2081   int expectedIterLeft = 0;
2082   int expectedIterTop = 0;
2083   int expectedIterCols = 0;
2084   int expectedIterRows = 0;
2085 
2086   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
2087   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
2088 
2089   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
2090           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
2091   {
2092     for ( int row = 0; row < expectedIterRows; row++ )
2093     {
2094       for ( int column = 0; column < expectedIterCols; column++ )
2095       {
2096         const double expectedValue = expectedRasterBlock->value( row, column );
2097         const double outputValue = outputRasterBlock->value( row, column );
2098         QCOMPARE( outputValue, expectedValue );
2099       }
2100     }
2101   }
2102 }
2103 
lineDensity_data()2104 void TestQgsProcessingAlgs::lineDensity_data()
2105 {
2106   QTest::addColumn<QString>( "inputDataset" );
2107   QTest::addColumn<QString>( "expectedDataset" );
2108   QTest::addColumn<double>( "searchRadius" );
2109   QTest::addColumn<double>( "pixelSize" );
2110   QTest::addColumn<QString>( "weightField" );
2111 
2112   /*
2113    * Testcase 1
2114    *
2115    * WGS84 data with weights
2116    * searchRadius = 3
2117    * pixelSize = 2
2118    */
2119   QTest::newRow( "testcase 1" )
2120       << "/linedensity.gml"
2121       << QStringLiteral( "/linedensity_testcase1.tif" )
2122       << 3.0
2123       << 2.0
2124       << QStringLiteral( "weight" );
2125 
2126   /*
2127    * Testcase 2
2128    *
2129    * WGS84 data without weights
2130    * searchRadius = 3
2131    * pixelSize = 2
2132    */
2133   QTest::newRow( "testcase_2" )
2134       << "/linedensity.gml"
2135       << QStringLiteral( "/linedensity_testcase2.tif" )
2136       << 3.0
2137       << 2.0
2138       << QStringLiteral( "" );
2139 
2140 }
2141 
lineDensity()2142 void TestQgsProcessingAlgs::lineDensity()
2143 {
2144   QFETCH( QString, inputDataset );
2145   QFETCH( QString, expectedDataset );
2146   QFETCH( double, searchRadius );
2147   QFETCH( double, pixelSize );
2148   QFETCH( QString, weightField );
2149 
2150   //prepare input params
2151   QgsProject p;
2152   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:linedensity" ) ) );
2153 
2154   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
2155 
2156   QgsVectorLayer *layer = new QgsVectorLayer( myDataPath + inputDataset + "|layername=linedensity", QStringLiteral( "layer" ), QStringLiteral( "ogr" ) );
2157   p.addMapLayer( layer );
2158   QVERIFY( layer->isValid() );
2159 
2160   //set project crs and ellipsoid from input layer
2161   p.setCrs( layer->crs(), true );
2162 
2163   //set project after layer has been added so that transform context/ellipsoid from crs is also set
2164   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
2165   context->setProject( &p );
2166 
2167   QVariantMap parameters;
2168 
2169   parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( layer ) );
2170   parameters.insert( QStringLiteral( "WEIGHT" ), weightField );
2171   parameters.insert( QStringLiteral( "RADIUS" ), searchRadius );
2172   parameters.insert( QStringLiteral( "PIXEL_SIZE" ), pixelSize );
2173   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
2174 
2175   //prepare expectedRaster
2176   std::unique_ptr<QgsRasterLayer> expectedRaster = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_raster_linedensity" + expectedDataset, "expectedDataset", "gdal" );
2177   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRaster->dataProvider()->clone() );
2178   QgsRasterIterator expectedIter( expectedInterface.get() );
2179   expectedIter.startRasterRead( 1, expectedRaster->width(), expectedRaster->height(), expectedInterface->extent() );
2180 
2181   //run alg...
2182 
2183   bool ok = false;
2184   QgsProcessingFeedback feedback;
2185   QVariantMap results;
2186 
2187   results = alg->run( parameters, *context, &feedback, &ok );
2188   QVERIFY( ok );
2189 
2190   //...and check results with expected datasets
2191   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
2192   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
2193 
2194   QCOMPARE( outputRaster->width(), expectedRaster->width() );
2195   QCOMPARE( outputRaster->height(), expectedRaster->height() );
2196 
2197   QgsRasterIterator outputIter( outputInterface.get() );
2198   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
2199   int outputIterLeft = 0;
2200   int outputIterTop = 0;
2201   int outputIterCols = 0;
2202   int outputIterRows = 0;
2203   int expectedIterLeft = 0;
2204   int expectedIterTop = 0;
2205   int expectedIterCols = 0;
2206   int expectedIterRows = 0;
2207 
2208   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
2209   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
2210 
2211   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
2212           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
2213   {
2214     for ( int row = 0; row < expectedIterRows; row++ )
2215     {
2216       for ( int column = 0; column < expectedIterCols; column++ )
2217       {
2218         const double expectedValue = expectedRasterBlock->value( row, column );
2219         const double outputValue = outputRasterBlock->value( row, column );
2220         QGSCOMPARENEAR( outputValue, expectedValue, 0.0000000002 );
2221       }
2222     }
2223   }
2224 }
2225 
rasterLogicOp_data()2226 void TestQgsProcessingAlgs::rasterLogicOp_data()
2227 {
2228   QTest::addColumn<QVector< double >>( "input1" );
2229   QTest::addColumn<QVector< double >>( "input2" );
2230   QTest::addColumn<QVector< double >>( "input3" );
2231   QTest::addColumn<bool>( "treatNodataAsFalse" );
2232   QTest::addColumn<qgssize>( "expectedOrNoDataCount" );
2233   QTest::addColumn<qgssize>( "expectedOrTrueCount" );
2234   QTest::addColumn<qgssize>( "expectedOrFalseCount" );
2235   QTest::addColumn<QVector< double >>( "expectedOr" );
2236   QTest::addColumn<qgssize>( "expectedAndNoDataCount" );
2237   QTest::addColumn<qgssize>( "expectedAndTrueCount" );
2238   QTest::addColumn<qgssize>( "expectedAndFalseCount" );
2239   QTest::addColumn<QVector< double >>( "expectedAnd" );
2240   QTest::addColumn<int>( "nRows" );
2241   QTest::addColumn<int>( "nCols" );
2242   QTest::addColumn<double>( "destNoDataValue" );
2243   QTest::addColumn<int>( "dataType" );
2244 
2245   QTest::newRow( "no nodata" ) << QVector< double > { 1, 2, 0, 0, 0, 0 }
2246                                << QVector< double > { 1, 0, 1, 1, 0, 1 }
2247                                << QVector< double > { 1, 2, 0, 0, 0, -1 }
2248                                << false
2249                                << 0ULL << 5ULL << 1ULL
2250                                << QVector< double > { 1, 1, 1, 1, 0, 1 }
2251                                << 0ULL << 1ULL << 5ULL
2252                                << QVector< double > { 1, 0, 0, 0, 0, 0 }
2253                                << 3 << 2
2254                                << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2255   QTest::newRow( "nodata" ) << QVector< double > { 1, -9999, 0, 0, 0, 0 }
2256                             << QVector< double > { 1, 0, 1, 1, 0, 1 }
2257                             << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2258                             << false
2259                             << 2ULL << 3ULL << 1ULL
2260                             << QVector< double > { 1, -9999, 1, -9999, 0, 1 }
2261                             << 2ULL << 1ULL << 3ULL
2262                             << QVector< double > { 1, -9999, 0, -9999, 0, 0 }
2263                             << 3 << 2
2264                             << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2265   QTest::newRow( "nodata as false" ) << QVector< double > { 1, -9999, 0, 0, 0, 0 }
2266                                      << QVector< double > { 1, 0, 1, 1, 0, 1 }
2267                                      << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2268                                      << true
2269                                      << 0ULL << 5ULL << 1ULL
2270                                      << QVector< double > { 1, 1, 1, 1, 0, 1 }
2271                                      << 0ULL << 1ULL << 5ULL
2272                                      << QVector< double > { 1, 0, 0, 0, 0, 0 }
2273                                      << 3 << 2
2274                                      << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2275   QTest::newRow( "missing block 1" ) << QVector< double > {}
2276                                      << QVector< double > { 1, 0, 1, 1, 0, 1 }
2277                                      << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2278                                      << false
2279                                      << 6ULL << 0ULL << 0ULL
2280                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2281                                      << 6ULL << 0ULL << 0ULL
2282                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2283                                      << 3 << 2
2284                                      << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2285   QTest::newRow( "missing block 1 nodata as false" ) << QVector< double > {}
2286       << QVector< double > { 1, 0, 1, 1, 0, 1 }
2287       << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2288       << true
2289       << 0ULL << 5ULL << 1ULL
2290       << QVector< double > { 1, 1, 1, 1, 0, 1 }
2291       << 0ULL << 0ULL << 6ULL
2292       << QVector< double > { 0, 0, 0, 0, 0, 0 }
2293       << 3 << 2
2294       << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2295   QTest::newRow( "missing block 2" ) << QVector< double > { 1, 0, 1, 1, 0, 1 }
2296                                      << QVector< double > {}
2297                                      << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2298                                      << false
2299                                      << 6ULL << 0ULL << 0ULL
2300                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2301                                      << 6ULL << 0ULL << 0ULL
2302                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2303                                      << 3 << 2
2304                                      << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2305   QTest::newRow( "missing block 2 nodata as false" ) << QVector< double > { 1, 0, 1, 1, 0, 1 }
2306       << QVector< double > {}
2307       << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2308       << true
2309       << 0ULL << 5ULL << 1ULL
2310       << QVector< double > { 1, 1, 1, 1, 0, 1 }
2311       << 0ULL << 0ULL << 6ULL
2312       << QVector< double > { 0, 0, 0, 0, 0, 0 }
2313       << 3 << 2
2314       << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2315   QTest::newRow( "missing block 3" ) << QVector< double > { 1, 0, 1, 1, 0, 1 }
2316                                      << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2317                                      << QVector< double > {}
2318                                      << false
2319                                      << 6ULL << 0ULL << 0ULL
2320                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2321                                      << 6ULL << 0ULL << 0ULL
2322                                      << QVector< double > { -9999, -9999, -9999, -9999, -9999, -9999 }
2323                                      << 3 << 2
2324                                      << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2325   QTest::newRow( "missing block 3 nodata as false" ) << QVector< double > { 1, 0, 1, 1, 0, 1 }
2326       << QVector< double > { 1, 2, 0, -9999, 0, -1 }
2327       << QVector< double > {}
2328       << true
2329       << 0ULL << 5ULL << 1ULL
2330       << QVector< double > { 1, 1, 1, 1, 0, 1 }
2331       << 0ULL << 0ULL << 6ULL
2332       << QVector< double > { 0, 0, 0, 0, 0, 0 }
2333       << 3 << 2
2334       << -9999.0 << static_cast< int >( Qgis::DataType::Float32 );
2335 }
2336 
rasterLogicOp()2337 void TestQgsProcessingAlgs::rasterLogicOp()
2338 {
2339   QFETCH( QVector< double >, input1 );
2340   QFETCH( QVector< double >, input2 );
2341   QFETCH( QVector< double >, input3 );
2342   QVector< QVector< double > > input;
2343   input << input1 << input2 << input3;
2344   QFETCH( bool, treatNodataAsFalse );
2345   QFETCH( qgssize, expectedOrNoDataCount );
2346   QFETCH( qgssize, expectedOrTrueCount );
2347   QFETCH( qgssize, expectedOrFalseCount );
2348   QFETCH( QVector< double >, expectedOr );
2349   QFETCH( qgssize, expectedAndNoDataCount );
2350   QFETCH( qgssize, expectedAndTrueCount );
2351   QFETCH( qgssize, expectedAndFalseCount );
2352   QFETCH( QVector< double >, expectedAnd );
2353   QFETCH( int, nRows );
2354   QFETCH( int, nCols );
2355   QFETCH( double, destNoDataValue );
2356   QFETCH( int, dataType );
2357 
2358   QgsRasterLogicalOrAlgorithm orAlg;
2359   QgsRasterLogicalAndAlgorithm andAlg;
2360 
2361   const QgsRectangle extent = QgsRectangle( 0, 0, nRows, nCols );
2362   const QgsRectangle badExtent = QgsRectangle( -100, -100, 90, 90 );
2363   const QgsCoordinateReferenceSystem crs( QStringLiteral( "EPSG:3857" ) );
2364   double tform[] =
2365   {
2366     extent.xMinimum(), extent.width() / nCols, 0.0,
2367     extent.yMaximum(), 0.0, -extent.height() / nRows
2368   };
2369 
2370   std::vector< QgsRasterAnalysisUtils::RasterLogicInput > inputs;
2371   for ( int ii = 0; ii < 3; ++ii )
2372   {
2373     // generate unique filename (need to open the file first to generate it)
2374     QTemporaryFile tmpFile;
2375     tmpFile.open();
2376     tmpFile.close();
2377 
2378     // create a GeoTIFF - this will create data provider in editable mode
2379     const QString filename = tmpFile.fileName();
2380 
2381     std::unique_ptr< QgsRasterFileWriter > writer = std::make_unique< QgsRasterFileWriter >( filename );
2382     writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
2383     writer->setOutputFormat( QStringLiteral( "GTiff" ) );
2384     std::unique_ptr<QgsRasterDataProvider > dp( writer->createOneBandRaster( Qgis::DataType::Float32, nCols, nRows, input[ii].empty() ? badExtent : extent, crs ) );
2385     QVERIFY( dp->isValid() );
2386     dp->setNoDataValue( 1, -9999 );
2387     std::unique_ptr< QgsRasterBlock > block( dp->block( 1, input[ii].empty() ? badExtent : extent, nCols, nRows ) );
2388     if ( !dp->isEditable() )
2389     {
2390       QVERIFY( dp->setEditable( true ) );
2391     }
2392     int i = 0;
2393     for ( int row = 0; row < nRows; row++ )
2394     {
2395       for ( int col = 0; col < nCols; col++ )
2396       {
2397         if ( !input[ii].empty() )
2398           block->setValue( row, col, input[ii][i++] );
2399       }
2400     }
2401     QVERIFY( dp->writeBlock( block.get(), 1 ) );
2402     QVERIFY( dp->setEditable( false ) );
2403 
2404     QgsRasterAnalysisUtils::RasterLogicInput input;
2405     input.sourceDataProvider = std::move( dp );
2406     input.hasNoDataValue = true;
2407     input.interface = input.sourceDataProvider.get();
2408 
2409     inputs.emplace_back( std::move( input ) );
2410   }
2411 
2412   // make destination OR raster
2413   QTemporaryFile tmpFile2;
2414   tmpFile2.open();
2415   tmpFile2.close();
2416 
2417   // create a GeoTIFF - this will create data provider in editable mode
2418   QString filename = tmpFile2.fileName();
2419   std::unique_ptr< QgsRasterDataProvider > dpOr( QgsRasterDataProvider::create( QStringLiteral( "gdal" ), filename, QStringLiteral( "GTiff" ), 1, static_cast< Qgis::DataType >( dataType ), 10, 10, tform, crs ) );
2420   QVERIFY( dpOr->isValid() );
2421 
2422   // make destination AND raster
2423   QTemporaryFile tmpFile3;
2424   tmpFile3.open();
2425   tmpFile3.close();
2426 
2427   // create a GeoTIFF - this will create data provider in editable mode
2428   filename = tmpFile3.fileName();
2429   std::unique_ptr< QgsRasterDataProvider > dpAnd( QgsRasterDataProvider::create( QStringLiteral( "gdal" ), filename, QStringLiteral( "GTiff" ), 1, static_cast< Qgis::DataType >( dataType ), 10, 10, tform, crs ) );
2430   QVERIFY( dpAnd->isValid() );
2431 
2432   QgsFeedback feedback;
2433   qgssize noDataCount = 0;
2434   qgssize trueCount = 0;
2435   qgssize falseCount = 0;
2436   QgsRasterAnalysisUtils::applyRasterLogicOperator( inputs, dpOr.get(), destNoDataValue, treatNodataAsFalse, nCols, nRows,
2437       extent, &feedback, orAlg.mExtractValFunc, noDataCount, trueCount, falseCount );
2438 
2439   QCOMPARE( noDataCount, expectedOrNoDataCount );
2440   QCOMPARE( trueCount, expectedOrTrueCount );
2441   QCOMPARE( falseCount, expectedOrFalseCount );
2442 
2443   // read back in values
2444   std::unique_ptr< QgsRasterBlock > block( dpOr->block( 1, extent, nCols, nRows ) );
2445   QVector< double > res( nCols * nRows );
2446   int i = 0;
2447   for ( int row = 0; row < nRows; row++ )
2448   {
2449     for ( int col = 0; col < nCols; col++ )
2450     {
2451       res[i++] = block->value( row, col );
2452     }
2453   }
2454 
2455   for ( int row = 0; row < nRows; row++ )
2456   {
2457     for ( int col = 0; col < nCols; col++ )
2458     {
2459       QCOMPARE( res[row * nCols + col], expectedOr[row * nCols + col] );
2460     }
2461   }
2462 
2463   noDataCount = 0;
2464   trueCount = 0;
2465   falseCount = 0;
2466   QgsRasterAnalysisUtils::applyRasterLogicOperator( inputs, dpAnd.get(), destNoDataValue, treatNodataAsFalse, nCols, nRows,
2467       extent, &feedback, andAlg.mExtractValFunc, noDataCount, trueCount, falseCount );
2468 
2469   QCOMPARE( noDataCount, expectedAndNoDataCount );
2470   QCOMPARE( trueCount, expectedAndTrueCount );
2471   QCOMPARE( falseCount, expectedAndFalseCount );
2472 
2473   // read back in values
2474   block.reset( dpAnd->block( 1, extent, nCols, nRows ) );
2475   QVector< double > res2( nCols * nRows );
2476   i = 0;
2477   for ( int row = 0; row < nRows; row++ )
2478   {
2479     for ( int col = 0; col < nCols; col++ )
2480     {
2481       res2[i++] = block->value( row, col );
2482     }
2483   }
2484 
2485   for ( int row = 0; row < nRows; row++ )
2486   {
2487     for ( int col = 0; col < nCols; col++ )
2488     {
2489       QCOMPARE( res2[row * nCols + col], expectedAnd[row * nCols + col] );
2490     }
2491   }
2492 }
2493 
cellStatistics_data()2494 void TestQgsProcessingAlgs::cellStatistics_data()
2495 {
2496   QTest::addColumn<QStringList>( "inputRasters" );
2497   QTest::addColumn<QString>( "referenceLayer" );
2498   QTest::addColumn<int>( "statistic" );
2499   QTest::addColumn<bool>( "ignoreNoData" );
2500   QTest::addColumn<QString>( "expectedRaster" );
2501   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
2502 
2503   /*
2504    * Testcase 1: sum
2505    */
2506   QTest::newRow( "testcase_1" )
2507       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2508       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2509       << 0
2510       << false
2511       << QStringLiteral( "/cellstatistics_sum_result.tif" )
2512       << Qgis::DataType::Float64;
2513 
2514   /*
2515    * Testcase 2: count
2516    */
2517   QTest::newRow( "testcase_2" )
2518       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2519       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2520       << 1
2521       << false
2522       << QStringLiteral( "/cellstatistics_count_result.tif" )
2523       << Qgis::DataType::Int32;
2524 
2525   /*
2526    * Testcase 3: mean
2527    */
2528   QTest::newRow( "testcase_3" )
2529       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2530       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2531       << 2
2532       << false
2533       << QStringLiteral( "/cellstatistics_mean_result.tif" )
2534       << Qgis::DataType::Float64;
2535 
2536   /*
2537    * Testcase 4: median
2538    */
2539   QTest::newRow( "testcase_4" )
2540       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2541       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2542       << 3
2543       << false
2544       << QStringLiteral( "/cellstatistics_median_result.tif" )
2545       << Qgis::DataType::Float64;
2546 
2547   /*
2548    * Testcase 5: Standard deviation
2549    */
2550   QTest::newRow( "testcase_5" )
2551       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2552       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2553       << 4
2554       << false
2555       << QStringLiteral( "/cellstatistics_stddev_result.tif" )
2556       << Qgis::DataType::Float64;
2557 
2558   /*
2559    * Testcase 6: Variance
2560    */
2561   QTest::newRow( "testcase_6" )
2562       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2563       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2564       << 5
2565       << false
2566       << QStringLiteral( "/cellstatistics_variance_result.tif" )
2567       << Qgis::DataType::Float64;
2568 
2569   /*
2570    * Testcase 7: Minimum
2571    */
2572   QTest::newRow( "testcase_7" )
2573       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2574       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2575       << 6
2576       << false
2577       << QStringLiteral( "/cellstatistics_min_result.tif" )
2578       << Qgis::DataType::Float64;
2579 
2580   /*
2581    * Testcase 8: Maximum
2582    */
2583   QTest::newRow( "testcase_8" )
2584       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2585       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2586       << 7
2587       << false
2588       << QStringLiteral( "/cellstatistics_max_result.tif" )
2589       << Qgis::DataType::Float64;
2590 
2591   /*
2592    * Testcase 9: Minority
2593    */
2594   QTest::newRow( "testcase_9" )
2595       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2596       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2597       << 8
2598       << false
2599       << QStringLiteral( "/cellstatistics_minority_result.tif" )
2600       << Qgis::DataType::Float64;
2601 
2602   /*
2603    * Testcase 10: Majority
2604    */
2605   QTest::newRow( "testcase_10" )
2606       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2607       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2608       << 9
2609       << false
2610       << QStringLiteral( "/cellstatistics_majority_result.tif" )
2611       << Qgis::DataType::Float64;
2612 
2613   /*
2614    * Testcase 11: Range
2615    */
2616   QTest::newRow( "testcase_11" )
2617       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2618       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2619       << 10
2620       << false
2621       << QStringLiteral( "/cellstatistics_range_result.tif" )
2622       << Qgis::DataType::Float64;
2623 
2624   /*
2625    * Testcase 12: Variety
2626    */
2627   QTest::newRow( "testcase_12" )
2628       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2629       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2630       << 11
2631       << false
2632       << QStringLiteral( "/cellstatistics_variety_result.tif" )
2633       << Qgis::DataType::Int32;
2634 
2635   /*
2636    * Testcase 13: Sum (integer)
2637    */
2638   QTest::newRow( "testcase_13" )
2639       << QStringList( {"/raster/statisticsRas1_int32.tif", "/raster/statisticsRas2_int32.tif", "/raster/statisticsRas3_int32.tif"} )
2640       << QStringLiteral( "/raster/statisticsRas1_int32.tif" )
2641       << 0
2642       << false
2643       << QStringLiteral( "/cellstatistics_sum_result_int32.tif" )
2644       << Qgis::DataType::Int32;
2645 
2646   /*
2647    * Testcase 14: sum (ignore nodata)
2648    */
2649   QTest::newRow( "testcase_14" )
2650       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2651       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2652       << 0
2653       << true
2654       << QStringLiteral( "/cellstatistics_sum_ignore_nodata_result.tif" )
2655       << Qgis::DataType::Float64;
2656 
2657   /*
2658    * Testcase 15: mean
2659    */
2660   QTest::newRow( "testcase_15" )
2661       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2662       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2663       << 2
2664       << true
2665       << QStringLiteral( "/cellstatistics_mean_ignore_nodata_result.tif" )
2666       << Qgis::DataType::Float64;
2667 
2668   /*
2669    * Testcase 16: Sum (integer)
2670    */
2671   QTest::newRow( "testcase_16" )
2672       << QStringList( {"/raster/statisticsRas1_int32.tif", "/raster/statisticsRas2_int32.tif", "/raster/statisticsRas3_int32.tif"} )
2673       << QStringLiteral( "/raster/statisticsRas1_int32.tif" )
2674       << 5
2675       << false
2676       << QStringLiteral( "/cellstatistics_variance_result_float32.tif" )
2677       << Qgis::DataType::Float32;
2678 
2679   /*
2680    * Testcase 17: median with even number of layers
2681    */
2682   QTest::newRow( "testcase_17" )
2683       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
2684       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2685       << 3
2686       << false
2687       << QStringLiteral( "/cellstatistics_median_result_fourLayers.tif" )
2688       << Qgis::DataType::Float64;
2689 
2690   /*
2691    * Testcase 18: median with even number of layers and integer inputs
2692    */
2693   QTest::newRow( "testcase_18" )
2694       << QStringList( {"/raster/statisticsRas1_int32.tif", "/raster/statisticsRas1_int32.tif", "/raster/statisticsRas2_int32.tif", "/raster/statisticsRas3_int32.tif"} )
2695       << QStringLiteral( "/raster/statisticsRas1_int32.tif" )
2696       << 3
2697       << false
2698       << QStringLiteral( "/cellstatistics_median_result_fourLayers_float32.tif" )
2699       << Qgis::DataType::Float32;
2700 
2701   /*
2702    * Testcase 19: sum with raster cell stacks containing only nodata
2703    */
2704   QTest::newRow( "testcase_19" )
2705       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas1_float64.asc"} )
2706       << QStringLiteral( "/raster/statisticsRas3_int32.tif" )
2707       << 0
2708       << true
2709       << QStringLiteral( "/cellstatistics_sum_result_ignoreNoData.tif" )
2710       << Qgis::DataType::Float64;
2711 
2712 }
2713 
cellStatistics()2714 void TestQgsProcessingAlgs::cellStatistics()
2715 {
2716   QFETCH( QStringList, inputRasters );
2717   QFETCH( QString, referenceLayer );
2718   QFETCH( int, statistic );
2719   QFETCH( bool, ignoreNoData );
2720   QFETCH( QString, expectedRaster );
2721   QFETCH( Qgis::DataType, expectedDataType );
2722 
2723 
2724   //prepare input params
2725   QgsProject p;
2726   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:cellstatistics" ) ) );
2727 
2728   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
2729 
2730   QStringList inputDatasetPaths;
2731 
2732   for ( const auto &raster : inputRasters )
2733   {
2734     inputDatasetPaths << myDataPath + raster;
2735   }
2736 
2737   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
2738 
2739   //set project crs and ellipsoid from input layer
2740   p.setCrs( inputRasterLayer1->crs(), true );
2741 
2742   //set project after layer has been added so that transform context/ellipsoid from crs is also set
2743   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
2744   context->setProject( &p );
2745 
2746   QVariantMap parameters;
2747 
2748   parameters.insert( QStringLiteral( "INPUT" ), inputDatasetPaths );
2749   parameters.insert( QStringLiteral( "STATISTIC" ), statistic );
2750   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
2751   parameters.insert( QStringLiteral( "REFERENCE_LAYER" ), QString( myDataPath + referenceLayer ) );
2752   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
2753 
2754   //prepare expectedRaster
2755   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_cellStatistics/" + expectedRaster, "expectedDataset", "gdal" );
2756   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
2757   QgsRasterIterator expectedIter( expectedInterface.get() );
2758   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
2759 
2760   //run alg...
2761 
2762   bool ok = false;
2763   QgsProcessingFeedback feedback;
2764   QVariantMap results;
2765 
2766   results = alg->run( parameters, *context, &feedback, &ok );
2767   QVERIFY( ok );
2768 
2769   //...and check results with expected datasets
2770   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
2771   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
2772 
2773   QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
2774   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
2775   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
2776 
2777   QgsRasterIterator outputIter( outputInterface.get() );
2778   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
2779   int outputIterLeft = 0;
2780   int outputIterTop = 0;
2781   int outputIterCols = 0;
2782   int outputIterRows = 0;
2783   int expectedIterLeft = 0;
2784   int expectedIterTop = 0;
2785   int expectedIterCols = 0;
2786   int expectedIterRows = 0;
2787 
2788   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
2789   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
2790 
2791   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
2792           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
2793   {
2794     for ( int row = 0; row < expectedIterRows; row++ )
2795     {
2796       for ( int column = 0; column < expectedIterCols; column++ )
2797       {
2798         const double expectedValue = expectedRasterBlock->value( row, column );
2799         const double outputValue = outputRasterBlock->value( row, column );
2800         QCOMPARE( outputValue, expectedValue );
2801       }
2802     }
2803   }
2804 }
2805 
Q_DECLARE_METATYPE(QgsRasterAnalysisUtils::CellValuePercentileMethods)2806 Q_DECLARE_METATYPE( QgsRasterAnalysisUtils::CellValuePercentileMethods )
2807 void TestQgsProcessingAlgs::percentileFunctions_data()
2808 {
2809   QTest::addColumn<QgsRasterAnalysisUtils::CellValuePercentileMethods>( "function" );
2810   QTest::addColumn<std::vector<double>>( "inputValues" );
2811   QTest::addColumn<std::vector<double>>( "inputPercentiles" );
2812   QTest::addColumn<std::vector<double>>( "expectedValues" );
2813 
2814   QTest::newRow( "testcase_1" )
2815       << QgsRasterAnalysisUtils::NearestRankPercentile
2816       << std::vector<double>( {100, 24, 49, 36, 2, 18, 98, 64, 20, 20} )
2817       << std::vector<double>( {0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} )
2818       << std::vector<double>( {2, 2, 18, 20, 20, 24, 36, 49, 64, 98, 100} );
2819 
2820   QTest::newRow( "testcase_2" )
2821       << QgsRasterAnalysisUtils::InterpolatedPercentileInc
2822       << std::vector<double>( {100, 24, 49, 36, 2, 18, 98, 64, 20, 20} )
2823       << std::vector<double>( {0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} )
2824       << std::vector<double>( {2.0, 16.4, 19.6, 20.0, 22.4, 30.0, 41.2, 53.5, 70.8, 98.2, 100} );
2825 
2826   QTest::newRow( "testcase_3" )
2827       << QgsRasterAnalysisUtils::InterpolatedPercentileExc
2828       << std::vector<double>( {100, 24, 49, 36, 2, 18, 98, 64, 20, 20} )
2829       << std::vector<double>( {0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} )
2830       << std::vector<double>( {-9999, 3.6, 18.4, 20, 21.6, 30, 43.8, 59.5, 91.2, 99.8, -9999} );
2831 }
2832 
percentileFunctions()2833 void TestQgsProcessingAlgs::percentileFunctions()
2834 {
2835   QFETCH( QgsRasterAnalysisUtils::CellValuePercentileMethods, function );
2836   QFETCH( std::vector<double>, inputValues );
2837   QFETCH( std::vector<double>, inputPercentiles );
2838   QFETCH( std::vector<double>, expectedValues );
2839 
2840   const int inputValuesSize = static_cast<int>( inputValues.size() );
2841   const int percentileSize = static_cast<int>( inputPercentiles.size() );
2842   double result;
2843 
2844   for ( int i = 0; i < percentileSize; i++ )
2845   {
2846     const double percentile = inputPercentiles[i];
2847     const double expectedValue = expectedValues[i];
2848 
2849     switch ( function )
2850     {
2851       case ( QgsRasterAnalysisUtils::NearestRankPercentile ):
2852       {
2853         result = QgsRasterAnalysisUtils::nearestRankPercentile( inputValues, inputValuesSize, percentile );
2854         QCOMPARE( result, expectedValue );
2855         break;
2856       }
2857       case ( QgsRasterAnalysisUtils::InterpolatedPercentileInc ):
2858       {
2859         result = QgsRasterAnalysisUtils::interpolatedPercentileInc( inputValues, inputValuesSize, percentile );
2860         QCOMPARE( result, expectedValue );
2861         break;
2862       }
2863       case ( QgsRasterAnalysisUtils::InterpolatedPercentileExc ):
2864       {
2865         result = QgsRasterAnalysisUtils::interpolatedPercentileExc( inputValues, inputValuesSize, percentile, -9999 );
2866         QCOMPARE( result, expectedValue );
2867         break;
2868       }
2869     }
2870   }
2871 }
2872 
percentileRaster_data()2873 void TestQgsProcessingAlgs::percentileRaster_data()
2874 {
2875   QTest::addColumn<QStringList>( "inputRasters" );
2876   QTest::addColumn<QString>( "referenceLayer" );
2877   QTest::addColumn<int>( "method" );
2878   QTest::addColumn<double>( "percentile" );
2879   QTest::addColumn<bool>( "ignoreNoData" );
2880   QTest::addColumn<QString>( "expectedRaster" );
2881   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
2882 
2883   /*
2884    * Testcase 1: nearest, ignoreNoData = true, dataType = Float64
2885    */
2886   QTest::newRow( "testcase_1" )
2887       << QStringList( {"/raster/statisticsRas1_float64.asc",
2888                        "/raster/statisticsRas4_float64.asc",
2889                        "/raster/rnd_percentile_raster1_float64.tif",
2890                        "/raster/rnd_percentile_raster2_float64.tif",
2891                        "/raster/rnd_percentile_raster3_float64.tif",
2892                        "/raster/rnd_percentile_raster4_float64.tif",
2893                        "/raster/rnd_percentile_raster5_float64.tif"} )
2894       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2895       << 0
2896       << 0.789
2897       << true
2898       << QStringLiteral( "/percentile_nearest_ignoreTrue_float64.tif" )
2899       << Qgis::DataType::Float64;
2900 
2901   /*
2902    * Testcase 2: inc, ignoreNoData = true, dataType = Float64
2903    */
2904   QTest::newRow( "testcase_2" )
2905       << QStringList( {"/raster/statisticsRas1_float64.asc",
2906                        "/raster/statisticsRas4_float64.asc",
2907                        "/raster/rnd_percentile_raster1_float64.tif",
2908                        "/raster/rnd_percentile_raster2_float64.tif",
2909                        "/raster/rnd_percentile_raster3_float64.tif",
2910                        "/raster/rnd_percentile_raster4_float64.tif",
2911                        "/raster/rnd_percentile_raster5_float64.tif"} )
2912       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2913       << 1
2914       << 0.789
2915       << true
2916       << QStringLiteral( "/percentile_inc_ignoreTrue_float64.tif" )
2917       << Qgis::DataType::Float64;
2918 
2919   /*
2920    * Testcase 3: exc, ignoreNoData = true, dataType = Float64
2921    */
2922   QTest::newRow( "testcase_3" )
2923       << QStringList( {"/raster/statisticsRas1_float64.asc",
2924                        "/raster/statisticsRas4_float64.asc",
2925                        "/raster/rnd_percentile_raster1_float64.tif",
2926                        "/raster/rnd_percentile_raster2_float64.tif",
2927                        "/raster/rnd_percentile_raster3_float64.tif",
2928                        "/raster/rnd_percentile_raster4_float64.tif",
2929                        "/raster/rnd_percentile_raster5_float64.tif"} )
2930       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2931       << 2
2932       << 0.789
2933       << true
2934       << QStringLiteral( "/percentile_exc_ignoreTrue_float64.tif" )
2935       << Qgis::DataType::Float64;
2936 
2937   /*
2938    * Testcase 4: nearest, ignoreNoData = false, dataType = Float64
2939    */
2940   QTest::newRow( "testcase_4" )
2941       << QStringList( {"/raster/statisticsRas1_float64.asc",
2942                        "/raster/statisticsRas4_float64.asc",
2943                        "/raster/rnd_percentile_raster1_float64.tif",
2944                        "/raster/rnd_percentile_raster2_float64.tif",
2945                        "/raster/rnd_percentile_raster3_float64.tif",
2946                        "/raster/rnd_percentile_raster4_float64.tif",
2947                        "/raster/rnd_percentile_raster5_float64.tif"} )
2948       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2949       << 0
2950       << 0.789
2951       << false
2952       << QStringLiteral( "/percentile_nearest_ignoreFalse_float64.tif" )
2953       << Qgis::DataType::Float64;
2954 
2955   /*
2956    * Testcase 5: inc, ignoreNoData = false, dataType = Float64
2957    */
2958   QTest::newRow( "testcase_5" )
2959       << QStringList( {"/raster/statisticsRas1_float64.asc",
2960                        "/raster/statisticsRas4_float64.asc",
2961                        "/raster/rnd_percentile_raster1_float64.tif",
2962                        "/raster/rnd_percentile_raster2_float64.tif",
2963                        "/raster/rnd_percentile_raster3_float64.tif",
2964                        "/raster/rnd_percentile_raster4_float64.tif",
2965                        "/raster/rnd_percentile_raster5_float64.tif"} )
2966       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2967       << 1
2968       << 0.789
2969       << false
2970       << QStringLiteral( "/percentile_inc_ignoreFalse_float64.tif" )
2971       << Qgis::DataType::Float64;
2972 
2973   /*
2974    * Testcase 6: exc, ignoreNoData = false, dataType = Float64
2975    */
2976   QTest::newRow( "testcase_6" )
2977       << QStringList( {"/raster/statisticsRas1_float64.asc",
2978                        "/raster/statisticsRas4_float64.asc",
2979                        "/raster/rnd_percentile_raster1_float64.tif",
2980                        "/raster/rnd_percentile_raster2_float64.tif",
2981                        "/raster/rnd_percentile_raster3_float64.tif",
2982                        "/raster/rnd_percentile_raster4_float64.tif",
2983                        "/raster/rnd_percentile_raster5_float64.tif"} )
2984       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
2985       << 2
2986       << 0.789
2987       << false
2988       << QStringLiteral( "/percentile_exc_ignoreFalse_float64.tif" )
2989       << Qgis::DataType::Float64;
2990 
2991   /*
2992    * Testcase 7: exc, ignoreNoData = false, dataType = Byte
2993    */
2994   QTest::newRow( "testcase_7" )
2995       << QStringList( {"/raster/rnd_percentile_raster1_byte.tif",
2996                        "/raster/rnd_percentile_raster2_byte.tif",
2997                        "/raster/rnd_percentile_raster3_byte.tif",
2998                        "/raster/rnd_percentile_raster4_byte.tif",
2999                        "/raster/rnd_percentile_raster5_byte.tif"} )
3000       << QStringLiteral( "/raster/rnd_percentile_raster1_byte.tif" )
3001       << 0
3002       << 0.789
3003       << false
3004       << QStringLiteral( "/percentile_nearest_ignoreFalse_byte.tif" )
3005       << Qgis::DataType::Byte;
3006 }
3007 
3008 
percentileRaster()3009 void TestQgsProcessingAlgs::percentileRaster()
3010 {
3011   QFETCH( QStringList, inputRasters );
3012   QFETCH( QString, referenceLayer );
3013   QFETCH( int, method );
3014   QFETCH( double, percentile );
3015   QFETCH( bool, ignoreNoData );
3016   QFETCH( QString, expectedRaster );
3017   QFETCH( Qgis::DataType, expectedDataType );
3018 
3019   //prepare input params
3020   QgsProject p;
3021   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:cellstackpercentile" ) ) );
3022 
3023   const QString myDataPath( TEST_DATA_DIR ); //defined in CMakeLists.txt
3024 
3025   QStringList inputDatasetPaths;
3026 
3027   for ( const auto &raster : inputRasters )
3028   {
3029     inputDatasetPaths << myDataPath + raster;
3030   }
3031 
3032   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
3033 
3034   //set project crs and ellipsoid from input layer
3035   p.setCrs( inputRasterLayer1->crs(), true );
3036 
3037   //set project after layer has been added so that transform context/ellipsoid from crs is also set
3038   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
3039   context->setProject( &p );
3040 
3041   QVariantMap parameters;
3042 
3043   parameters.insert( QStringLiteral( "INPUT" ), inputDatasetPaths );
3044   parameters.insert( QStringLiteral( "METHOD" ), method );
3045   parameters.insert( QStringLiteral( "PERCENTILE" ), percentile );
3046   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
3047   parameters.insert( QStringLiteral( "REFERENCE_LAYER" ), QString( myDataPath + referenceLayer ) );
3048   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
3049 
3050   //prepare expectedRaster
3051   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_cellStackPercentile/" + expectedRaster, "expectedDataset", "gdal" );
3052   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
3053   QgsRasterIterator expectedIter( expectedInterface.get() );
3054   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
3055 
3056   //run alg...
3057   bool ok = false;
3058   QgsProcessingFeedback feedback;
3059   QVariantMap results;
3060 
3061   results = alg->run( parameters, *context, &feedback, &ok );
3062   QVERIFY( ok );
3063 
3064   //...and check results with expected datasets
3065   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
3066   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
3067 
3068   QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
3069   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
3070   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
3071 
3072   QgsRasterIterator outputIter( outputInterface.get() );
3073   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
3074   int outputIterLeft = 0;
3075   int outputIterTop = 0;
3076   int outputIterCols = 0;
3077   int outputIterRows = 0;
3078   int expectedIterLeft = 0;
3079   int expectedIterTop = 0;
3080   int expectedIterCols = 0;
3081   int expectedIterRows = 0;
3082 
3083   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
3084   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
3085 
3086   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
3087           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
3088   {
3089     for ( int row = 0; row < expectedIterRows; row++ )
3090     {
3091       for ( int column = 0; column < expectedIterCols; column++ )
3092       {
3093         const double roundedExpectedValue = std::round( expectedRasterBlock->value( row, column ) * 4 ) * 4;
3094         const double roundedOutputValue = std::round( outputRasterBlock->value( row, column ) * 4 ) * 4;
3095         QCOMPARE( roundedOutputValue, roundedExpectedValue );
3096       }
3097     }
3098   }
3099 }
3100 
Q_DECLARE_METATYPE(QgsRasterAnalysisUtils::CellValuePercentRankMethods)3101 Q_DECLARE_METATYPE( QgsRasterAnalysisUtils::CellValuePercentRankMethods )
3102 void TestQgsProcessingAlgs::percentrankFunctions_data()
3103 {
3104   QTest::addColumn<QgsRasterAnalysisUtils::CellValuePercentRankMethods>( "function" );
3105   QTest::addColumn<std::vector<double>>( "inputValues" );
3106   QTest::addColumn<std::vector<double>>( "inputPercentrank" );
3107   QTest::addColumn<std::vector<double>>( "expectedValues" );
3108 
3109   QTest::newRow( "testcase_1" )
3110       << QgsRasterAnalysisUtils::InterpolatedPercentRankInc
3111       << std::vector<double>( {100, 24, 49, 36, 2, 18, 98, 64, 20, 20} )
3112       << std::vector<double>( {-8, 2, 18, 20, 33, 47, 29, 39.5, 57, 39, 12, 100, 150} )
3113       << std::vector<double>( {-9999, 0, 0.111111111111, 0.222222222222, 0.527777777778, 0.649572649573, 0.490740740741, 0.58547008547, 0.725925925926, 0.581196581197, 0.0694444444444, 1, -9999} );
3114 
3115   QTest::newRow( "testcase_2" )
3116       << QgsRasterAnalysisUtils::InterpolatedPercentRankExc
3117       << std::vector<double>( {100, 24, 49, 36, 2, 18, 98, 64, 20, 20} )
3118       << std::vector<double>( {-8, 2, 18, 20, 33, 47, 29, 39.5, 57, 39, 12, 100, 150} )
3119       << std::vector<double>( {-9999, 0.0909090909091, 0.1818181818181, 0.272727272727, 0.522727272727, 0.622377622378, 0.492424242424, 0.56993006993, 0.684848484848, 0.566433566434, 0.1477272727272, 0.909090909091, -9999} );
3120 }
3121 
percentrankFunctions()3122 void TestQgsProcessingAlgs::percentrankFunctions()
3123 {
3124   QFETCH( QgsRasterAnalysisUtils::CellValuePercentRankMethods, function );
3125   QFETCH( std::vector<double>, inputValues );
3126   QFETCH( std::vector<double>, inputPercentrank );
3127   QFETCH( std::vector<double>, expectedValues );
3128 
3129   const int inputValuesSize = static_cast<int>( inputValues.size() );
3130   const int percentrankSize = static_cast<int>( inputPercentrank.size() );
3131   double result;
3132 
3133   for ( int i = 0; i < percentrankSize; i++ )
3134   {
3135     const double percentrank = inputPercentrank[i];
3136     const double expectedValue = expectedValues[i];
3137 
3138     switch ( function )
3139     {
3140       case ( QgsRasterAnalysisUtils::InterpolatedPercentRankInc ):
3141       {
3142         result = QgsRasterAnalysisUtils::interpolatedPercentRankInc( inputValues, inputValuesSize, percentrank, -9999 );
3143         QCOMPARE( result, expectedValue );
3144         break;
3145       }
3146       case ( QgsRasterAnalysisUtils::InterpolatedPercentRankExc ):
3147       {
3148         result = QgsRasterAnalysisUtils::interpolatedPercentRankExc( inputValues, inputValuesSize, percentrank, -9999 );
3149         QCOMPARE( result, expectedValue );
3150         break;
3151       }
3152     }
3153   }
3154 
3155   std::vector<double> cellVal = std::vector<double>( {13, 36, 13, 44, 60} );
3156 
3157   qDebug() << QgsRasterAnalysisUtils::interpolatedPercentRankInc( cellVal, 5, 13, 200 );
3158 
3159   QVERIFY( true );
3160 }
3161 
percentrankByRaster_data()3162 void TestQgsProcessingAlgs::percentrankByRaster_data()
3163 {
3164   QTest::addColumn<QString>( "valueLayer" );
3165   QTest::addColumn<int>( "valueLayerBand" );
3166   QTest::addColumn<QStringList>( "inputRasters" );
3167   QTest::addColumn<QString>( "referenceLayer" );
3168   QTest::addColumn<int>( "method" );
3169   QTest::addColumn<bool>( "ignoreNoData" );
3170   QTest::addColumn<double>( "noDataValue" );
3171   QTest::addColumn<QString>( "expectedRaster" );
3172   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
3173 
3174   /*
3175    * Testcase 1: nearest, ignoreNoData = true, dataType = Float64
3176    */
3177   QTest::newRow( "testcase_1" )
3178       << QStringLiteral( "/raster/rnd_percentrank_valueraster_float64.tif" )
3179       << 1
3180       << QStringList( {"/raster/statisticsRas1_float64.asc",
3181                        "/raster/statisticsRas4_float64.asc",
3182                        "/raster/rnd_percentile_raster1_float64.tif",
3183                        "/raster/rnd_percentile_raster2_float64.tif",
3184                        "/raster/rnd_percentile_raster3_float64.tif",
3185                        "/raster/rnd_percentile_raster4_float64.tif",
3186                        "/raster/rnd_percentile_raster5_float64.tif"} )
3187       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3188       << 0
3189       << true
3190       << -9999.0
3191       << QStringLiteral( "/percentRankByRaster_inc_ignoreTrue_float64.tif" )
3192       << Qgis::DataType::Float32;
3193 
3194   /*
3195    * Testcase 2: inc, ignoreNoData = true, dataType = Float64
3196    */
3197   QTest::newRow( "testcase_2" )
3198       << QStringLiteral( "/raster/rnd_percentrank_valueraster_float64.tif" )
3199       << 1
3200       << QStringList( {"/raster/statisticsRas1_float64.asc",
3201                        "/raster/statisticsRas4_float64.asc",
3202                        "/raster/rnd_percentile_raster1_float64.tif",
3203                        "/raster/rnd_percentile_raster2_float64.tif",
3204                        "/raster/rnd_percentile_raster3_float64.tif",
3205                        "/raster/rnd_percentile_raster4_float64.tif",
3206                        "/raster/rnd_percentile_raster5_float64.tif"} )
3207       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3208       << 1
3209       << true
3210       << -9999.0
3211       << QStringLiteral( "/percentRankByRaster_exc_ignoreTrue_float64.tif" )
3212       << Qgis::DataType::Float32;
3213 
3214   /*
3215    * Testcase 3: nearest, ignoreNoData = false, dataType = Float64
3216    */
3217   QTest::newRow( "testcase_3" )
3218       << QStringLiteral( "/raster/rnd_percentrank_valueraster_float64.tif" )
3219       << 1
3220       << QStringList( {"/raster/statisticsRas1_float64.asc",
3221                        "/raster/statisticsRas4_float64.asc",
3222                        "/raster/rnd_percentile_raster1_float64.tif",
3223                        "/raster/rnd_percentile_raster2_float64.tif",
3224                        "/raster/rnd_percentile_raster3_float64.tif",
3225                        "/raster/rnd_percentile_raster4_float64.tif",
3226                        "/raster/rnd_percentile_raster5_float64.tif"} )
3227       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3228       << 0
3229       << false
3230       << -9999.0
3231       << QStringLiteral( "/percentRankByRaster_inc_ignoreFalse_float64.tif" )
3232       << Qgis::DataType::Float32;
3233 
3234   /*
3235    * Testcase 4: inc, ignoreNoData = false, dataType = Float64
3236    */
3237   QTest::newRow( "testcase_4" )
3238       << QStringLiteral( "/raster/rnd_percentrank_valueraster_float64.tif" )
3239       << 1
3240       << QStringList( {"/raster/statisticsRas1_float64.asc",
3241                        "/raster/statisticsRas4_float64.asc",
3242                        "/raster/rnd_percentile_raster1_float64.tif",
3243                        "/raster/rnd_percentile_raster2_float64.tif",
3244                        "/raster/rnd_percentile_raster3_float64.tif",
3245                        "/raster/rnd_percentile_raster4_float64.tif",
3246                        "/raster/rnd_percentile_raster5_float64.tif"} )
3247       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3248       << 1
3249       << false
3250       << -9999.0
3251       << QStringLiteral( "/percentRankByRaster_exc_ignoreFalse_float64.tif" )
3252       << Qgis::DataType::Float32;
3253 
3254 
3255   /*
3256    * Testcase 5: inc, ignoreNoData = false, dataType = Byte
3257    */
3258   QTest::newRow( "testcase_5" )
3259       << QStringLiteral( "/raster/rnd_percentile_raster1_byte.tif" )
3260       << 1
3261       << QStringList( {"/raster/rnd_percentile_raster1_byte.tif",
3262                        "/raster/rnd_percentile_raster2_byte.tif",
3263                        "/raster/rnd_percentile_raster3_byte.tif",
3264                        "/raster/rnd_percentile_raster4_byte.tif",
3265                        "/raster/rnd_percentile_raster5_byte.tif"} )
3266       << QStringLiteral( "/raster/rnd_percentile_raster1_byte.tif" )
3267       << 0
3268       << false
3269       << 200.0
3270       << QStringLiteral( "/percentRankByRaster_inc_ignoreFalse_byte.tif" )
3271       << Qgis::DataType::Float32;
3272 }
3273 
3274 
percentrankByRaster()3275 void TestQgsProcessingAlgs::percentrankByRaster()
3276 {
3277   QFETCH( QString, valueLayer );
3278   QFETCH( int, valueLayerBand );
3279   QFETCH( QStringList, inputRasters );
3280   QFETCH( QString, referenceLayer );
3281   QFETCH( int, method );
3282   QFETCH( bool, ignoreNoData );
3283   QFETCH( double, noDataValue );
3284   QFETCH( QString, expectedRaster );
3285   QFETCH( Qgis::DataType, expectedDataType );
3286 
3287   //prepare input params
3288   QgsProject p;
3289   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:cellstackpercentrankfromrasterlayer" ) ) );
3290 
3291   const QString myDataPath( TEST_DATA_DIR ); //defined in CMakeLists.txt
3292 
3293   QStringList inputDatasetPaths;
3294 
3295   for ( const auto &raster : inputRasters )
3296   {
3297     inputDatasetPaths << myDataPath + raster;
3298   }
3299 
3300   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
3301 
3302   //set project crs and ellipsoid from input layer
3303   p.setCrs( inputRasterLayer1->crs(), true );
3304 
3305   //set project after layer has been added so that transform context/ellipsoid from crs is also set
3306   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
3307   context->setProject( &p );
3308 
3309   QVariantMap parameters;
3310 
3311   parameters.insert( QStringLiteral( "INPUT" ), inputDatasetPaths );
3312   parameters.insert( QStringLiteral( "INPUT_VALUE_RASTER" ), QString( myDataPath + valueLayer ) );
3313   parameters.insert( QStringLiteral( "VALUE_RASTER_BAND" ), valueLayerBand );
3314   parameters.insert( QStringLiteral( "METHOD" ), method );
3315   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
3316   parameters.insert( QStringLiteral( "OUTPUT_NODATA_VALUE" ), noDataValue );
3317   parameters.insert( QStringLiteral( "REFERENCE_LAYER" ), QString( myDataPath + referenceLayer ) );
3318   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
3319 
3320   //prepare expectedRaster
3321   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_cellStackPercentrankFromRaster/" + expectedRaster, "expectedDataset", "gdal" );
3322   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
3323   QgsRasterIterator expectedIter( expectedInterface.get() );
3324   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
3325 
3326   //run alg...
3327   bool ok = false;
3328   QgsProcessingFeedback feedback;
3329   QVariantMap results;
3330 
3331   results = alg->run( parameters, *context, &feedback, &ok );
3332   QVERIFY( ok );
3333 
3334   //...and check results with expected datasets
3335   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
3336   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
3337 
3338   QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
3339   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
3340   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
3341 
3342   QgsRasterIterator outputIter( outputInterface.get() );
3343   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
3344   int outputIterLeft = 0;
3345   int outputIterTop = 0;
3346   int outputIterCols = 0;
3347   int outputIterRows = 0;
3348   int expectedIterLeft = 0;
3349   int expectedIterTop = 0;
3350   int expectedIterCols = 0;
3351   int expectedIterRows = 0;
3352 
3353   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
3354   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
3355 
3356   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
3357           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
3358   {
3359     for ( int row = 0; row < expectedIterRows; row++ )
3360     {
3361       for ( int column = 0; column < expectedIterCols; column++ )
3362       {
3363         const double roundedExpectedValue = std::round( expectedRasterBlock->value( row, column ) * 4 ) * 4;
3364         const double roundedOutputValue = std::round( outputRasterBlock->value( row, column ) * 4 ) * 4;
3365         QCOMPARE( roundedOutputValue, roundedExpectedValue );
3366       }
3367     }
3368   }
3369 }
3370 
percentrankByValue_data()3371 void TestQgsProcessingAlgs::percentrankByValue_data()
3372 {
3373   QTest::addColumn<QStringList>( "inputRasters" );
3374   QTest::addColumn<QString>( "referenceLayer" );
3375   QTest::addColumn<double>( "value" );
3376   QTest::addColumn<int>( "method" );
3377   QTest::addColumn<bool>( "ignoreNoData" );
3378   QTest::addColumn<double>( "noDataValue" );
3379   QTest::addColumn<QString>( "expectedRaster" );
3380   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
3381 
3382   /*
3383    * Testcase 1: nearest, ignoreNoData = true, dataType = Float64
3384    */
3385   QTest::newRow( "testcase_1" )
3386       << QStringList( {"/raster/statisticsRas1_float64.asc",
3387                        "/raster/statisticsRas4_float64.asc",
3388                        "/raster/rnd_percentile_raster1_float64.tif",
3389                        "/raster/rnd_percentile_raster2_float64.tif",
3390                        "/raster/rnd_percentile_raster3_float64.tif",
3391                        "/raster/rnd_percentile_raster4_float64.tif",
3392                        "/raster/rnd_percentile_raster5_float64.tif"} )
3393       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3394       << 83.327
3395       << 0
3396       << true
3397       << -9999.0
3398       << QStringLiteral( "/percentRankByValue_inc_ignoreTrue_float64.tif" )
3399       << Qgis::DataType::Float32;
3400 
3401   /*
3402    * Testcase 2: inc, ignoreNoData = true, dataType = Float64
3403    */
3404   QTest::newRow( "testcase_2" )
3405       << QStringList( {"/raster/statisticsRas1_float64.asc",
3406                        "/raster/statisticsRas4_float64.asc",
3407                        "/raster/rnd_percentile_raster1_float64.tif",
3408                        "/raster/rnd_percentile_raster2_float64.tif",
3409                        "/raster/rnd_percentile_raster3_float64.tif",
3410                        "/raster/rnd_percentile_raster4_float64.tif",
3411                        "/raster/rnd_percentile_raster5_float64.tif"} )
3412       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3413       << 7.99
3414       << 1
3415       << true
3416       << -9999.0
3417       << QStringLiteral( "/percentRankByValue_exc_ignoreTrue_float64.tif" )
3418       << Qgis::DataType::Float32;
3419 
3420   /*
3421    * Testcase 3: nearest, ignoreNoData = false, dataType = Float64
3422    */
3423   QTest::newRow( "testcase_3" )
3424       << QStringList( {"/raster/statisticsRas1_float64.asc",
3425                        "/raster/statisticsRas4_float64.asc",
3426                        "/raster/rnd_percentile_raster1_float64.tif",
3427                        "/raster/rnd_percentile_raster2_float64.tif",
3428                        "/raster/rnd_percentile_raster3_float64.tif",
3429                        "/raster/rnd_percentile_raster4_float64.tif",
3430                        "/raster/rnd_percentile_raster5_float64.tif"} )
3431       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3432       << 200.78
3433       << 0
3434       << false
3435       << -9999.0
3436       << QStringLiteral( "/percentRankByValue_inc_ignoreFalse_float64.tif" )
3437       << Qgis::DataType::Float32;
3438 
3439   /*
3440    * Testcase 4: inc, ignoreNoData = false, dataType = Float64
3441    */
3442   QTest::newRow( "testcase_4" )
3443       << QStringList( {"/raster/statisticsRas1_float64.asc",
3444                        "/raster/statisticsRas4_float64.asc",
3445                        "/raster/rnd_percentile_raster1_float64.tif",
3446                        "/raster/rnd_percentile_raster2_float64.tif",
3447                        "/raster/rnd_percentile_raster3_float64.tif",
3448                        "/raster/rnd_percentile_raster4_float64.tif",
3449                        "/raster/rnd_percentile_raster5_float64.tif"} )
3450       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3451       << 56.78
3452       << 1
3453       << false
3454       << -9999.0
3455       << QStringLiteral( "/percentRankByValue_exc_ignoreFalse_float64.tif" )
3456       << Qgis::DataType::Float32;
3457 
3458 
3459   /*
3460    * Testcase 5: inc, ignoreNoData = false, dataType = Byte
3461    */
3462   QTest::newRow( "testcase_5" )
3463       << QStringList( {"/raster/rnd_percentile_raster1_byte.tif",
3464                        "/raster/rnd_percentile_raster2_byte.tif",
3465                        "/raster/rnd_percentile_raster3_byte.tif",
3466                        "/raster/rnd_percentile_raster4_byte.tif",
3467                        "/raster/rnd_percentile_raster5_byte.tif"} )
3468       << QStringLiteral( "/raster/rnd_percentile_raster1_byte.tif" )
3469       << 19.0
3470       << 0
3471       << false
3472       << 200.0
3473       << QStringLiteral( "/percentRankByValue_inc_ignoreFalse_byte.tif" )
3474       << Qgis::DataType::Float32;
3475 }
3476 
3477 
percentrankByValue()3478 void TestQgsProcessingAlgs::percentrankByValue()
3479 {
3480   QFETCH( QStringList, inputRasters );
3481   QFETCH( QString, referenceLayer );
3482   QFETCH( double, value );
3483   QFETCH( int, method );
3484   QFETCH( bool, ignoreNoData );
3485   QFETCH( double, noDataValue );
3486   QFETCH( QString, expectedRaster );
3487   QFETCH( Qgis::DataType, expectedDataType );
3488 
3489   //prepare input params
3490   QgsProject p;
3491   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:cellstackpercentrankfromvalue" ) ) );
3492 
3493   const QString myDataPath( TEST_DATA_DIR ); //defined in CMakeLists.txt
3494 
3495   QStringList inputDatasetPaths;
3496 
3497   for ( const auto &raster : inputRasters )
3498   {
3499     inputDatasetPaths << myDataPath + raster;
3500   }
3501 
3502   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
3503 
3504   //set project crs and ellipsoid from input layer
3505   p.setCrs( inputRasterLayer1->crs(), true );
3506 
3507   //set project after layer has been added so that transform context/ellipsoid from crs is also set
3508   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
3509   context->setProject( &p );
3510 
3511   QVariantMap parameters;
3512 
3513   parameters.insert( QStringLiteral( "INPUT" ), inputDatasetPaths );
3514   parameters.insert( QStringLiteral( "VALUE" ), value );
3515   parameters.insert( QStringLiteral( "METHOD" ), method );
3516   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
3517   parameters.insert( QStringLiteral( "OUTPUT_NODATA_VALUE" ), noDataValue );
3518   parameters.insert( QStringLiteral( "REFERENCE_LAYER" ), QString( myDataPath + referenceLayer ) );
3519   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
3520 
3521   //prepare expectedRaster
3522   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/expected_cellStackPercentrankFromValue/" + expectedRaster, "expectedDataset", "gdal" );
3523   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
3524   QgsRasterIterator expectedIter( expectedInterface.get() );
3525   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
3526 
3527   //run alg...
3528   bool ok = false;
3529   QgsProcessingFeedback feedback;
3530   QVariantMap results;
3531 
3532   results = alg->run( parameters, *context, &feedback, &ok );
3533   QVERIFY( ok );
3534 
3535   //...and check results with expected datasets
3536   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
3537   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
3538 
3539   QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
3540   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
3541   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
3542 
3543   QgsRasterIterator outputIter( outputInterface.get() );
3544   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
3545   int outputIterLeft = 0;
3546   int outputIterTop = 0;
3547   int outputIterCols = 0;
3548   int outputIterRows = 0;
3549   int expectedIterLeft = 0;
3550   int expectedIterTop = 0;
3551   int expectedIterCols = 0;
3552   int expectedIterRows = 0;
3553 
3554   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
3555   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
3556 
3557   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
3558           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
3559   {
3560     for ( int row = 0; row < expectedIterRows; row++ )
3561     {
3562       for ( int column = 0; column < expectedIterCols; column++ )
3563       {
3564         const double roundedExpectedValue = std::round( expectedRasterBlock->value( row, column ) * 4 ) * 4;
3565         const double roundedOutputValue = std::round( outputRasterBlock->value( row, column ) * 4 ) * 4;
3566         QCOMPARE( roundedOutputValue, roundedExpectedValue );
3567       }
3568     }
3569   }
3570 }
3571 
rasterFrequencyByComparisonOperator_data()3572 void TestQgsProcessingAlgs::rasterFrequencyByComparisonOperator_data()
3573 {
3574   QTest::addColumn<QString>( "algName" );
3575   QTest::addColumn<QString>( "inputValueRaster" );
3576   QTest::addColumn<int>( "inputValueRasterBand" );
3577   QTest::addColumn<QStringList>( "inputRasters" );
3578   QTest::addColumn<bool>( "ignoreNoData" );
3579   QTest::addColumn<QString>( "expectedRaster" );
3580   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
3581 
3582   /*
3583    * Testcase 1 - equal to frequency: don't ignore NoData
3584    */
3585   QTest::newRow( "testcase_1" )
3586       << QStringLiteral( "native:equaltofrequency" )
3587       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3588       << 1
3589       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3590       << false
3591       << QStringLiteral( "/expected_equalToFrequency/equalToFrequencyTest1.tif" )
3592       << Qgis::DataType::Int32;
3593   /*
3594    * Testcase 2 - equal to frequency: ignore NoData
3595    */
3596   QTest::newRow( "testcase_2" )
3597       << QStringLiteral( "native:equaltofrequency" )
3598       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3599       << 1
3600       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3601       << true
3602       << QStringLiteral( "/expected_equalToFrequency/equalToFrequencyTest2.tif" )
3603       << Qgis::DataType::Int32;
3604 
3605   /*
3606    * Testcase 3 - equal to frequency: NoData in value raster
3607    */
3608   QTest::newRow( "testcase_3" )
3609       << QStringLiteral( "native:equaltofrequency" )
3610       << QStringLiteral( "/raster/valueRas2_float64.asc" )
3611       << 1
3612       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3613       << false
3614       << QStringLiteral( "/expected_equalToFrequency/equalToFrequencyTest3.tif" )
3615       << Qgis::DataType::Int32;
3616 
3617   /*
3618    * Testcase 4 - equal to frequency: test with random byte raster
3619    */
3620   QTest::newRow( "testcase_4" )
3621       << QStringLiteral( "native:equaltofrequency" )
3622       << QStringLiteral( "/raster/valueRas3_float64.asc" )
3623       << 1
3624       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3625       << false
3626       << QStringLiteral( "/expected_equalToFrequency/equalToFrequencyTest4.tif" )
3627       << Qgis::DataType::Int32;
3628 
3629   /*
3630    * Testcase 5 - greater than frequency: don't ignore NoData
3631    */
3632   QTest::newRow( "testcase_5" )
3633       << QStringLiteral( "native:greaterthanfrequency" )
3634       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3635       << 1
3636       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3637       << false
3638       << QStringLiteral( "/expected_greaterThanFrequency/greaterThanFrequencyTest1.tif" )
3639       << Qgis::DataType::Int32;
3640   /*
3641    * Testcase 6 - greater than frequency: ignore NoData
3642    */
3643   QTest::newRow( "testcase_6" )
3644       << QStringLiteral( "native:greaterthanfrequency" )
3645       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3646       << 1
3647       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3648       << true
3649       << QStringLiteral( "/expected_greaterThanFrequency/greaterThanFrequencyTest2.tif" )
3650       << Qgis::DataType::Int32;
3651 
3652   /*
3653    * Testcase 7 - greater than frequency: NoData in value raster
3654    */
3655   QTest::newRow( "testcase_7" )
3656       << QStringLiteral( "native:greaterthanfrequency" )
3657       << QStringLiteral( "/raster/valueRas2_float64.asc" )
3658       << 1
3659       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3660       << false
3661       << QStringLiteral( "/expected_greaterThanFrequency/greaterThanFrequencyTest3.tif" )
3662       << Qgis::DataType::Int32;
3663 
3664   /*
3665    * Testcase 8 - greater than frequency: test with random byte raster
3666    */
3667   QTest::newRow( "testcase_8" )
3668       << QStringLiteral( "native:greaterthanfrequency" )
3669       << QStringLiteral( "/raster/valueRas3_float64.asc" )
3670       << 1
3671       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3672       << false
3673       << QStringLiteral( "/expected_greaterThanFrequency/greaterThanFrequencyTest4.tif" )
3674       << Qgis::DataType::Int32;
3675 
3676   /*
3677    * Testcase 9 - less than frequency: don't ignore NoData
3678    */
3679   QTest::newRow( "testcase_9" )
3680       << QStringLiteral( "native:lessthanfrequency" )
3681       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3682       << 1
3683       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3684       << false
3685       << QStringLiteral( "/expected_lessThanFrequency/lessThanFrequencyTest1.tif" )
3686       << Qgis::DataType::Int32;
3687   /*
3688    * Testcase 10 - greater than frequency: ignore NoData
3689    */
3690   QTest::newRow( "testcase_10" )
3691       << QStringLiteral( "native:lessthanfrequency" )
3692       << QStringLiteral( "/raster/valueRas1_float64.asc" )
3693       << 1
3694       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3695       << true
3696       << QStringLiteral( "/expected_lessThanFrequency/lessThanFrequencyTest2.tif" )
3697       << Qgis::DataType::Int32;
3698 
3699   /*
3700    * Testcase 11 - less than frequency: NoData in value raster
3701    */
3702   QTest::newRow( "testcase_11" )
3703       << QStringLiteral( "native:lessthanfrequency" )
3704       << QStringLiteral( "/raster/valueRas2_float64.asc" )
3705       << 1
3706       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3707       << false
3708       << QStringLiteral( "/expected_lessThanFrequency/lessThanFrequencyTest3.tif" )
3709       << Qgis::DataType::Int32;
3710 
3711   /*
3712    * Testcase 12 - less than frequency: test with random byte raster
3713    */
3714   QTest::newRow( "testcase_12" )
3715       << QStringLiteral( "native:lessthanfrequency" )
3716       << QStringLiteral( "/raster/valueRas3_float64.asc" )
3717       << 1
3718       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3719       << false
3720       << QStringLiteral( "/expected_lessThanFrequency/lessThanFrequencyTest4.tif" )
3721       << Qgis::DataType::Int32;
3722 }
3723 
rasterFrequencyByComparisonOperator()3724 void TestQgsProcessingAlgs::rasterFrequencyByComparisonOperator()
3725 {
3726   QFETCH( QString, algName );
3727   QFETCH( QString, inputValueRaster );
3728   QFETCH( int, inputValueRasterBand );
3729   QFETCH( QStringList, inputRasters );
3730   QFETCH( bool, ignoreNoData );
3731   QFETCH( QString, expectedRaster );
3732   QFETCH( Qgis::DataType, expectedDataType );
3733 
3734   //prepare input params
3735   QgsProject p;
3736   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( algName ) );
3737 
3738   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
3739 
3740   QStringList inputDatasetPaths;
3741 
3742   for ( const auto &raster : inputRasters )
3743   {
3744     inputDatasetPaths << myDataPath + raster;
3745   }
3746 
3747   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
3748 
3749   //set project crs and ellipsoid from input layer
3750   p.setCrs( inputRasterLayer1->crs(), true );
3751 
3752   //set project after layer has been added so that transform context/ellipsoid from crs is also set
3753   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
3754   context->setProject( &p );
3755 
3756   QVariantMap parameters;
3757 
3758   parameters.insert( QStringLiteral( "INPUT_VALUE_RASTER" ), QString( myDataPath + inputValueRaster ) );
3759   parameters.insert( QStringLiteral( "INPUT_VALUE_RASTER_BAND" ), inputValueRasterBand );
3760   parameters.insert( QStringLiteral( "INPUT_RASTERS" ), inputDatasetPaths );
3761   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
3762   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
3763 
3764   //prepare expectedRaster
3765   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images" + expectedRaster, "expectedDataset", "gdal" );
3766   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
3767   QgsRasterIterator expectedIter( expectedInterface.get() );
3768   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
3769 
3770   //run alg...
3771 
3772   bool ok = false;
3773   QgsProcessingFeedback feedback;
3774   QVariantMap results;
3775 
3776   results = alg->run( parameters, *context, &feedback, &ok );
3777   QVERIFY( ok );
3778 
3779   //...and check results with expected datasets
3780   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
3781   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
3782 
3783   QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
3784   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
3785   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
3786 
3787   QgsRasterIterator outputIter( outputInterface.get() );
3788   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
3789   int outputIterLeft = 0;
3790   int outputIterTop = 0;
3791   int outputIterCols = 0;
3792   int outputIterRows = 0;
3793   int expectedIterLeft = 0;
3794   int expectedIterTop = 0;
3795   int expectedIterCols = 0;
3796   int expectedIterRows = 0;
3797 
3798   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
3799   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
3800 
3801   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
3802           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
3803   {
3804     for ( int row = 0; row < expectedIterRows; row++ )
3805     {
3806       for ( int column = 0; column < expectedIterCols; column++ )
3807       {
3808         const double expectedValue = expectedRasterBlock->value( row, column );
3809         const double outputValue = outputRasterBlock->value( row, column );
3810         QCOMPARE( outputValue, expectedValue );
3811 
3812         const Qgis::DataType outputDataType = outputRasterBlock->dataType();
3813         QCOMPARE( outputDataType, expectedDataType );
3814       }
3815     }
3816   }
3817 }
3818 
3819 
rasterLocalPosition_data()3820 void TestQgsProcessingAlgs::rasterLocalPosition_data()
3821 {
3822   QTest::addColumn<QString>( "algName" );
3823   QTest::addColumn<QStringList>( "inputRasters" );
3824   QTest::addColumn<QString>( "referenceRaster" );
3825   QTest::addColumn<bool>( "ignoreNoData" );
3826   QTest::addColumn<QString>( "expectedRaster" );
3827 
3828   QTest::newRow( "testcase_1" )
3829       << QStringLiteral( "native:lowestpositioninrasterstack" )
3830       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3831       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3832       << false
3833       << QStringLiteral( "/expected_lowestPosition/expectedLowestPositionTest1.tif" );
3834 
3835   QTest::newRow( "testcase_2" )
3836       << QStringLiteral( "native:lowestpositioninrasterstack" )
3837       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3838       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3839       << true
3840       << QStringLiteral( "/expected_lowestPosition/expectedLowestPositionTest2.tif" );
3841 
3842   QTest::newRow( "testcase_3" )
3843       << QStringLiteral( "native:lowestpositioninrasterstack" )
3844       << QStringList( {"/raster/statisticsRas2_float64.asc", "/raster/statisticsRas1_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3845       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3846       << false
3847       << QStringLiteral( "/expected_lowestPosition/expectedLowestPositionTest3.tif" );
3848 
3849   QTest::newRow( "testcase_4" )
3850       << QStringLiteral( "native:highestpositioninrasterstack" )
3851       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3852       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3853       << false
3854       << QStringLiteral( "/expected_highestPosition/expectedHighestPositionTest1.tif" );
3855 
3856   QTest::newRow( "testcase_5" )
3857       << QStringLiteral( "native:highestpositioninrasterstack" )
3858       << QStringList( {"/raster/statisticsRas1_float64.asc", "/raster/statisticsRas2_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3859       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3860       << true
3861       << QStringLiteral( "/expected_highestPosition/expectedHighestPositionTest2.tif" );
3862 
3863   QTest::newRow( "testcase_6" )
3864       << QStringLiteral( "native:highestpositioninrasterstack" )
3865       << QStringList( {"/raster/statisticsRas2_float64.asc", "/raster/statisticsRas1_float64.asc", "/raster/statisticsRas3_float64.asc"} )
3866       << QStringLiteral( "/raster/statisticsRas1_float64.asc" )
3867       << false
3868       << QStringLiteral( "/expected_highestPosition/expectedHighestPositionTest3.tif" );
3869 }
3870 
rasterLocalPosition()3871 void TestQgsProcessingAlgs::rasterLocalPosition()
3872 {
3873   QFETCH( QString, algName );
3874   QFETCH( QStringList, inputRasters );
3875   QFETCH( QString, referenceRaster );
3876   QFETCH( bool, ignoreNoData );
3877   QFETCH( QString, expectedRaster );
3878 
3879   //prepare input params
3880   QgsProject p;
3881   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( algName ) );
3882 
3883   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
3884 
3885   QStringList inputDatasetPaths;
3886 
3887   for ( const auto &raster : inputRasters )
3888   {
3889     inputDatasetPaths << myDataPath + raster;
3890   }
3891 
3892   std::unique_ptr<QgsRasterLayer> inputRasterLayer1 = std::make_unique< QgsRasterLayer >( myDataPath + inputRasters[0], "inputDataset", "gdal" );
3893 
3894   //set project crs and ellipsoid from input layer
3895   p.setCrs( inputRasterLayer1->crs(), true );
3896 
3897   //set project after layer has been added so that transform context/ellipsoid from crs is also set
3898   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
3899   context->setProject( &p );
3900 
3901   QVariantMap parameters;
3902 
3903   parameters.insert( QStringLiteral( "INPUT_RASTERS" ), inputDatasetPaths );
3904   parameters.insert( QStringLiteral( "REFERENCE_LAYER" ), QString( myDataPath + referenceRaster ) );
3905   parameters.insert( QStringLiteral( "IGNORE_NODATA" ), ignoreNoData );
3906   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
3907 
3908   //prepare expectedRaster
3909   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images" + expectedRaster, "expectedDataset", "gdal" );
3910   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
3911   QgsRasterIterator expectedIter( expectedInterface.get() );
3912   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
3913 
3914   //run alg...
3915   bool ok = false;
3916   QgsProcessingFeedback feedback;
3917   QVariantMap results;
3918 
3919   results = alg->run( parameters, *context, &feedback, &ok );
3920   QVERIFY( ok );
3921 
3922   //...and check results with expected datasets
3923   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
3924   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
3925 
3926   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
3927   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
3928 
3929   QgsRasterIterator outputIter( outputInterface.get() );
3930   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
3931   int outputIterLeft = 0;
3932   int outputIterTop = 0;
3933   int outputIterCols = 0;
3934   int outputIterRows = 0;
3935   int expectedIterLeft = 0;
3936   int expectedIterTop = 0;
3937   int expectedIterCols = 0;
3938   int expectedIterRows = 0;
3939 
3940   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
3941   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
3942 
3943   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
3944           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
3945   {
3946     for ( int row = 0; row < expectedIterRows; row++ )
3947     {
3948       for ( int column = 0; column < expectedIterCols; column++ )
3949       {
3950         const double expectedValue = expectedRasterBlock->value( row, column );
3951         const double outputValue = outputRasterBlock->value( row, column );
3952         QCOMPARE( outputValue, expectedValue );
3953 
3954         const Qgis::DataType outputDataType = outputRasterBlock->dataType();
3955         QCOMPARE( outputDataType, Qgis::DataType::Int32 );
3956       }
3957     }
3958   }
3959 }
3960 
roundRasterValues_data()3961 void TestQgsProcessingAlgs::roundRasterValues_data()
3962 {
3963   QTest::addColumn<QString>( "inputRaster" );
3964   QTest::addColumn<QString>( "expectedRaster" );
3965   QTest::addColumn<int>( "inputBand" );
3966   QTest::addColumn<int>( "roundingDirection" );
3967   QTest::addColumn<int>( "decimals" );
3968   QTest::addColumn<int>( "baseN" );
3969 
3970   /*
3971    * Testcase 1
3972    *
3973    * Integer Raster Layer
3974    * band = 1
3975    * roundingDirection = nearest
3976    * decimals = 2
3977    */
3978   QTest::newRow( "testcase 1" )
3979       << "/raster/dem.tif"
3980       << QStringLiteral( "/roundRasterValues_testcase1.tif" ) //no output expected: can't round integer
3981       << 1
3982       << 1
3983       << 2
3984       << 10;
3985 
3986   /*
3987    * Testcase 2
3988    *
3989    * WGS84 dem
3990    * band = 1
3991    * roundingDirection = up
3992    * decimals = 2
3993    */
3994   QTest::newRow( "testcase 2" )
3995       << "/raster/dem.tif"
3996       << QStringLiteral( "/roundRasterValues_testcase2.tif" )
3997       << 1
3998       << 0
3999       << 2
4000       << 10;
4001 
4002   /*
4003    * Testcase 3
4004    *
4005    * WGS84 dem
4006    * band = 1
4007    * roundingDirection = down
4008    * decimals = 1
4009    */
4010   QTest::newRow( "testcase 3" )
4011       << "/raster/dem.tif"
4012       << QStringLiteral( "/roundRasterValues_testcase3.tif" )
4013       << 1
4014       << 2
4015       << 1
4016       << 10;
4017 
4018   /*
4019    * Testcase 4
4020    *
4021    * WGS84 dem
4022    * band = 1
4023    * roundingDirection = nearest
4024    * decimals = -1
4025    */
4026   QTest::newRow( "testcase 4" )
4027       << "/raster/dem.tif"
4028       << QStringLiteral( "/roundRasterValues_testcase4.tif" )
4029       << 1
4030       << 1
4031       << -1
4032       << 10;
4033 
4034   /*
4035    * Testcase 5
4036    *
4037    * WGS84 dem
4038    * band = 1
4039    * roundingDirection = up
4040    * decimals = -1
4041    */
4042   QTest::newRow( "testcase 5" )
4043       << "/raster/dem.tif"
4044       << QStringLiteral( "/roundRasterValues_testcase5.tif" )
4045       << 1
4046       << 0
4047       << -1
4048       << 10;
4049 
4050   /*
4051    * Testcase 6
4052    *
4053    * WGS84 dem
4054    * band = 1
4055    * roundingDirection = down
4056    * decimals = -1
4057    */
4058   QTest::newRow( "testcase 6" )
4059       << "/raster/dem.tif"
4060       << QStringLiteral( "/roundRasterValues_testcase6.tif" )
4061       << 1
4062       << 2
4063       << -1
4064       << 10;
4065 
4066   /*
4067    * Testcase 7
4068    *
4069    * WGS84 int
4070    * band = 1
4071    * roundingDirection = nearest
4072    * decimals = 2
4073    */
4074   QTest::newRow( "testcase 7" )
4075       << "/raster/band1_int16_noct_epsg4326.tif"
4076       << QStringLiteral( "/roundRasterValues_testcase7.tif" )
4077       << 1
4078       << 1
4079       << -1
4080       << 10;
4081 
4082   /*
4083    * Testcase 8
4084    *
4085    * WGS84 int
4086    * band = 1
4087    * roundingDirection = nearest
4088    * decimals = -1
4089    */
4090   QTest::newRow( "testcase 8" )
4091       << "/raster/band1_int16_noct_epsg4326.tif"
4092       << QStringLiteral( "/roundRasterValues_testcase8.tif" )
4093       << 1
4094       << 1
4095       << -1
4096       << 10;
4097 
4098 }
4099 
roundRasterValues()4100 void TestQgsProcessingAlgs::roundRasterValues()
4101 {
4102   QFETCH( QString, inputRaster );
4103   QFETCH( QString, expectedRaster );
4104   QFETCH( int, inputBand );
4105   QFETCH( int, roundingDirection );
4106   QFETCH( int, decimals );
4107   QFETCH( int, baseN );
4108 
4109   //prepare input params
4110   QgsProject p;
4111   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:roundrastervalues" ) ) );
4112 
4113   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
4114 
4115   std::unique_ptr<QgsRasterLayer> inputRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + inputRaster, "inputDataset", "gdal" );
4116 
4117   //set project crs and ellipsoid from input layer
4118   p.setCrs( inputRasterLayer->crs(), true );
4119 
4120   //set project after layer has been added so that transform context/ellipsoid from crs is also set
4121   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4122   context->setProject( &p );
4123 
4124   QVariantMap parameters;
4125 
4126   parameters.insert( QStringLiteral( "INPUT" ), QString( myDataPath + inputRaster ) );
4127   parameters.insert( QStringLiteral( "BAND" ), inputBand );
4128   parameters.insert( QStringLiteral( "ROUNDING_DIRECTION" ), roundingDirection );
4129   parameters.insert( QStringLiteral( "DECIMAL_PLACES" ), decimals );
4130   parameters.insert( QStringLiteral( "BASE_N" ), baseN );
4131   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4132 
4133   //prepare expectedRaster
4134   std::unique_ptr<QgsRasterLayer> expectedRasterLayer = std::make_unique< QgsRasterLayer >( myDataPath + "/control_images/roundRasterValues/" + expectedRaster, "expectedDataset", "gdal" );
4135   std::unique_ptr< QgsRasterInterface > expectedInterface( expectedRasterLayer->dataProvider()->clone() );
4136   QgsRasterIterator expectedIter( expectedInterface.get() );
4137   expectedIter.startRasterRead( 1, expectedRasterLayer->width(), expectedRasterLayer->height(), expectedInterface->extent() );
4138 
4139   //run alg...
4140 
4141   bool ok = false;
4142   QgsProcessingFeedback feedback;
4143   QVariantMap results;
4144 
4145   results = alg->run( parameters, *context, &feedback, &ok );
4146   QVERIFY( ok );
4147 
4148   //...and check results with expected datasets
4149   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
4150   std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
4151 
4152   QCOMPARE( outputRaster->width(), expectedRasterLayer->width() );
4153   QCOMPARE( outputRaster->height(), expectedRasterLayer->height() );
4154 
4155   QgsRasterIterator outputIter( outputInterface.get() );
4156   outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
4157   int outputIterLeft = 0;
4158   int outputIterTop = 0;
4159   int outputIterCols = 0;
4160   int outputIterRows = 0;
4161   int expectedIterLeft = 0;
4162   int expectedIterTop = 0;
4163   int expectedIterCols = 0;
4164   int expectedIterRows = 0;
4165 
4166   std::unique_ptr< QgsRasterBlock > outputRasterBlock;
4167   std::unique_ptr< QgsRasterBlock > expectedRasterBlock;
4168 
4169   while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) &&
4170           expectedIter.readNextRasterPart( 1, expectedIterCols, expectedIterRows, expectedRasterBlock, expectedIterLeft, expectedIterTop ) )
4171   {
4172     for ( int row = 0; row < expectedIterRows; row++ )
4173     {
4174       for ( int column = 0; column < expectedIterCols; column++ )
4175       {
4176         const double expectedValue = expectedRasterBlock->value( row, column );
4177         const double outputValue = outputRasterBlock->value( row, column );
4178         QCOMPARE( outputValue, expectedValue );
4179       }
4180     }
4181   }
4182 }
4183 
layoutMapExtent()4184 void TestQgsProcessingAlgs::layoutMapExtent()
4185 {
4186   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:printlayoutmapextenttolayer" ) ) );
4187   QVERIFY( alg != nullptr );
4188 
4189   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4190   QgsProject p;
4191   context->setProject( &p );
4192 
4193   QVariantMap parameters;
4194   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "l" ) );
4195   parameters.insert( QStringLiteral( "MAP" ), QStringLiteral( "m" ) );
4196   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4197 
4198   bool ok = false;
4199   QgsProcessingFeedback feedback;
4200   QVariantMap results;
4201   // no layout
4202   results = alg->run( parameters, *context, &feedback, &ok );
4203   QVERIFY( !ok );
4204 
4205   QgsPrintLayout *layout = new QgsPrintLayout( &p );
4206   layout->setName( QStringLiteral( "l" ) );
4207   p.layoutManager()->addLayout( layout );
4208 
4209   // no matching map
4210   results = alg->run( parameters, *context, &feedback, &ok );
4211   QVERIFY( !ok );
4212 
4213   QgsLayoutItemMap *map = new QgsLayoutItemMap( layout );
4214   layout->addLayoutItem( map );
4215   map->setId( QStringLiteral( "m" ) );
4216   map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
4217   map->attemptSetSceneRect( QRectF( 100, 100, 150, 180 ) );
4218   map->zoomToExtent( QgsRectangle( 10000, 100000, 60000, 180000 ) );
4219   map->setMapRotation( 45 );
4220   map->setScale( 10000 );
4221   QgsLayoutItemMap *map2 = new QgsLayoutItemMap( layout );
4222   layout->addLayoutItem( map2 );
4223   map2->setId( QStringLiteral( "m2" ) );
4224   map2->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3785" ) ) );
4225   map2->attemptSetSceneRect( QRectF( 100, 100, 50, 80 ) );
4226   map2->zoomToExtent( QgsRectangle( 10000, 100000, 5000, 8000 ) );
4227   map2->setMapRotation( 0 );
4228   map2->setScale( 1000 );
4229 
4230   results = alg->run( parameters, *context, &feedback, &ok );
4231   QVERIFY( ok );
4232 
4233   QCOMPARE( results.value( QStringLiteral( "WIDTH" ) ).toDouble(), 150.0 );
4234   QCOMPARE( results.value( QStringLiteral( "HEIGHT" ) ).toDouble(), 180.0 );
4235   QCOMPARE( results.value( QStringLiteral( "SCALE" ) ).toDouble(), 10000.0 );
4236   QCOMPARE( results.value( QStringLiteral( "ROTATION" ) ).toDouble(), 45.0 );
4237 
4238   QgsFeature f;
4239   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:3111" ) );
4240   QVERIFY( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures().nextFeature( f ) );
4241   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "m" ) );
4242   QCOMPARE( f.attribute( 1 ).toDouble(), 150.0 );
4243   QCOMPARE( f.attribute( 2 ).toDouble(), 180.0 );
4244   QCOMPARE( f.attribute( 3 ).toDouble(), 10000.0 );
4245   QCOMPARE( f.attribute( 4 ).toDouble(), 45.0 );
4246   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((33833 140106, 34894 141167, 36167 139894, 35106 138833, 33833 140106))" ) );
4247 
4248   // all maps
4249   parameters.remove( QStringLiteral( "MAP" ) );
4250   results = alg->run( parameters, *context, &feedback, &ok );
4251   QVERIFY( ok );
4252 
4253   QVERIFY( !results.value( QStringLiteral( "WIDTH" ) ).isValid() );
4254   QVERIFY( !results.value( QStringLiteral( "HEIGHT" ) ).isValid() );
4255   QVERIFY( !results.value( QStringLiteral( "SCALE" ) ).isValid() );
4256   QVERIFY( !results.value( QStringLiteral( "ROTATION" ) ).isValid() );
4257 
4258   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:3785" ) );
4259   QgsFeatureIterator it = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures();
4260   QgsFeature f1;
4261   QVERIFY( it.nextFeature( f1 ) );
4262   QgsFeature f2;
4263   QVERIFY( it.nextFeature( f2 ) );
4264   f = f1.attribute( 0 ).toString() == QLatin1String( "m" ) ? f1 : f2;
4265   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "m" ) );
4266   QCOMPARE( f.attribute( 1 ).toDouble(), 150.0 );
4267   QCOMPARE( f.attribute( 2 ).toDouble(), 180.0 );
4268   QCOMPARE( f.attribute( 3 ).toDouble(), 10000.0 );
4269   QCOMPARE( f.attribute( 4 ).toDouble(), 45.0 );
4270   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((12077408 -7108521, 12079627 -7107575, 12080760 -7110245, 12078540 -7111191, 12077408 -7108521))" ) );
4271   f = f1.attribute( 0 ).toString() == QLatin1String( "m" ) ? f2 : f1;
4272   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "m2" ) );
4273   QCOMPARE( f.attribute( 1 ).toDouble(), 50.0 );
4274   QCOMPARE( f.attribute( 2 ).toDouble(), 80.0 );
4275   QGSCOMPARENEAR( f.attribute( 3 ).toDouble(), 1000.0, 0.0001 );
4276   QCOMPARE( f.attribute( 4 ).toDouble(), 0.0 );
4277   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((7475 54040, 7525 54040, 7525 53960, 7475 53960, 7475 54040))" ) );
4278 
4279   // crs override
4280   parameters.insert( QStringLiteral( "CRS" ),  QStringLiteral( "EPSG:3111" ) );
4281   results = alg->run( parameters, *context, &feedback, &ok );
4282   QVERIFY( ok );
4283 
4284   QVERIFY( !results.value( QStringLiteral( "WIDTH" ) ).isValid() );
4285   QVERIFY( !results.value( QStringLiteral( "HEIGHT" ) ).isValid() );
4286   QVERIFY( !results.value( QStringLiteral( "SCALE" ) ).isValid() );
4287   QVERIFY( !results.value( QStringLiteral( "ROTATION" ) ).isValid() );
4288 
4289   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:3111" ) );
4290   it = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures();
4291   QVERIFY( it.nextFeature( f1 ) );
4292   QVERIFY( it.nextFeature( f2 ) );
4293   f = f1.attribute( 0 ).toString() == QLatin1String( "m" ) ? f1 : f2;
4294   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "m" ) );
4295   QCOMPARE( f.attribute( 1 ).toDouble(), 150.0 );
4296   QCOMPARE( f.attribute( 2 ).toDouble(), 180.0 );
4297   QCOMPARE( f.attribute( 3 ).toDouble(), 10000.0 );
4298   QCOMPARE( f.attribute( 4 ).toDouble(), 45.0 );
4299   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((33833 140106, 34894 141167, 36167 139894, 35106 138833, 33833 140106))" ) );
4300   f = f1.attribute( 0 ).toString() == QLatin1String( "m" ) ? f2 : f1;
4301   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "m2" ) );
4302   QCOMPARE( f.attribute( 1 ).toDouble(), 50.0 );
4303   QCOMPARE( f.attribute( 2 ).toDouble(), 80.0 );
4304   QGSCOMPARENEAR( f.attribute( 3 ).toDouble(), 1000.0, 0.0001 );
4305   QCOMPARE( f.attribute( 4 ).toDouble(), 0.0 );
4306   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((-10399464 -5347896, -10399461 -5347835, -10399364 -5347840, -10399367 -5347901, -10399464 -5347896))" ) );
4307 
4308 }
4309 
styleFromProject()4310 void TestQgsProcessingAlgs::styleFromProject()
4311 {
4312   QgsProject p;
4313   QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
4314   QVERIFY( vl->isValid() );
4315   p.addMapLayer( vl );
4316   QgsSimpleMarkerSymbolLayer *simpleMarkerLayer = new QgsSimpleMarkerSymbolLayer();
4317   QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol();
4318   markerSymbol->changeSymbolLayer( 0, simpleMarkerLayer );
4319   vl->setRenderer( new QgsSingleSymbolRenderer( markerSymbol ) );
4320   // rule based renderer
4321   QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4322   QVERIFY( vl2->isValid() );
4323   p.addMapLayer( vl2 );
4324   QgsSymbol *s1 = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry );
4325   s1->setColor( QColor( 0, 255, 0 ) );
4326   QgsSymbol *s2 = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry );
4327   s2->setColor( QColor( 0, 255, 255 ) );
4328   QgsRuleBasedRenderer::Rule *rootRule = new QgsRuleBasedRenderer::Rule( nullptr );
4329   QgsRuleBasedRenderer::Rule *rule2 = new QgsRuleBasedRenderer::Rule( s1, 0, 0, QStringLiteral( "fld >= 5 and fld <= 20" ) );
4330   rootRule->appendChild( rule2 );
4331   QgsRuleBasedRenderer::Rule *rule3 = new QgsRuleBasedRenderer::Rule( s2, 0, 0, QStringLiteral( "fld <= 10" ) );
4332   rule2->appendChild( rule3 );
4333   vl2->setRenderer( new QgsRuleBasedRenderer( rootRule ) );
4334   // labeling
4335   QgsPalLayerSettings settings;
4336   settings.fieldName = QStringLiteral( "Class" );
4337   vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
4338   // raster layer
4339   QgsRasterLayer *rl = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/tenbytenraster.asc",
4340       QStringLiteral( "rl" ) );
4341   QVERIFY( rl->isValid() );
4342   p.addMapLayer( rl );
4343 
4344   QgsRasterShader *rasterShader = new QgsRasterShader();
4345   QgsColorRampShader *colorRampShader = new QgsColorRampShader();
4346   colorRampShader->setColorRampType( QgsColorRampShader::Interpolated );
4347   colorRampShader->setSourceColorRamp( new QgsGradientColorRamp( QColor( 255, 255, 0 ), QColor( 255, 0, 255 ) ) );
4348   rasterShader->setRasterShaderFunction( colorRampShader );
4349   QgsSingleBandPseudoColorRenderer *r = new QgsSingleBandPseudoColorRenderer( rl->dataProvider(), 1, rasterShader );
4350   rl->setRenderer( r );
4351 
4352   // with layout
4353   QgsPrintLayout *l = new QgsPrintLayout( &p );
4354   l->setName( QStringLiteral( "test layout" ) );
4355   l->initializeDefaults();
4356   QgsLayoutItemScaleBar *scalebar = new QgsLayoutItemScaleBar( l );
4357   scalebar->attemptSetSceneRect( QRectF( 20, 180, 50, 20 ) );
4358   l->addLayoutItem( scalebar );
4359   scalebar->setTextFormat( QgsTextFormat::fromQFont( QgsFontUtils::getStandardTestFont() ) );
4360 
4361   p.layoutManager()->addLayout( l );
4362 
4363   // with annotations
4364   QgsTextAnnotation *annotation = new QgsTextAnnotation();
4365   QgsSymbol *a1 = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry );
4366   a1->setColor( QColor( 0, 200, 0 ) );
4367   annotation->setMarkerSymbol( static_cast< QgsMarkerSymbol * >( a1 ) );
4368   QgsSymbol *a2 = QgsSymbol::defaultSymbol( QgsWkbTypes::PolygonGeometry );
4369   a2->setColor( QColor( 200, 200, 0 ) );
4370   annotation->setFillSymbol( static_cast< QgsFillSymbol * >( a2 ) );
4371   p.annotationManager()->addAnnotation( annotation );
4372 
4373   // ok, run alg
4374   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:stylefromproject" ) ) );
4375   QVERIFY( alg != nullptr );
4376 
4377   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4378   context->setProject( &p );
4379 
4380   QVariantMap parameters;
4381   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4382 
4383   bool ok = false;
4384   QgsProcessingFeedback feedback;
4385   QVariantMap results;
4386   results = alg->run( parameters, *context, &feedback, &ok );
4387   QVERIFY( ok );
4388   QCOMPARE( results.value( QStringLiteral( "SYMBOLS" ) ).toInt(), 6 );
4389   QCOMPARE( results.value( QStringLiteral( "COLORRAMPS" ) ).toInt(), 1 );
4390   QCOMPARE( results.value( QStringLiteral( "TEXTFORMATS" ) ).toInt(), 1 );
4391   QCOMPARE( results.value( QStringLiteral( "LABELSETTINGS" ) ).toInt(), 1 );
4392 
4393   // read style file back in
4394   QgsStyle s;
4395   s.createMemoryDatabase();
4396   QVERIFY( s.importXml( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
4397   QCOMPARE( s.symbolCount(), 6 );
4398   QVERIFY( s.symbolNames().contains( QStringLiteral( "Annotation Fill" ) ) );
4399   QVERIFY( s.symbolNames().contains( QStringLiteral( "Annotation Marker" ) ) );
4400   QVERIFY( s.symbolNames().contains( QStringLiteral( "test layout Page" ) ) );
4401   QVERIFY( s.symbolNames().contains( QStringLiteral( "vl" ) ) );
4402   QVERIFY( s.symbolNames().contains( QStringLiteral( "vl2" ) ) );
4403   QVERIFY( s.symbolNames().contains( QStringLiteral( "vl2 (2)" ) ) );
4404   QCOMPARE( s.colorRampCount(), 1 );
4405   QVERIFY( s.colorRampNames().contains( QStringLiteral( "rl" ) ) );
4406   QCOMPARE( s.textFormatCount(), 1 );
4407   QVERIFY( s.textFormatNames().contains( QStringLiteral( "test layout <Scalebar>" ) ) );
4408   QCOMPARE( s.labelSettingsCount(), 1 );
4409   QVERIFY( s.labelSettingsNames().contains( QStringLiteral( "vl" ) ) );
4410 
4411   // using a project path
4412   QTemporaryFile tmpFile;
4413   tmpFile.open();
4414   tmpFile.close();
4415   QVERIFY( p.write( tmpFile.fileName() ) );
4416   p.clear();
4417   parameters.insert( QStringLiteral( "INPUT" ), tmpFile.fileName() );
4418   ok = false;
4419   results = alg->run( parameters, *context, &feedback, &ok );
4420   QVERIFY( ok );
4421   QCOMPARE( results.value( QStringLiteral( "SYMBOLS" ) ).toInt(), 6 );
4422   // this should be 1, but currently raster layers aren't supported -
4423   // we first need to allow raster renderers to be read and restored for invalid layer sources
4424   QCOMPARE( results.value( QStringLiteral( "COLORRAMPS" ) ).toInt(), 0 );
4425   QCOMPARE( results.value( QStringLiteral( "TEXTFORMATS" ) ).toInt(), 1 );
4426   QCOMPARE( results.value( QStringLiteral( "LABELSETTINGS" ) ).toInt(), 1 );
4427 }
4428 
combineStyles()4429 void TestQgsProcessingAlgs::combineStyles()
4430 {
4431   QgsStyle s1;
4432   s1.createMemoryDatabase();
4433   QgsStyle s2;
4434   s2.createMemoryDatabase();
4435 
4436   QgsSimpleMarkerSymbolLayer *simpleMarkerLayer = new QgsSimpleMarkerSymbolLayer();
4437   QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol();
4438   markerSymbol->changeSymbolLayer( 0, simpleMarkerLayer );
4439   s1.addSymbol( QStringLiteral( "sym1" ), markerSymbol, true );
4440   s1.tagSymbol( QgsStyle::SymbolEntity, QStringLiteral( "sym1" ), QStringList() << QStringLiteral( "t1" ) << QStringLiteral( "t2" ) );
4441 
4442   QgsSymbol *sym1 = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry );
4443   s2.addSymbol( QStringLiteral( "sym2" ), sym1, true );
4444   QgsSymbol *sym2 = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry );
4445   s2.addSymbol( QStringLiteral( "sym1" ), sym2, true );
4446 
4447   QgsPalLayerSettings settings;
4448   settings.fieldName = QStringLiteral( "Class" );
4449   s1.addLabelSettings( QStringLiteral( "label1" ), settings, true );
4450 
4451   s2.addColorRamp( QStringLiteral( "ramp1" ), new QgsGradientColorRamp( QColor( 255, 255, 0 ), QColor( 255, 0, 255 ) ), true );
4452   s2.addTextFormat( QStringLiteral( "format2" ), QgsTextFormat::fromQFont( QgsFontUtils::getStandardTestFont() ), true );
4453 
4454   QTemporaryFile tmpFile;
4455   tmpFile.open();
4456   tmpFile.close();
4457   QVERIFY( s1.exportXml( tmpFile.fileName() ) );
4458   QTemporaryFile tmpFile2;
4459   tmpFile2.open();
4460   tmpFile2.close();
4461   QVERIFY( s2.exportXml( tmpFile2.fileName() ) );
4462 
4463   // ok, run alg
4464   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:combinestyles" ) ) );
4465   QVERIFY( alg != nullptr );
4466 
4467   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4468 
4469   QVariantMap parameters;
4470   parameters.insert( QStringLiteral( "INPUT" ), QStringList() << tmpFile.fileName() << tmpFile2.fileName() );
4471   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4472 
4473   bool ok = false;
4474   QgsProcessingFeedback feedback;
4475   QVariantMap results;
4476   results = alg->run( parameters, *context, &feedback, &ok );
4477   QVERIFY( ok );
4478   QCOMPARE( results.value( QStringLiteral( "SYMBOLS" ) ).toInt(), 3 );
4479   QCOMPARE( results.value( QStringLiteral( "COLORRAMPS" ) ).toInt(), 1 );
4480   QCOMPARE( results.value( QStringLiteral( "TEXTFORMATS" ) ).toInt(), 1 );
4481   QCOMPARE( results.value( QStringLiteral( "LABELSETTINGS" ) ).toInt(), 1 );
4482 
4483   // check result
4484   QgsStyle s;
4485   s.createMemoryDatabase();
4486   QVERIFY( s.importXml( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
4487   QCOMPARE( s.symbolCount(), 3 );
4488   QVERIFY( s.symbolNames().contains( QStringLiteral( "sym1" ) ) );
4489   QVERIFY( s.symbolNames().contains( QStringLiteral( "sym2" ) ) );
4490   QVERIFY( s.symbolNames().contains( QStringLiteral( "sym1 (2)" ) ) );
4491   QCOMPARE( s.tagsOfSymbol( QgsStyle::SymbolEntity, QStringLiteral( "sym1" ) ).count(), 2 );
4492   QVERIFY( s.tagsOfSymbol( QgsStyle::SymbolEntity, QStringLiteral( "sym1" ) ).contains( QStringLiteral( "t1" ) ) );
4493   QVERIFY( s.tagsOfSymbol( QgsStyle::SymbolEntity, QStringLiteral( "sym1" ) ).contains( QStringLiteral( "t2" ) ) );
4494   QCOMPARE( s.colorRampCount(), 1 );
4495   QVERIFY( s.colorRampNames().contains( QStringLiteral( "ramp1" ) ) );
4496   QCOMPARE( s.textFormatCount(), 1 );
4497   QVERIFY( s.textFormatNames().contains( QStringLiteral( "format2" ) ) );
4498   QCOMPARE( s.labelSettingsCount(), 1 );
4499   QVERIFY( s.labelSettingsNames().contains( QStringLiteral( "label1" ) ) );
4500 }
4501 
bookmarksToLayer()4502 void TestQgsProcessingAlgs::bookmarksToLayer()
4503 {
4504   QgsApplication::bookmarkManager()->clear();
4505   // create some bookmarks
4506   QgsBookmark b1;
4507   b1.setName( QStringLiteral( "test name" ) );
4508   b1.setGroup( QStringLiteral( "test group" ) );
4509   b1.setExtent( QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) ) );
4510   QgsApplication::bookmarkManager()->addBookmark( b1 );
4511 
4512   QgsBookmark b2;
4513   b2.setName( QStringLiteral( "test name 2" ) );
4514   b2.setExtent( QgsReferencedRectangle( QgsRectangle( 16259461, -2477192, 16391255, -2372535 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) ) );
4515   QgsApplication::bookmarkManager()->addBookmark( b2 );
4516   QgsBookmark b3;
4517   b3.setName( QStringLiteral( "test name 3" ) );
4518   b3.setExtent( QgsReferencedRectangle( QgsRectangle( 11, 21, 31, 41 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) ) );
4519   QgsProject p;
4520   p.bookmarkManager()->addBookmark( b3 );
4521 
4522   // ok, run alg
4523   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:bookmarkstolayer" ) ) );
4524   QVERIFY( alg != nullptr );
4525 
4526   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4527   context->setProject( &p );
4528 
4529   QVariantMap parameters;
4530   parameters.insert( QStringLiteral( "SOURCE" ), QVariantList() << 0 );
4531   parameters.insert( QStringLiteral( "CRS" ), QStringLiteral( "EPSG:4326" ) );
4532   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4533 
4534   bool ok = false;
4535   QgsProcessingFeedback feedback;
4536   QVariantMap results;
4537   results = alg->run( parameters, *context, &feedback, &ok );
4538   QVERIFY( ok );
4539 
4540   // check result
4541   QgsFeature f;
4542   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:4326" ) );
4543   QgsFeatureIterator it = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures();
4544   QVERIFY( it.nextFeature( f ) );
4545   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name 3" ) );
4546   QCOMPARE( f.attribute( 1 ).toString(), QString() );
4547   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((11 21, 31 21, 31 41, 11 41, 11 21))" ) );
4548   QVERIFY( !it.nextFeature( f ) );
4549 
4550   // user bookmarks
4551   parameters.insert( QStringLiteral( "SOURCE" ), QVariantList() << 1 );
4552   ok = false;
4553   alg.reset( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:bookmarkstolayer" ) ) );
4554   results = alg->run( parameters, *context, &feedback, &ok );
4555   QVERIFY( ok );
4556   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:4326" ) );
4557   it = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures();
4558   QVERIFY( it.nextFeature( f ) );
4559   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name" ) );
4560   QCOMPARE( f.attribute( 1 ).toString(), QStringLiteral( "test group" ) );
4561   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((1 2, 3 2, 3 4, 1 4, 1 2))" ) );
4562   QVERIFY( it.nextFeature( f ) );
4563   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name 2" ) );
4564   QCOMPARE( f.attribute( 1 ).toString(), QString() );
4565   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22))" ) );
4566   QVERIFY( !it.nextFeature( f ) );
4567 
4568   // both
4569   parameters.insert( QStringLiteral( "SOURCE" ), QVariantList() << 0 << 1 );
4570   ok = false;
4571   alg.reset( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:bookmarkstolayer" ) ) );
4572   results = alg->run( parameters, *context, &feedback, &ok );
4573   QVERIFY( ok );
4574   QCOMPARE( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() )->crs().authid(), QStringLiteral( "EPSG:4326" ) );
4575   it = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->getFeatures();
4576   QVERIFY( it.nextFeature( f ) );
4577   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name 3" ) );
4578   QCOMPARE( f.attribute( 1 ).toString(), QString() );
4579   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((11 21, 31 21, 31 41, 11 41, 11 21))" ) );
4580   QVERIFY( it.nextFeature( f ) );
4581   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name" ) );
4582   QCOMPARE( f.attribute( 1 ).toString(), QStringLiteral( "test group" ) );
4583   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((1 2, 3 2, 3 4, 1 4, 1 2))" ) );
4584   QVERIFY( it.nextFeature( f ) );
4585   QCOMPARE( f.attribute( 0 ).toString(), QStringLiteral( "test name 2" ) );
4586   QCOMPARE( f.attribute( 1 ).toString(), QString() );
4587   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Polygon ((146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22))" ) );
4588   QVERIFY( !it.nextFeature( f ) );
4589 
4590 }
4591 
layerToBookmarks()4592 void TestQgsProcessingAlgs::layerToBookmarks()
4593 {
4594   std::unique_ptr<QgsVectorLayer> inputLayer( std::make_unique<QgsVectorLayer>( QStringLiteral( "Polygon?crs=epsg:4326&field=province:string&field=municipality:string" ), QStringLiteral( "layer" ), QStringLiteral( "memory" ) ) );
4595   QVERIFY( inputLayer->isValid() );
4596 
4597   QgsFeature f;
4598   f.setAttributes( QgsAttributes() << QStringLiteral( "b1" ) << QStringLiteral( "g1" ) );
4599   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((11 21, 31 21, 31 41, 11 41, 11 21))" ) ) );
4600   inputLayer->dataProvider()->addFeature( f );
4601   f.setAttributes( QgsAttributes() << QStringLiteral( "b2" ) << QString() );
4602   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -22, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 147 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -21, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22, 146 -22))" ) ) );
4603   inputLayer->dataProvider()->addFeature( f );
4604 
4605   QgsApplication::bookmarkManager()->clear();
4606 
4607   // run alg
4608   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:layertobookmarks" ) ) );
4609   QVERIFY( alg != nullptr );
4610 
4611   QgsProject p;
4612   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4613   context->setProject( &p );
4614 
4615   QVariantMap parameters;
4616   parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( inputLayer.get() ) );
4617   parameters.insert( QStringLiteral( "DESTINATION" ), 0 );
4618   parameters.insert( QStringLiteral( "NAME_EXPRESSION" ), QStringLiteral( "upper(province)" ) );
4619   parameters.insert( QStringLiteral( "GROUP_EXPRESSION" ), QStringLiteral( "upper(municipality)" ) );
4620 
4621   bool ok = false;
4622   QgsProcessingFeedback feedback;
4623   QVariantMap results;
4624   results = alg->run( parameters, *context, &feedback, &ok );
4625   QVERIFY( ok );
4626 
4627   QCOMPARE( p.bookmarkManager()->bookmarks().count(), 2 );
4628   QCOMPARE( p.bookmarkManager()->bookmarks().at( 0 ).name(), QStringLiteral( "B1" ) );
4629   QCOMPARE( p.bookmarkManager()->bookmarks().at( 0 ).group(), QStringLiteral( "G1" ) );
4630   QCOMPARE( p.bookmarkManager()->bookmarks().at( 0 ).extent().crs().authid(), QStringLiteral( "EPSG:4326" ) );
4631   QCOMPARE( p.bookmarkManager()->bookmarks().at( 0 ).extent().toString( 0 ), QStringLiteral( "11,21 : 31,41" ) );
4632   QCOMPARE( p.bookmarkManager()->bookmarks().at( 1 ).name(), QStringLiteral( "B2" ) );
4633   QCOMPARE( p.bookmarkManager()->bookmarks().at( 1 ).group(), QString() );
4634   QCOMPARE( p.bookmarkManager()->bookmarks().at( 1 ).extent().crs().authid(), QStringLiteral( "EPSG:4326" ) );
4635   QCOMPARE( p.bookmarkManager()->bookmarks().at( 1 ).extent().toString( 0 ), QStringLiteral( "146,-22 : 147,-21" ) );
4636   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().count(), 0 );
4637   p.bookmarkManager()->clear();
4638 
4639   // send to application bookmarks
4640   parameters.insert( QStringLiteral( "DESTINATION" ), 1 );
4641   parameters.insert( QStringLiteral( "GROUP_EXPRESSION" ), QVariant() );
4642 
4643   ok = false;
4644   alg.reset( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:layertobookmarks" ) ) );
4645   results = alg->run( parameters, *context, &feedback, &ok );
4646   QVERIFY( ok );
4647 
4648   QCOMPARE( p.bookmarkManager()->bookmarks().count(), 0 );
4649   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().count(), 2 );
4650   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 0 ).name(), QStringLiteral( "B1" ) );
4651   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 0 ).group(), QString() );
4652   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 0 ).extent().crs().authid(), QStringLiteral( "EPSG:4326" ) );
4653   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 0 ).extent().toString( 0 ), QStringLiteral( "11,21 : 31,41" ) );
4654   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 1 ).name(), QStringLiteral( "B2" ) );
4655   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 1 ).group(), QString() );
4656   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 1 ).extent().crs().authid(), QStringLiteral( "EPSG:4326" ) );
4657   QCOMPARE( QgsApplication::bookmarkManager()->bookmarks().at( 1 ).extent().toString( 0 ), QStringLiteral( "146,-22 : 147,-21" ) );
4658 }
4659 
repairShapefile()4660 void TestQgsProcessingAlgs::repairShapefile()
4661 {
4662   const QTemporaryDir tmpPath;
4663 
4664   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
4665   QFile::copy( dataDir + "/points.shp", tmpPath.filePath( QStringLiteral( "points.shp" ) ) );
4666   QFile::copy( dataDir + "/points.shp", tmpPath.filePath( QStringLiteral( "points.prj" ) ) );
4667   QFile::copy( dataDir + "/points.shp", tmpPath.filePath( QStringLiteral( "points.dbf" ) ) );
4668   // no shx!!
4669 
4670   std::unique_ptr< QgsVectorLayer > layer = std::make_unique< QgsVectorLayer >( tmpPath.filePath( QStringLiteral( "points.shp" ) ) );
4671   QVERIFY( !layer->isValid() );
4672 
4673   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:repairshapefile" ) ) );
4674   QVERIFY( alg != nullptr );
4675 
4676   QVariantMap parameters;
4677   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "not a file" ) );
4678 
4679   bool ok = false;
4680   QgsProcessingFeedback feedback;
4681   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4682 
4683   QVariantMap results;
4684   results = alg->run( parameters, *context, &feedback, &ok );
4685   QVERIFY( !ok );
4686 
4687   parameters.insert( QStringLiteral( "INPUT" ), tmpPath.filePath( QStringLiteral( "points.shp" ) ) );
4688   ok = false;
4689   results = alg->run( parameters, *context, &feedback, &ok );
4690   QVERIFY( ok );
4691   QCOMPARE( results.value( QStringLiteral( "OUTPUT" ) ).toString(), tmpPath.filePath( QStringLiteral( "points.shp" ) ) );
4692 
4693   layer = std::make_unique< QgsVectorLayer >( tmpPath.filePath( QStringLiteral( "points.shp" ) ) );
4694   QVERIFY( layer->isValid() );
4695 }
4696 
renameField()4697 void TestQgsProcessingAlgs::renameField()
4698 {
4699   QgsProject p;
4700   QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4701   QVERIFY( layer->isValid() );
4702   p.addMapLayer( layer );
4703 
4704   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:renametablefield" ) ) );
4705   QVERIFY( alg != nullptr );
4706 
4707   QVariantMap parameters;
4708   parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( layer ) );
4709   parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "doesntexist" ) );
4710   parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "newname" ) );
4711 
4712   bool ok = false;
4713   QgsProcessingFeedback feedback;
4714   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4715 
4716   QVariantMap results;
4717   results = alg->run( parameters, *context, &feedback, &ok );
4718   // field doesn't exist
4719   QVERIFY( !ok );
4720 
4721   parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "col1" ) );
4722   parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "pk" ) );
4723 
4724   ok = false;
4725   results = alg->run( parameters, *context, &feedback, &ok );
4726   // already a field with this name
4727   QVERIFY( !ok );
4728 
4729   parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "newname" ) );
4730   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
4731   results = alg->run( parameters, *context, &feedback, &ok );
4732   QVERIFY( ok );
4733 
4734   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->fields().at( 1 ).name(), QStringLiteral( "newname" ) );
4735 
4736 }
4737 
compareDatasets()4738 void TestQgsProcessingAlgs::compareDatasets()
4739 {
4740   QgsProject p;
4741   QgsVectorLayer *pointLayer = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4742   QVERIFY( pointLayer->isValid() );
4743   p.addMapLayer( pointLayer );
4744   QgsVectorLayer *originalLayer = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:string&field=col2:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4745   QVERIFY( originalLayer->isValid() );
4746   p.addMapLayer( originalLayer );
4747   QgsVectorLayer *revisedLayer = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:string&field=col2:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4748   QVERIFY( revisedLayer->isValid() );
4749   p.addMapLayer( revisedLayer );
4750   QgsVectorLayer *differentAttrs = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col3:string&field=col2:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
4751   QVERIFY( differentAttrs->isValid() );
4752   p.addMapLayer( differentAttrs );
4753 
4754   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:detectvectorchanges" ) ) );
4755   QVERIFY( alg != nullptr );
4756 
4757   QVariantMap parameters;
4758   // differing geometry types - alg should fail
4759   parameters.insert( QStringLiteral( "ORIGINAL" ), QVariant::fromValue( pointLayer ) );
4760   parameters.insert( QStringLiteral( "REVISED" ), QVariant::fromValue( originalLayer ) );
4761 
4762   bool ok = false;
4763   QgsProcessingFeedback feedback;
4764   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4765 
4766   QVariantMap results;
4767   results = alg->run( parameters, *context, &feedback, &ok );
4768   QVERIFY( !ok );
4769 
4770   // differing fields - alg should fail
4771   parameters.insert( QStringLiteral( "ORIGINAL" ), QVariant::fromValue( originalLayer ) );
4772   parameters.insert( QStringLiteral( "REVISED" ), QVariant::fromValue( differentAttrs ) );
4773   parameters.insert( QStringLiteral( "COMPARE_ATTRIBUTES" ), QStringLiteral( "col1;col2" ) );
4774   results = alg->run( parameters, *context, &feedback, &ok );
4775   QVERIFY( !ok );
4776 
4777   // yet if we aren't comparing the field, we shouldn't fail...
4778   parameters.insert( QStringLiteral( "COMPARE_ATTRIBUTES" ), QStringLiteral( "col2" ) );
4779   results = alg->run( parameters, *context, &feedback, &ok );
4780   QVERIFY( ok );
4781 
4782   // no features, no outputs
4783   parameters.insert( QStringLiteral( "ORIGINAL" ), QVariant::fromValue( originalLayer ) );
4784   parameters.insert( QStringLiteral( "REVISED" ), QVariant::fromValue( revisedLayer ) );
4785   results = alg->run( parameters, *context, &feedback, &ok );
4786   QVERIFY( ok );
4787   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 0LL );
4788   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 0LL );
4789   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 0LL );
4790 
4791   parameters.insert( QStringLiteral( "UNCHANGED" ), QgsProcessing::TEMPORARY_OUTPUT );
4792   parameters.insert( QStringLiteral( "ADDED" ), QgsProcessing::TEMPORARY_OUTPUT );
4793   parameters.insert( QStringLiteral( "DELETED" ), QgsProcessing::TEMPORARY_OUTPUT );
4794 
4795   // add two features to original
4796   QgsFeature f;
4797   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "b1" ) << QStringLiteral( "g1" ) );
4798   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 10 0)" ) ) );
4799   originalLayer->dataProvider()->addFeature( f );
4800   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "c1" ) << QStringLiteral( "g1" ) );
4801   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 10 0)" ) ) );
4802   originalLayer->dataProvider()->addFeature( f );
4803 
4804   // just compare two columns
4805   parameters.insert( QStringLiteral( "COMPARE_ATTRIBUTES" ), QStringLiteral( "col1;col2" ) );
4806 
4807   results = alg->run( parameters, *context, &feedback, &ok );
4808   QVERIFY( ok );
4809   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 0LL );
4810   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "UNCHANGED" ) ).toString() ) )->featureCount(), 0L );
4811   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 0LL );
4812   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "ADDED" ) ).toString() ) )->featureCount(), 0L );
4813   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 2LL );
4814   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "DELETED" ) ).toString() ) )->featureCount(), 2L );
4815 
4816   // add one same to revised - note that the first attributes differs here, but we aren't considering that
4817   f.setAttributes( QgsAttributes() << 55 << QStringLiteral( "b1" ) << QStringLiteral( "g1" ) );
4818   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 10 0)" ) ) );
4819   revisedLayer->dataProvider()->addFeature( f );
4820 
4821   results = alg->run( parameters, *context, &feedback, &ok );
4822   QVERIFY( ok );
4823   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 1LL );
4824   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "UNCHANGED" ) ).toString() ) )->featureCount(), 1L );
4825   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 0LL );
4826   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "ADDED" ) ).toString() ) )->featureCount(), 0L );
4827   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4828   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "DELETED" ) ).toString() ) )->featureCount(), 1L );
4829 
4830   // ok, let's compare the differing attribute too
4831   parameters.insert( QStringLiteral( "COMPARE_ATTRIBUTES" ), QStringLiteral( "col1;col2;pk" ) );
4832   results = alg->run( parameters, *context, &feedback, &ok );
4833   QVERIFY( ok );
4834   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 0LL );
4835   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 1LL );
4836   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 2LL );
4837 
4838   parameters.insert( QStringLiteral( "COMPARE_ATTRIBUTES" ), QStringLiteral( "col1;col2" ) );
4839   // similar to the second feature, but geometry differs
4840   f.setAttributes( QgsAttributes() << 55 << QStringLiteral( "c1" ) << QStringLiteral( "g1" ) );
4841   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 11 0)" ) ) );
4842   revisedLayer->dataProvider()->addFeature( f );
4843   results = alg->run( parameters, *context, &feedback, &ok );
4844   QVERIFY( ok );
4845   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 1LL );
4846   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "UNCHANGED" ) ).toString() ) )->featureCount(), 1L );
4847   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 1LL );
4848   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "ADDED" ) ).toString() ) )->featureCount(), 1L );
4849   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4850   QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "DELETED" ) ).toString() ) )->featureCount(), 1L );
4851   // note - we skip the featureCount checks after this -- we can be confident at this stage that all sinks are correctly being populated
4852 
4853 
4854   // add another which is identical to first, must be considered as another "added" feature (i.e.
4855   // don't match to same original feature multiple times)
4856   f.setAttributes( QgsAttributes() << 555 << QStringLiteral( "b1" ) << QStringLiteral( "g1" ) );
4857   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 10 0)" ) ) );
4858   revisedLayer->dataProvider()->addFeature( f );
4859   results = alg->run( parameters, *context, &feedback, &ok );
4860   QVERIFY( ok );
4861   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 1LL );
4862   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4863   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4864 
4865   // add a match for the second feature
4866   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "c1" ) << QStringLiteral( "g1" ) );
4867   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 10 0)" ) ) );
4868   revisedLayer->dataProvider()->addFeature( f );
4869   results = alg->run( parameters, *context, &feedback, &ok );
4870   QVERIFY( ok );
4871   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 2LL );
4872   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4873   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 0LL );
4874 
4875   // test topological match (different number of vertices)
4876   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "c1" ) << QStringLiteral( "g1" ) );
4877   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 10, 5 10, 10 10)" ) ) );
4878   originalLayer->dataProvider()->addFeature( f );
4879   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 10, 1 10, 5 10, 10 10)" ) ) );
4880   revisedLayer->dataProvider()->addFeature( f );
4881 
4882   // exact match shouldn't equate the two
4883   parameters.insert( QStringLiteral( "MATCH_TYPE" ), 0 );
4884   results = alg->run( parameters, *context, &feedback, &ok );
4885   QVERIFY( ok );
4886   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 2LL );
4887   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 3LL );
4888   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4889 
4890   // but topological match should
4891   parameters.insert( QStringLiteral( "MATCH_TYPE" ), 1 );
4892   results = alg->run( parameters, *context, &feedback, &ok );
4893   QVERIFY( ok );
4894   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 3LL );
4895   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4896   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 0LL );
4897 
4898   // null geometry comparisons
4899   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "d1" ) << QStringLiteral( "g1" ) );
4900   f.clearGeometry();
4901   originalLayer->dataProvider()->addFeature( f );
4902   results = alg->run( parameters, *context, &feedback, &ok );
4903   QVERIFY( ok );
4904   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 3LL );
4905   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4906   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4907 
4908   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "e1" ) << QStringLiteral( "g1" ) );
4909   originalLayer->dataProvider()->addFeature( f );
4910   results = alg->run( parameters, *context, &feedback, &ok );
4911   QVERIFY( ok );
4912   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 3LL );
4913   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4914   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 2LL );
4915 
4916   f.setAttributes( QgsAttributes() << 5 << QStringLiteral( "d1" ) << QStringLiteral( "g1" ) );
4917   revisedLayer->dataProvider()->addFeature( f );
4918   results = alg->run( parameters, *context, &feedback, &ok );
4919   QVERIFY( ok );
4920   QCOMPARE( results.value( QStringLiteral( "UNCHANGED_COUNT" ) ).toLongLong(), 4LL );
4921   QCOMPARE( results.value( QStringLiteral( "ADDED_COUNT" ) ).toLongLong(), 2LL );
4922   QCOMPARE( results.value( QStringLiteral( "DELETED_COUNT" ) ).toLongLong(), 1LL );
4923 
4924 }
4925 
shapefileEncoding()4926 void TestQgsProcessingAlgs::shapefileEncoding()
4927 {
4928   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:shpencodinginfo" ) ) );
4929   QVERIFY( alg != nullptr );
4930 
4931   QVariantMap parameters;
4932   parameters.insert( QStringLiteral( "INPUT" ), QString() );
4933 
4934   bool ok = false;
4935   QgsProcessingFeedback feedback;
4936   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4937 
4938   QVariantMap results;
4939   results = alg->run( parameters, *context, &feedback, &ok );
4940 
4941   parameters.insert( QStringLiteral( "INPUT" ), QString( QStringLiteral( TEST_DATA_DIR ) + "/shapefile/iso-8859-1.shp" ) );
4942   results = alg->run( parameters, *context, &feedback, &ok );
4943   QVERIFY( ok );
4944   QCOMPARE( results.value( QStringLiteral( "ENCODING" ) ).toString(), QStringLiteral( "ISO-8859-1" ) );
4945   QCOMPARE( results.value( QStringLiteral( "CPG_ENCODING" ) ).toString(), QStringLiteral( "ISO-8859-1" ) );
4946   QCOMPARE( results.value( QStringLiteral( "LDID_ENCODING" ) ).toString(), QString() );
4947 
4948   parameters.insert( QStringLiteral( "INPUT" ), QString( QStringLiteral( TEST_DATA_DIR ) + "/shapefile/windows-1252_ldid.shp" ) );
4949   results = alg->run( parameters, *context, &feedback, &ok );
4950   QVERIFY( ok );
4951   QCOMPARE( results.value( QStringLiteral( "ENCODING" ) ).toString(), QStringLiteral( "CP1252" ) );
4952   QCOMPARE( results.value( QStringLiteral( "CPG_ENCODING" ) ).toString(), QString() );
4953   QCOMPARE( results.value( QStringLiteral( "LDID_ENCODING" ) ).toString(), QStringLiteral( "CP1252" ) );
4954 
4955   parameters.insert( QStringLiteral( "INPUT" ), QString( QStringLiteral( TEST_DATA_DIR ) + "/shapefile/system_encoding.shp" ) );
4956   results = alg->run( parameters, *context, &feedback, &ok );
4957   QVERIFY( ok );
4958   QCOMPARE( results.value( QStringLiteral( "ENCODING" ) ).toString(), QString() );
4959   QCOMPARE( results.value( QStringLiteral( "CPG_ENCODING" ) ).toString(), QString() );
4960   QCOMPARE( results.value( QStringLiteral( "LDID_ENCODING" ) ).toString(), QString() );
4961 }
4962 
setLayerEncoding()4963 void TestQgsProcessingAlgs::setLayerEncoding()
4964 {
4965   QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( TEST_DATA_DIR ) + "/shapefile/system_encoding.shp",
4966       QStringLiteral( "test" ), QStringLiteral( "ogr" ) );
4967   QVERIFY( vl->isValid() );
4968   QgsProject p;
4969   p.addMapLayers(
4970     QList<QgsMapLayer *>() << vl );
4971 
4972   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:setlayerencoding" ) ) );
4973   QVERIFY( alg != nullptr );
4974 
4975   QVariantMap parameters;
4976   parameters.insert( QStringLiteral( "INPUT" ), QString() );
4977 
4978   bool ok = false;
4979   QgsProcessingFeedback feedback;
4980   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
4981   context->setProject( &p );
4982 
4983   QVariantMap results;
4984   results = alg->run( parameters, *context, &feedback, &ok );
4985   QVERIFY( !ok );
4986 
4987   parameters.insert( QStringLiteral( "INPUT" ), vl->id() );
4988   parameters.insert( QStringLiteral( "ENCODING" ), QStringLiteral( "ISO-8859-1" ) );
4989   results = alg->run( parameters, *context, &feedback, &ok );
4990   QVERIFY( ok );
4991   QCOMPARE( vl->dataProvider()->encoding(), QStringLiteral( "ISO-8859-1" ) );
4992 
4993 }
4994 
4995 class TestProcessingFeedback : public QgsProcessingFeedback
4996 {
4997   public:
4998 
reportError(const QString & error,bool)4999     void reportError( const QString &error, bool ) override
5000     {
5001       errors << error;
5002     }
5003 
5004     QStringList errors;
5005 
5006 };
5007 
raiseException()5008 void TestQgsProcessingAlgs::raiseException()
5009 {
5010   TestProcessingFeedback feedback;
5011 
5012   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:raiseexception" ) ) );
5013   QVERIFY( alg != nullptr );
5014 
5015   QVariantMap parameters;
5016   parameters.insert( QStringLiteral( "MESSAGE" ), QStringLiteral( "you done screwed up boy" ) );
5017 
5018   bool ok = false;
5019   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5020 
5021   QVariantMap results;
5022   results = alg->run( parameters, *context, &feedback, &ok );
5023   QVERIFY( !ok );
5024 
5025   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "you done screwed up boy" ) );
5026 
5027   parameters.insert( QStringLiteral( "CONDITION" ), QStringLiteral( "FALSE" ) );
5028   feedback.errors.clear();
5029   results = alg->run( parameters, *context, &feedback, &ok );
5030   QVERIFY( ok );
5031 
5032   QCOMPARE( feedback.errors, QStringList() );
5033 
5034   parameters.insert( QStringLiteral( "CONDITION" ), QStringLiteral( "TRUE" ) );
5035   feedback.errors.clear();
5036   results = alg->run( parameters, *context, &feedback, &ok );
5037   QVERIFY( !ok );
5038 
5039   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "you done screwed up boy" ) );
5040 }
5041 
raiseWarning()5042 void TestQgsProcessingAlgs::raiseWarning()
5043 {
5044   TestProcessingFeedback feedback;
5045 
5046   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:raisewarning" ) ) );
5047   QVERIFY( alg != nullptr );
5048 
5049   QVariantMap parameters;
5050   parameters.insert( QStringLiteral( "MESSAGE" ), QStringLiteral( "you mighta screwed up boy, but i aint so sure" ) );
5051 
5052   bool ok = false;
5053   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5054 
5055   QVariantMap results;
5056   results = alg->run( parameters, *context, &feedback, &ok );
5057   QVERIFY( ok );
5058 
5059   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "you mighta screwed up boy, but i aint so sure" ) );
5060 
5061   parameters.insert( QStringLiteral( "CONDITION" ), QStringLiteral( "FALSE" ) );
5062   feedback.errors.clear();
5063   results = alg->run( parameters, *context, &feedback, &ok );
5064   QVERIFY( ok );
5065 
5066   QCOMPARE( feedback.errors, QStringList() );
5067 
5068   parameters.insert( QStringLiteral( "CONDITION" ), QStringLiteral( "TRUE" ) );
5069   feedback.errors.clear();
5070   results = alg->run( parameters, *context, &feedback, &ok );
5071   QVERIFY( ok );
5072 
5073   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "you mighta screwed up boy, but i aint so sure" ) );
5074 }
5075 
randomFloatingPointDistributionRaster_data()5076 void TestQgsProcessingAlgs::randomFloatingPointDistributionRaster_data()
5077 {
5078   QTest::addColumn<QString>( "inputExtent" );
5079   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
5080   QTest::addColumn<bool>( "succeeds" );
5081   QTest::addColumn<QString>( "crs" );
5082   QTest::addColumn<double>( "pixelSize" );
5083   QTest::addColumn<int>( "typeId" );
5084 
5085   QTest::newRow( "testcase 1" )
5086       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5087       << Qgis::DataType::Float32
5088       << true
5089       << "EPSG:4326"
5090       << 1.0
5091       << 0;
5092 
5093 
5094   QTest::newRow( "testcase 2" )
5095       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5096       << Qgis::DataType::Float64
5097       << true
5098       << "EPSG:4326"
5099       << 1.0
5100       << 1;
5101 }
5102 
randomFloatingPointDistributionRaster()5103 void TestQgsProcessingAlgs::randomFloatingPointDistributionRaster()
5104 {
5105   //this test only checks if the correct raster data type is chosen
5106   //value by value comparison is not effective for random rasters
5107   QFETCH( QString, inputExtent );
5108   QFETCH( Qgis::DataType, expectedDataType );
5109   QFETCH( bool, succeeds );
5110   QFETCH( QString, crs );
5111   QFETCH( double, pixelSize );
5112   QFETCH( int, typeId );
5113 
5114   //prepare input params
5115   QgsProject p;
5116   //set project crs and ellipsoid from input layer
5117   p.setCrs( QgsCoordinateReferenceSystem( crs ), true );
5118 
5119   QStringList alglist = QStringList();
5120   alglist << QStringLiteral( "native:createrandomnormalrasterlayer" )
5121           << QStringLiteral( "native:createrandomexponentialrasterlayer" )
5122           << QStringLiteral( "native:createrandomgammarasterlayer" );
5123 
5124   for ( int i = 0; i < alglist.length(); i++ )
5125   {
5126     const QString algname = alglist[i];
5127 
5128     std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( algname ) );
5129     //set project after layer has been added so that transform context/ellipsoid from crs is also set
5130     std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5131     context->setProject( &p );
5132 
5133     QVariantMap parameters;
5134 
5135     parameters.insert( QStringLiteral( "EXTENT" ), inputExtent );
5136     parameters.insert( QStringLiteral( "TARGET_CRS" ), QgsCoordinateReferenceSystem( crs ) );
5137     parameters.insert( QStringLiteral( "PIXEL_SIZE" ), pixelSize );
5138     parameters.insert( QStringLiteral( "OUTPUT_TYPE" ), typeId );
5139     parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
5140 
5141     bool ok = false;
5142     QgsProcessingFeedback feedback;
5143     QVariantMap results;
5144 
5145     if ( !succeeds )
5146     {
5147       //verify if user feedback for unacceptable values are thrown
5148       alg->run( parameters, *context, &feedback, &ok );
5149       QCOMPARE( ok, succeeds );
5150     }
5151     else
5152     {
5153       //run alg...
5154       results = alg->run( parameters, *context, &feedback, &ok );
5155       QVERIFY( ok );
5156 
5157       //...and check results with expected datasets
5158       std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
5159       std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
5160 
5161       QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
5162     }
5163   }
5164 }
5165 
5166 
randomIntegerDistributionRaster_data()5167 void TestQgsProcessingAlgs::randomIntegerDistributionRaster_data()
5168 {
5169   QTest::addColumn<QString>( "inputExtent" );
5170   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
5171   QTest::addColumn<bool>( "succeeds" );
5172   QTest::addColumn<QString>( "crs" );
5173   QTest::addColumn<double>( "pixelSize" );
5174   QTest::addColumn<int>( "typeId" );
5175 
5176   QTest::newRow( "testcase 1" )
5177       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5178       << Qgis::DataType::Int16
5179       << true
5180       << "EPSG:4326"
5181       << 1.0
5182       << 0;
5183 
5184   QTest::newRow( "testcase 2" )
5185       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5186       << Qgis::DataType::UInt16
5187       << true
5188       << "EPSG:4326"
5189       << 1.0
5190       << 1;
5191 
5192 
5193   QTest::newRow( "testcase 3" )
5194       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5195       << Qgis::DataType::Int32
5196       << true
5197       << "EPSG:4326"
5198       << 1.0
5199       << 2;
5200 
5201   QTest::newRow( "testcase 4" )
5202       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5203       << Qgis::DataType::UInt32
5204       << true
5205       << "EPSG:4326"
5206       << 1.0
5207       << 3;
5208 
5209   QTest::newRow( "testcase 5" )
5210       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5211       << Qgis::DataType::Float32
5212       << true
5213       << "EPSG:4326"
5214       << 1.0
5215       << 4;
5216 
5217   QTest::newRow( "testcase 6" )
5218       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5219       << Qgis::DataType::Float64
5220       << true
5221       << "EPSG:4326"
5222       << 1.0
5223       << 5;
5224 }
5225 
5226 
randomIntegerDistributionRaster()5227 void TestQgsProcessingAlgs::randomIntegerDistributionRaster()
5228 {
5229   //this test only checks if the correct raster data type is chosen
5230   //value by value comparison is not effective for random rasters
5231   QFETCH( QString, inputExtent );
5232   QFETCH( Qgis::DataType, expectedDataType );
5233   QFETCH( bool, succeeds );
5234   QFETCH( QString, crs );
5235   QFETCH( double, pixelSize );
5236   QFETCH( int, typeId );
5237 
5238   //prepare input params
5239   QgsProject p;
5240   //set project crs and ellipsoid from input layer
5241   p.setCrs( QgsCoordinateReferenceSystem( crs ), true );
5242 
5243   QStringList alglist = QStringList();
5244   alglist << QStringLiteral( "native:createrandombinomialrasterlayer" )
5245           << QStringLiteral( "native:createrandomgeometricrasterlayer" )
5246           << QStringLiteral( "native:createrandomnegativebinomialrasterlayer" )
5247           << QStringLiteral( "native:createrandompoissonrasterlayer" );
5248 
5249   for ( int i = 0; i < alglist.length(); i++ )
5250   {
5251     const QString algname = alglist[i];
5252 
5253     std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( algname ) );
5254     //set project after layer has been added so that transform context/ellipsoid from crs is also set
5255     std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5256     context->setProject( &p );
5257 
5258     QVariantMap parameters;
5259 
5260     parameters.insert( QStringLiteral( "EXTENT" ), inputExtent );
5261     parameters.insert( QStringLiteral( "TARGET_CRS" ), QgsCoordinateReferenceSystem( crs ) );
5262     parameters.insert( QStringLiteral( "PIXEL_SIZE" ), pixelSize );
5263     parameters.insert( QStringLiteral( "OUTPUT_TYPE" ), typeId );
5264     parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
5265 
5266     bool ok = false;
5267     QgsProcessingFeedback feedback;
5268     QVariantMap results;
5269 
5270     if ( !succeeds )
5271     {
5272       //verify if user feedback for unacceptable values are thrown
5273       alg->run( parameters, *context, &feedback, &ok );
5274       QCOMPARE( ok, succeeds );
5275     }
5276     else
5277     {
5278       //run alg...
5279       results = alg->run( parameters, *context, &feedback, &ok );
5280       QVERIFY( ok );
5281 
5282       //...and check results with expected datasets
5283       std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
5284       std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
5285 
5286       QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
5287     }
5288   }
5289 }
5290 
randomRaster_data()5291 void TestQgsProcessingAlgs::randomRaster_data()
5292 {
5293   QTest::addColumn<QString>( "inputExtent" );
5294   QTest::addColumn<Qgis::DataType>( "expectedDataType" );
5295   QTest::addColumn<bool>( "succeeds" );
5296   QTest::addColumn<QString>( "crs" );
5297   QTest::addColumn<double>( "pixelSize" );
5298   QTest::addColumn<double>( "lowerBound" );
5299   QTest::addColumn<double>( "upperBound" );
5300   QTest::addColumn<int>( "typeId" );
5301 
5302 
5303   QTest::newRow( "testcase 1" )
5304       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5305       << Qgis::DataType::Byte
5306       << true
5307       << "EPSG:4326"
5308       << 1.0
5309       << 1.0
5310       << 1.0 //should be min max
5311       << 0;
5312 
5313   QTest::newRow( "testcase 2" )
5314       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5315       << Qgis::DataType::Byte
5316       << false
5317       << "EPSG:4326"
5318       << 1.0
5319       << -1.0 //fails --> value too small for byte
5320       << 10.0 //fails --> value too large
5321       << 0;
5322 
5323   QTest::newRow( "testcase 3" )
5324       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5325       << Qgis::DataType::Byte
5326       << false
5327       << "EPSG:4326"
5328       << 1.0
5329       << 1.0
5330       << 256.0 //fails --> value too big for byte
5331       << 0;
5332 
5333   QTest::newRow( "testcase 4" )
5334       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5335       << Qgis::DataType::Int16
5336       << true
5337       << "EPSG:4326"
5338       << 1.0
5339       << 1.0
5340       << 10.0
5341       << 1;
5342 
5343   QTest::newRow( "testcase 5" )
5344       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5345       << Qgis::DataType::Int16
5346       << false
5347       << "EPSG:4326"
5348       << 1.0
5349       << -32769.0
5350       << -10.0
5351       << 1;
5352 
5353   QTest::newRow( "testcase 6" )
5354       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5355       << Qgis::DataType::Int16
5356       << false
5357       << "EPSG:4326"
5358       << 1.0
5359       << 1.0
5360       << 32769.0
5361       << 1;
5362 
5363   QTest::newRow( "testcase 7" )
5364       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5365       << Qgis::DataType::UInt16
5366       << false
5367       << "EPSG:4326"
5368       << 1.0
5369       << -1.0
5370       << 12.0
5371       << 2;
5372 
5373   QTest::newRow( "testcase 8" )
5374       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5375       << Qgis::DataType::UInt16
5376       << false
5377       << "EPSG:4326"
5378       << 1.0
5379       << 100.0
5380       << -1.0
5381       << 2;
5382 
5383   QTest::newRow( "testcase 9" )
5384       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5385       << Qgis::DataType::UInt16
5386       << false
5387       << "EPSG:4326"
5388       << 1.0
5389       << 0.0
5390       << 65536.0
5391       << 2;
5392 
5393   QTest::newRow( "testcase 10" )
5394       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5395       << Qgis::DataType::Int32
5396       << true
5397       << "EPSG:4326"
5398       << 1.0
5399       << 1.0
5400       << 12.0
5401       << 3;
5402 
5403   QTest::newRow( "testcase 10" )
5404       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5405       << Qgis::DataType::Int32
5406       << false
5407       << "EPSG:4326"
5408       << 1.0
5409       << 15.0
5410       << 12.0
5411       << 3;
5412 
5413   QTest::newRow( "testcase 11" )
5414       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5415       << Qgis::DataType::Int32
5416       << false
5417       << "EPSG:4326"
5418       << 1.0
5419       << -2147483649.0
5420       << 1.0
5421       << 3;
5422 
5423   QTest::newRow( "testcase 12" )
5424       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5425       << Qgis::DataType::Int32
5426       << false
5427       << "EPSG:4326"
5428       << 1.0
5429       << 1.0
5430       << 2147483649.0
5431       << 3;
5432 
5433   QTest::newRow( "testcase 13" )
5434       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5435       << Qgis::DataType::UInt32
5436       << true
5437       << "EPSG:4326"
5438       << 1.0
5439       << 1.0
5440       << 12.0
5441       << 4;
5442 
5443   QTest::newRow( "testcase 14" )
5444       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5445       << Qgis::DataType::UInt32
5446       << false
5447       << "EPSG:4326"
5448       << 1.0
5449       << 1.0
5450       << 4294967296.0
5451       << 4;
5452 
5453   QTest::newRow( "testcase 14" )
5454       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5455       << Qgis::DataType::UInt32
5456       << false
5457       << "EPSG:4326"
5458       << 1.0
5459       << -10.0
5460       << -1.0
5461       << 4;
5462 
5463   QTest::newRow( "testcase 16" )
5464       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5465       << Qgis::DataType::Float32
5466       << true
5467       << "EPSG:4326"
5468       << 1.0
5469       << 0.0
5470       << 12.12
5471       << 5;
5472 
5473   QTest::newRow( "testcase 17" )
5474       << "-3.000000000,7.000000000,-4.000000000,6.000000000 [EPSG:4326]"
5475       << Qgis::DataType::Float64
5476       << true
5477       << "EPSG:4326"
5478       << 1.0
5479       << -15.0
5480       << 12.125789212532487
5481       << 6;
5482 
5483 
5484 }
5485 
randomRaster()5486 void TestQgsProcessingAlgs::randomRaster()
5487 {
5488   QFETCH( QString, inputExtent );
5489   QFETCH( Qgis::DataType, expectedDataType );
5490   QFETCH( bool, succeeds );
5491   QFETCH( QString, crs );
5492   QFETCH( double, pixelSize );
5493   QFETCH( double, lowerBound );
5494   QFETCH( double, upperBound );
5495   QFETCH( int, typeId );
5496 
5497   if ( qgsDoubleNear( lowerBound, upperBound ) )
5498   {
5499     //if bounds are the same, use numeric min and max as bounds
5500     switch ( typeId )
5501     {
5502       case 0:
5503         lowerBound = std::numeric_limits<quint8>::min();
5504         upperBound = std::numeric_limits<quint8>::max();
5505         break;
5506       case 1:
5507         lowerBound = std::numeric_limits<qint16>::min();
5508         upperBound = std::numeric_limits<qint16>::max();
5509         break;
5510       case 2:
5511         lowerBound = std::numeric_limits<quint16>::min();
5512         upperBound = std::numeric_limits<quint16>::max();
5513         break;
5514       case 3:
5515         lowerBound = std::numeric_limits<qint32>::min();
5516         upperBound = std::numeric_limits<qint32>::max();
5517         break;
5518       case 4:
5519         lowerBound = std::numeric_limits<quint32>::min();
5520         upperBound = std::numeric_limits<quint32>::max();
5521         break;
5522       case 5:
5523         lowerBound = std::numeric_limits<float>::min();
5524         upperBound = std::numeric_limits<float>::max();
5525         break;
5526       case 6:
5527         lowerBound = std::numeric_limits<double>::min();
5528         upperBound = std::numeric_limits<double>::max();
5529         break;
5530     }
5531   }
5532 
5533   //prepare input params
5534   QgsProject p;
5535   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:createrandomuniformrasterlayer" ) ) );
5536 
5537   const QString myDataPath( TEST_DATA_DIR ); //defined in CmakeLists.txt
5538 
5539   //set project crs and ellipsoid from input layer
5540   p.setCrs( QgsCoordinateReferenceSystem( crs ), true );
5541 
5542   //set project after layer has been added so that transform context/ellipsoid from crs is also set
5543   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5544   context->setProject( &p );
5545 
5546   QVariantMap parameters;
5547 
5548   parameters.insert( QStringLiteral( "EXTENT" ), inputExtent );
5549   parameters.insert( QStringLiteral( "TARGET_CRS" ), QgsCoordinateReferenceSystem( crs ) );
5550   parameters.insert( QStringLiteral( "PIXEL_SIZE" ), pixelSize );
5551   parameters.insert( QStringLiteral( "OUTPUT_TYPE" ), typeId );
5552   parameters.insert( QStringLiteral( "LOWER_BOUND" ), lowerBound );
5553   parameters.insert( QStringLiteral( "UPPER_BOUND" ), upperBound );
5554   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
5555 
5556   bool ok = false;
5557   QgsProcessingFeedback feedback;
5558   QVariantMap results;
5559 
5560   if ( !succeeds )
5561   {
5562     //verify if user feedback for unacceptable values are thrown
5563     alg->run( parameters, *context, &feedback, &ok );
5564     QCOMPARE( ok, succeeds );
5565   }
5566   else
5567   {
5568     //run alg...
5569     results = alg->run( parameters, *context, &feedback, &ok );
5570     QVERIFY( ok );
5571 
5572     //...and check results with expected datasets
5573     std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
5574     std::unique_ptr< QgsRasterInterface > outputInterface( outputRaster->dataProvider()->clone() );
5575 
5576     QCOMPARE( outputInterface->dataType( 1 ), expectedDataType );
5577 
5578     QgsRasterIterator outputIter( outputInterface.get() );
5579     outputIter.startRasterRead( 1, outputRaster->width(), outputRaster->height(), outputInterface->extent() );
5580     int outputIterLeft = 0;
5581     int outputIterTop = 0;
5582     int outputIterCols = 0;
5583     int outputIterRows = 0;
5584 
5585     std::unique_ptr< QgsRasterBlock > outputRasterBlock;
5586 
5587     while ( outputIter.readNextRasterPart( 1, outputIterCols, outputIterRows, outputRasterBlock, outputIterLeft, outputIterTop ) )
5588     {
5589       for ( int row = 0; row < outputIterRows; row++ )
5590       {
5591         for ( int column = 0; column < outputIterCols; column++ )
5592         {
5593           const double outputValue = outputRasterBlock->value( row, column );
5594           //check if random values are in range
5595           QVERIFY( outputValue >= lowerBound && outputValue <= upperBound );
5596         }
5597       }
5598     }
5599   }
5600 }
5601 
filterByLayerType()5602 void TestQgsProcessingAlgs::filterByLayerType()
5603 {
5604   QgsProject p;
5605   QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
5606   QVERIFY( vl->isValid() );
5607   p.addMapLayer( vl );
5608   // raster layer
5609   QgsRasterLayer *rl = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/tenbytenraster.asc", QStringLiteral( "rl" ) );
5610   QVERIFY( rl->isValid() );
5611   p.addMapLayer( rl );
5612 
5613 
5614   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:filterlayersbytype" ) ) );
5615   QVERIFY( alg != nullptr );
5616 
5617   QVariantMap parameters;
5618   // vector input
5619   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "vl" ) );
5620 
5621   bool ok = false;
5622   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5623   context->setProject( &p );
5624   QgsProcessingFeedback feedback;
5625   QVariantMap results;
5626   results = alg->run( parameters, *context, &feedback, &ok );
5627   QVERIFY( ok );
5628   QVERIFY( !results.value( QStringLiteral( "VECTOR" ) ).toString().isEmpty() );
5629   QVERIFY( !results.contains( QStringLiteral( "RASTER" ) ) );
5630 
5631   // raster input
5632   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "rl" ) );
5633   ok = false;
5634   results = alg->run( parameters, *context, &feedback, &ok );
5635   QVERIFY( ok );
5636   QVERIFY( !results.value( QStringLiteral( "RASTER" ) ).toString().isEmpty() );
5637   QVERIFY( !results.contains( QStringLiteral( "VECTOR" ) ) );
5638 }
5639 
conditionalBranch()5640 void TestQgsProcessingAlgs::conditionalBranch()
5641 {
5642   QVariantMap config;
5643   QVariantList conditions;
5644   QVariantMap cond1;
5645   cond1.insert( QStringLiteral( "name" ), QStringLiteral( "name1" ) );
5646   cond1.insert( QStringLiteral( "expression" ), QStringLiteral( "1 * 1" ) );
5647   conditions << cond1;
5648   QVariantMap cond2;
5649   cond2.insert( QStringLiteral( "name" ), QStringLiteral( "name2" ) );
5650   cond2.insert( QStringLiteral( "expression" ), QStringLiteral( "1 * 0" ) );
5651   conditions << cond2;
5652   config.insert( QStringLiteral( "conditions" ), conditions );
5653 
5654   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:condition" ), config ) );
5655   QVERIFY( alg != nullptr );
5656 
5657   QCOMPARE( alg->outputDefinitions().size(), 2 );
5658   QCOMPARE( alg->outputDefinitions().at( 0 )->name(), QStringLiteral( "name1" ) );
5659   QCOMPARE( alg->outputDefinitions().at( 1 )->name(), QStringLiteral( "name2" ) );
5660 
5661   QVariantMap parameters;
5662   // vector input
5663   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "vl" ) );
5664 
5665   bool ok = false;
5666   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5667   QgsProcessingFeedback feedback;
5668   QVariantMap results;
5669   results = alg->run( parameters, *context, &feedback, &ok, config );
5670   QVERIFY( ok );
5671   QCOMPARE( results.value( QStringLiteral( "name1" ) ).toInt(), 1 );
5672   QCOMPARE( results.value( QStringLiteral( "name2" ) ).toInt(), 0 );
5673 }
5674 
saveLog()5675 void TestQgsProcessingAlgs::saveLog()
5676 {
5677   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:savelog" ) ) );
5678   QVERIFY( alg != nullptr );
5679 
5680   QVariantMap parameters;
5681   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
5682 
5683   bool ok = false;
5684   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5685   QgsProcessingFeedback feedback;
5686   feedback.reportError( QStringLiteral( "test" ) );
5687   QVariantMap results;
5688   results = alg->run( parameters, *context, &feedback, &ok );
5689   QVERIFY( ok );
5690 
5691   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
5692   QFile file( results.value( QStringLiteral( "OUTPUT" ) ).toString() );
5693   QVERIFY( file.open( QFile::ReadOnly  | QIODevice::Text ) );
5694   QCOMPARE( QString( file.readAll() ), QStringLiteral( "test\n" ) );
5695 
5696   parameters.insert( QStringLiteral( "USE_HTML" ), true );
5697   results = alg->run( parameters, *context, &feedback, &ok );
5698   QVERIFY( ok );
5699   QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
5700   QFile file2( results.value( QStringLiteral( "OUTPUT" ) ).toString() );
5701   QVERIFY( file2.open( QFile::ReadOnly  | QIODevice::Text ) );
5702   QCOMPARE( QString( file2.readAll() ), QStringLiteral( "<span style=\"color:red\">test</span><br/>" ) );
5703 }
5704 
setProjectVariable()5705 void TestQgsProcessingAlgs::setProjectVariable()
5706 {
5707   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:setprojectvariable" ) ) );
5708   QVERIFY( alg != nullptr );
5709 
5710   QVariantMap parameters;
5711   parameters.insert( QStringLiteral( "NAME" ), QStringLiteral( "my_var" ) );
5712   parameters.insert( QStringLiteral( "VALUE" ), 11 );
5713 
5714   bool ok = false;
5715   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5716   QgsProject p;
5717   context->setProject( &p );
5718   QgsProcessingFeedback feedback;
5719   QVariantMap results;
5720   results = alg->run( parameters, *context, &feedback, &ok );
5721   QVERIFY( ok );
5722 
5723   std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::projectScope( &p ) );
5724   QCOMPARE( scope->variable( QStringLiteral( "my_var" ) ).toInt(), 11 );
5725 
5726   //overwrite existing
5727   parameters.insert( QStringLiteral( "VALUE" ), 13 );
5728   results = alg->run( parameters, *context, &feedback, &ok );
5729   QVERIFY( ok );
5730   scope.reset( QgsExpressionContextUtils::projectScope( &p ) );
5731   QCOMPARE( scope->variable( QStringLiteral( "my_var" ) ).toInt(), 13 );
5732 }
5733 
5734 #ifndef QT_NO_PRINTER
exportLayoutPdf()5735 void TestQgsProcessingAlgs::exportLayoutPdf()
5736 {
5737   QgsProject p;
5738   QgsPrintLayout *layout = new QgsPrintLayout( &p );
5739   layout->initializeDefaults();
5740   layout->setName( QStringLiteral( "my layout" ) );
5741   p.layoutManager()->addLayout( layout );
5742 
5743   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:printlayouttopdf" ) ) );
5744   QVERIFY( alg != nullptr );
5745 
5746   const QString outputPdf = QDir::tempPath() + "/my_layout.pdf";
5747   if ( QFile::exists( outputPdf ) )
5748     QFile::remove( outputPdf );
5749 
5750   QVariantMap parameters;
5751   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "missing" ) );
5752   parameters.insert( QStringLiteral( "OUTPUT" ), outputPdf );
5753 
5754   bool ok = false;
5755   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5756   context->setProject( &p );
5757   QgsProcessingFeedback feedback;
5758   QVariantMap results;
5759   results = alg->run( parameters, *context, &feedback, &ok );
5760   // invalid layout name
5761   QVERIFY( !ok );
5762   QVERIFY( !QFile::exists( outputPdf ) );
5763 
5764   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "my layout" ) );
5765   results = alg->run( parameters, *context, &feedback, &ok );
5766   QVERIFY( ok );
5767 
5768   QVERIFY( QFile::exists( outputPdf ) );
5769 }
5770 
exportLayoutPng()5771 void TestQgsProcessingAlgs::exportLayoutPng()
5772 {
5773   QgsProject p;
5774   QgsPrintLayout *layout = new QgsPrintLayout( &p );
5775   layout->initializeDefaults();
5776   layout->setName( QStringLiteral( "my layout" ) );
5777 
5778   QgsLayoutItemMap *map = new QgsLayoutItemMap( layout );
5779   map->setBackgroundEnabled( false );
5780   map->setFrameEnabled( false );
5781   map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
5782   layout->addLayoutItem( map );
5783   map->setExtent( mPointsLayer->extent() );
5784 
5785   p.layoutManager()->addLayout( layout );
5786 
5787   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:printlayouttoimage" ) ) );
5788   QVERIFY( alg != nullptr );
5789 
5790   QString outputPng = QDir::tempPath() + "/my_layout.png";
5791   if ( QFile::exists( outputPng ) )
5792     QFile::remove( outputPng );
5793 
5794   QVariantMap parameters;
5795   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "missing" ) );
5796   parameters.insert( QStringLiteral( "OUTPUT" ), outputPng );
5797 
5798   bool ok = false;
5799   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5800   context->setProject( &p );
5801   QgsProcessingFeedback feedback;
5802   QVariantMap results;
5803   results = alg->run( parameters, *context, &feedback, &ok );
5804   // invalid layout name
5805   QVERIFY( !ok );
5806   QVERIFY( !QFile::exists( outputPng ) );
5807 
5808   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "my layout" ) );
5809   results = alg->run( parameters, *context, &feedback, &ok );
5810   QVERIFY( ok );
5811   QVERIFY( QFile::exists( outputPng ) );
5812 
5813   outputPng = QDir::tempPath() + "/my_layout_custom_layers.png";
5814   if ( QFile::exists( outputPng ) )
5815     QFile::remove( outputPng );
5816 
5817   parameters.insert( QStringLiteral( "OUTPUT" ), outputPng );
5818   parameters.insert( QStringLiteral( "LAYERS" ), QVariantList() << QVariant::fromValue( mPointsLayer ) );
5819   parameters.insert( QStringLiteral( "DPI" ), 96 );
5820   results = alg->run( parameters, *context, &feedback, &ok );
5821   QVERIFY( ok );
5822   QVERIFY( QFile::exists( outputPng ) );
5823   QVERIFY( imageCheck( "export_layout_custom_layers", outputPng ) );
5824 }
5825 
exportAtlasLayoutPdf()5826 void TestQgsProcessingAlgs::exportAtlasLayoutPdf()
5827 {
5828   QgsMapLayer *polygonLayer = mPolygonLayer->clone();
5829   QgsProject p;
5830   p.addMapLayers( QList<QgsMapLayer *>() << polygonLayer );
5831 
5832   QgsPrintLayout *layout = new QgsPrintLayout( &p );
5833   layout->initializeDefaults();
5834   layout->setName( QStringLiteral( "my layout" ) );
5835 
5836   QgsLayoutItemMap *map = new QgsLayoutItemMap( layout );
5837   map->setBackgroundEnabled( false );
5838   map->setFrameEnabled( false );
5839   map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
5840   layout->addLayoutItem( map );
5841   map->setExtent( polygonLayer->extent() );
5842 
5843   p.layoutManager()->addLayout( layout );
5844 
5845   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:atlaslayouttopdf" ) ) );
5846   QVERIFY( alg != nullptr );
5847 
5848   const QString outputPdf = QDir::tempPath() + "/my_atlas_layout.pdf";
5849   if ( QFile::exists( outputPdf ) )
5850     QFile::remove( outputPdf );
5851 
5852   QVariantMap parameters;
5853   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "my layout" ) );
5854   parameters.insert( QStringLiteral( "COVERAGE_LAYER" ), QVariant::fromValue( polygonLayer ) );
5855   parameters.insert( QStringLiteral( "OUTPUT" ), outputPdf );
5856   parameters.insert( QStringLiteral( "DPI" ), 96 );
5857 
5858   bool ok = false;
5859   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5860   context->setProject( &p );
5861   QgsProcessingFeedback feedback;
5862   QVariantMap results;
5863   results = alg->run( parameters, *context, &feedback, &ok );
5864   QVERIFY( ok );
5865 
5866   QVERIFY( QFile::exists( outputPdf ) );
5867 }
5868 
exportAtlasLayoutPng()5869 void TestQgsProcessingAlgs::exportAtlasLayoutPng()
5870 {
5871   QgsMapLayer *polygonLayer = mPolygonLayer->clone();
5872   QgsProject p;
5873   p.addMapLayers( QList<QgsMapLayer *>() << polygonLayer );
5874 
5875   QgsPrintLayout *layout = new QgsPrintLayout( &p );
5876   layout->initializeDefaults();
5877   layout->setName( QStringLiteral( "my layout" ) );
5878 
5879   QgsLayoutItemMap *map = new QgsLayoutItemMap( layout );
5880   map->setBackgroundEnabled( false );
5881   map->setFrameEnabled( false );
5882   map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
5883   layout->addLayoutItem( map );
5884   map->setExtent( polygonLayer->extent() );
5885 
5886   p.layoutManager()->addLayout( layout );
5887 
5888   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:atlaslayouttoimage" ) ) );
5889   QVERIFY( alg != nullptr );
5890 
5891   const QDir tempDir( QDir::tempPath() );
5892   if ( !tempDir.mkdir( "my_atlas" ) )
5893   {
5894     const QDir dir( QDir::tempPath() + "/my_atlas" );
5895     const QStringList files = dir.entryList( QStringList() << "*.*", QDir::Files );
5896     for ( const QString &file : files )
5897       QFile::remove( QDir::tempPath() + "/my_atlas/" + file );
5898   }
5899 
5900   QVariantMap parameters;
5901   parameters.insert( QStringLiteral( "LAYOUT" ), QStringLiteral( "my layout" ) );
5902   parameters.insert( QStringLiteral( "COVERAGE_LAYER" ), QVariant::fromValue( polygonLayer ) );
5903   parameters.insert( QStringLiteral( "FOLDER" ), QString( QDir::tempPath() + "/my_atlas" ) );
5904   parameters.insert( QStringLiteral( "FILENAME_EXPRESSION" ), QStringLiteral( "'export_'||@atlas_featurenumber" ) );
5905   parameters.insert( QStringLiteral( "DPI" ), 96 );
5906 
5907   bool ok = false;
5908   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5909   context->setProject( &p );
5910   QgsProcessingFeedback feedback;
5911   QVariantMap results;
5912   results = alg->run( parameters, *context, &feedback, &ok );
5913   QVERIFY( ok );
5914 
5915   QVERIFY( QFile::exists( QDir::tempPath() + "/my_atlas/export_1.png" ) );
5916   QVERIFY( QFile::exists( QDir::tempPath() + "/my_atlas/export_10.png" ) );
5917   QVERIFY( imageCheck( "export_atlas", QDir::tempPath() + "/my_atlas/export_1.png" ) );
5918 
5919   parameters[QStringLiteral( "FILENAME_EXPRESSION" )] = QStringLiteral( "'custom_'||@atlas_featurenumber" );
5920   parameters.insert( QStringLiteral( "LAYERS" ), QVariantList() << QVariant::fromValue( mPointsLayer ) );
5921 
5922   ok = false;
5923   results.clear();
5924   results = alg->run( parameters, *context, &feedback, &ok );
5925   QVERIFY( ok );
5926 
5927   QVERIFY( QFile::exists( QDir::tempPath() + "/my_atlas/custom_1.png" ) );
5928   QVERIFY( QFile::exists( QDir::tempPath() + "/my_atlas/custom_10.png" ) );
5929   QVERIFY( imageCheck( "export_atlas_custom_layers", QDir::tempPath() + "/my_atlas/custom_1.png" ) );
5930 }
5931 #endif
5932 
tinMeshCreation()5933 void TestQgsProcessingAlgs::tinMeshCreation()
5934 {
5935   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:tinmeshcreation" ) ) );
5936   QVERIFY( alg != nullptr );
5937 
5938   QVariantList inputLayers;
5939 
5940   QVariantMap pointLayer;
5941   pointLayer[QStringLiteral( "source" )] = "points";
5942   pointLayer[QStringLiteral( "type" )] = 0;
5943   pointLayer[QStringLiteral( "attributeIndex" )] = mPointsLayer->fields().indexOf( "Importance" );
5944 
5945   inputLayers.append( pointLayer );
5946 
5947   QVariantMap polyLayer;
5948   polyLayer[QStringLiteral( "source" )] = "polygons";
5949   polyLayer[QStringLiteral( "type" )] = 2;
5950   polyLayer[QStringLiteral( "attributeIndex" )] = mPolygonLayer->fields().indexOf( "Value" );
5951 
5952   inputLayers.append( polyLayer );
5953 
5954   QVariantMap parameters;
5955   parameters.insert( QStringLiteral( "SOURCE_DATA" ), inputLayers );
5956   parameters.insert( QStringLiteral( "OUTPUT_MESH" ), QString( QDir::tempPath() + "/meshLayer.2dm" ) );
5957   parameters.insert( QStringLiteral( "MESH_FORMAT" ), 0 );
5958 
5959   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
5960   context->setProject( QgsProject::instance() );
5961   QgsProcessingFeedback feedback;
5962   QVariantMap results;
5963   bool ok = false;
5964   results = alg->run( parameters, *context, &feedback, &ok );
5965   QVERIFY( ok );
5966 
5967   QgsMeshLayer meshLayer( QDir::tempPath() + "/meshLayer.2dm", "mesh", "mdal" );
5968   QVERIFY( meshLayer.isValid() );
5969 
5970   QgsMeshDataProvider *provider = meshLayer.dataProvider();
5971   QCOMPARE( provider->vertexCount(), 627 );
5972   QCOMPARE( provider->faceCount(), 1218 );
5973 
5974   meshLayer.updateTriangularMesh();
5975   QVERIFY( qgsDoubleNear( meshLayer.datasetValue( QgsMeshDatasetIndex( 0, 0 ), QgsPointXY( -103.0, 39.0 ) ).scalar(), 20.0, 0.001 ) );
5976   QVERIFY( qgsDoubleNear( meshLayer.datasetValue( QgsMeshDatasetIndex( 0, 0 ), QgsPointXY( -86.0, 35.0 ) ).scalar(), 1.855, 0.001 ) ) ;
5977 }
5978 
exportMeshVertices()5979 void TestQgsProcessingAlgs::exportMeshVertices()
5980 {
5981   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:exportmeshvertices" ) ) );
5982   QVERIFY( alg != nullptr );
5983 
5984   QVariantMap parameters;
5985   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
5986 
5987   QVariantList datasetGroup;
5988   datasetGroup << 1 << 2;
5989   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
5990 
5991   QVariantMap datasetTime;
5992   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
5993   QVariantList datasetIndex;
5994   datasetIndex << 1 << 1;
5995   datasetTime[QStringLiteral( "value" )] = datasetIndex;
5996   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
5997 
5998   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
5999   parameters.insert( QStringLiteral( "VECTOR_OPTION" ), 2 );
6000 
6001   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6002   context->setProject( QgsProject::instance() );
6003   QgsProcessingFeedback feedback;
6004   QVariantMap results;
6005   bool ok = false;
6006   results = alg->run( parameters, *context, &feedback, &ok );
6007   QVERIFY( ok );
6008 
6009   QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
6010   QVERIFY( resultLayer );
6011   QVERIFY( resultLayer->isValid() );
6012   QVERIFY( resultLayer->geometryType() == QgsWkbTypes::PointGeometry );
6013   QCOMPARE( resultLayer->featureCount(), 5l );
6014   const QgsAttributeList attributeList = resultLayer->attributeList();
6015   QCOMPARE( resultLayer->fields().count(), 5 );
6016   QCOMPARE( resultLayer->fields().at( 0 ).name(), QStringLiteral( "VertexScalarDataset" ) );
6017   QCOMPARE( resultLayer->fields().at( 1 ).name(), QStringLiteral( "VertexVectorDataset_x" ) );
6018   QCOMPARE( resultLayer->fields().at( 2 ).name(), QStringLiteral( "VertexVectorDataset_y" ) );
6019   QCOMPARE( resultLayer->fields().at( 3 ).name(), QStringLiteral( "VertexVectorDataset_mag" ) );
6020   QCOMPARE( resultLayer->fields().at( 4 ).name(), QStringLiteral( "VertexVectorDataset_dir" ) );
6021 
6022   QgsFeatureIterator featIt = resultLayer->getFeatures();
6023   QgsFeature feat;
6024   featIt.nextFeature( feat );
6025   QCOMPARE( QStringLiteral( "PointZ (1000 2000 20)" ), feat.geometry().asWkt() );
6026   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 );
6027   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 );
6028   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 );
6029   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.828, 2 ) );
6030   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6031 
6032   featIt.nextFeature( feat );
6033   QCOMPARE( QStringLiteral( "PointZ (2000 2000 30)" ), feat.geometry().asWkt() );
6034   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 );
6035   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 );
6036   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 );
6037   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 3.605, 2 ) );
6038   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 56.3099, 2 ) );
6039   featIt.nextFeature( feat );
6040   QCOMPARE( QStringLiteral( "PointZ (3000 2000 40)" ), feat.geometry().asWkt() );
6041   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 4.0 );
6042   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 4.0 );
6043   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 );
6044   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 5.0, 2 ) );
6045   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 53.130, 2 ) );
6046   featIt.nextFeature( feat );
6047   QCOMPARE( QStringLiteral( "PointZ (2000 3000 50)" ), feat.geometry().asWkt() );
6048   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 );
6049   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 );
6050   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 );
6051   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 4.242, 2 ) );
6052   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45, 2 ) );
6053   featIt.nextFeature( feat );
6054   QCOMPARE( QStringLiteral( "PointZ (1000 3000 10)" ), feat.geometry().asWkt() );
6055   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 );
6056   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 );
6057   QCOMPARE( feat.attributes().at( 2 ).toDouble(), -1.0 );
6058   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.236, 2 ) );
6059   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 116.565, 2 ) );
6060 }
6061 
exportMeshFaces()6062 void TestQgsProcessingAlgs::exportMeshFaces()
6063 {
6064   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:exportmeshfaces" ) ) );
6065   QVERIFY( alg != nullptr );
6066 
6067   QVariantMap parameters;
6068   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
6069 
6070   QVariantList datasetGroup;
6071   datasetGroup << 3 << 4;
6072   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6073 
6074   QVariantMap datasetTime;
6075   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6076   QVariantList datasetIndex;
6077   datasetIndex << 1 << 1;
6078   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6079   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6080 
6081   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6082   parameters.insert( QStringLiteral( "VECTOR_OPTION" ), 2 );
6083 
6084   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6085   context->setProject( QgsProject::instance() );
6086   QgsProcessingFeedback feedback;
6087   QVariantMap results;
6088   bool ok = false;
6089   results = alg->run( parameters, *context, &feedback, &ok );
6090   QVERIFY( ok );
6091 
6092   QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
6093   QVERIFY( resultLayer );
6094   QVERIFY( resultLayer->isValid() );
6095   QVERIFY( resultLayer->geometryType() == QgsWkbTypes::PolygonGeometry );
6096   QCOMPARE( resultLayer->featureCount(), 2l );
6097   const QgsAttributeList attributeList = resultLayer->attributeList();
6098   QCOMPARE( resultLayer->fields().count(), 5 );
6099   QCOMPARE( resultLayer->fields().at( 0 ).name(), QStringLiteral( "FaceScalarDataset" ) );
6100   QCOMPARE( resultLayer->fields().at( 1 ).name(), QStringLiteral( "FaceVectorDataset_x" ) );
6101   QCOMPARE( resultLayer->fields().at( 2 ).name(), QStringLiteral( "FaceVectorDataset_y" ) );
6102   QCOMPARE( resultLayer->fields().at( 3 ).name(), QStringLiteral( "FaceVectorDataset_mag" ) );
6103   QCOMPARE( resultLayer->fields().at( 4 ).name(), QStringLiteral( "FaceVectorDataset_dir" ) );
6104 
6105   QgsFeatureIterator featIt = resultLayer->getFeatures();
6106   QgsFeature feat;
6107   featIt.nextFeature( feat );
6108   QCOMPARE( QStringLiteral( "PolygonZ ((1000 2000 20, 2000 2000 30, 2000 3000 50, 1000 3000 10, 1000 2000 20))" ), feat.geometry().asWkt() );
6109   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 );
6110   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 );
6111   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 );
6112   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.828, 2 ) );
6113   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6114 
6115   featIt.nextFeature( feat );
6116   QCOMPARE( QStringLiteral( "PolygonZ ((2000 2000 30, 3000 2000 40, 2000 3000 50, 2000 2000 30))" ), feat.geometry().asWkt() );
6117   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 );
6118   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 );
6119   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 );
6120   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 4.242, 2 ) );
6121   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6122 }
6123 
exportMeshEdges()6124 void TestQgsProcessingAlgs::exportMeshEdges()
6125 {
6126   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:exportmeshedges" ) ) );
6127   QVERIFY( alg != nullptr );
6128 
6129   QVariantMap parameters;
6130   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer 1D" );
6131 
6132   QVariantList datasetGroup;
6133   datasetGroup << 1 << 2;
6134   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6135 
6136   QVariantMap datasetTime;
6137   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6138   QVariantList datasetIndex;
6139   datasetIndex << 1 << 1;
6140   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6141   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6142 
6143   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6144   parameters.insert( QStringLiteral( "VECTOR_OPTION" ), 2 );
6145 
6146   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6147   context->setProject( QgsProject::instance() );
6148   QgsProcessingFeedback feedback;
6149   QVariantMap results;
6150   bool ok = false;
6151   results = alg->run( parameters, *context, &feedback, &ok );
6152   QVERIFY( ok );
6153 
6154   QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
6155   QVERIFY( resultLayer );
6156   QVERIFY( resultLayer->isValid() );
6157   QVERIFY( resultLayer->geometryType() == QgsWkbTypes::LineGeometry );
6158   QCOMPARE( resultLayer->featureCount(), 3l );
6159   const QgsAttributeList attributeList = resultLayer->attributeList();
6160   QCOMPARE( resultLayer->fields().count(), 5 );
6161   QCOMPARE( resultLayer->fields().at( 0 ).name(), QStringLiteral( "EdgeScalarDataset" ) );
6162   QCOMPARE( resultLayer->fields().at( 1 ).name(), QStringLiteral( "EdgeVectorDataset_x" ) );
6163   QCOMPARE( resultLayer->fields().at( 2 ).name(), QStringLiteral( "EdgeVectorDataset_y" ) );
6164   QCOMPARE( resultLayer->fields().at( 3 ).name(), QStringLiteral( "EdgeVectorDataset_mag" ) );
6165   QCOMPARE( resultLayer->fields().at( 4 ).name(), QStringLiteral( "EdgeVectorDataset_dir" ) );
6166 
6167   QgsFeatureIterator featIt = resultLayer->getFeatures();
6168   QgsFeature feat;
6169   featIt.nextFeature( feat );
6170   QCOMPARE( QStringLiteral( "LineStringZ (1000 2000 20, 2000 2000 30)" ), feat.geometry().asWkt() );
6171   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 2.0 );
6172   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 2.0 );
6173   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.0 );
6174   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 2.828, 2 ) );
6175   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6176 
6177   featIt.nextFeature( feat );
6178   QCOMPARE( QStringLiteral( "LineStringZ (2000 2000 30, 3000 2000 40)" ), feat.geometry().asWkt() );
6179   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 3.0 );
6180   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 3.0 );
6181   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 3.0 );
6182   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 4.242, 2 ) );
6183   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6184 
6185   featIt.nextFeature( feat );
6186   QCOMPARE( QStringLiteral( "LineStringZ (3000 2000 40, 2000 3000 50)" ), feat.geometry().asWkt() );
6187   QCOMPARE( feat.attributes().at( 0 ).toDouble(), 4.0 );
6188   QCOMPARE( feat.attributes().at( 1 ).toDouble(), 4.0 );
6189   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 4.0 );
6190   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 5.656, 2 ) );
6191   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 45.0, 2 ) );
6192 }
6193 
exportMeshOnGrid()6194 void TestQgsProcessingAlgs::exportMeshOnGrid()
6195 {
6196   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:exportmeshongrid" ) ) );
6197   QVERIFY( alg != nullptr );
6198 
6199   const QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt
6200   const QString meshUri( dataDir + "/mesh/trap_steady_05_3D.nc" );
6201 
6202   QVariantMap parameters;
6203   parameters.insert( QStringLiteral( "INPUT" ), meshUri );
6204 
6205   QVariantList datasetGroup;
6206   for ( int i = 0; i < 12; ++i )
6207     datasetGroup.append( i );
6208   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6209 
6210   QVariantMap datasetTime;
6211   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6212   QVariantList datasetIndex;
6213   datasetIndex << 1 << 1;
6214   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6215   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6216 
6217   parameters.insert( QStringLiteral( "GRID_SPACING" ), 25.0 );
6218 
6219   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6220   parameters.insert( QStringLiteral( "VECTOR_OPTION" ), 2 );
6221 
6222   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6223   context->setProject( QgsProject::instance() );
6224   QgsProcessingFeedback feedback;
6225   QVariantMap results;
6226   bool ok = false;
6227   results = alg->run( parameters, *context, &feedback, &ok );
6228   QVERIFY( ok );
6229 
6230   QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
6231   QVERIFY( resultLayer );
6232   QVERIFY( resultLayer->isValid() );
6233   QVERIFY( resultLayer->geometryType() == QgsWkbTypes::PointGeometry );
6234   QCOMPARE( resultLayer->featureCount(), 205l );
6235   const QgsAttributeList attributeList = resultLayer->attributeList();
6236   QCOMPARE( resultLayer->fields().count(), 21 );
6237   QStringList fieldsName;
6238   fieldsName << QStringLiteral( "Bed Elevation" ) << QStringLiteral( "temperature" ) << QStringLiteral( "temperature/Maximums" )
6239              << QStringLiteral( "temperature/Minimums" ) << QStringLiteral( "temperature/Time at Maximums" ) << QStringLiteral( "temperature/Time at Minimums" )
6240              << QStringLiteral( "velocity_x" ) << QStringLiteral( "velocity_y" ) << QStringLiteral( "velocity_mag" ) << QStringLiteral( "velocity_dir" )
6241              << QStringLiteral( "velocity/Maximums_x" ) << QStringLiteral( "velocity/Maximums_y" ) << QStringLiteral( "velocity/Maximums_mag" ) << QStringLiteral( "velocity/Maximums_dir" )
6242              << QStringLiteral( "velocity/Minimums_x" ) << QStringLiteral( "velocity/Minimums_y" ) << QStringLiteral( "velocity/Minimums_mag" ) << QStringLiteral( "velocity/Minimums_dir" )
6243              << QStringLiteral( "velocity/Time at Maximums" ) << QStringLiteral( "velocity/Time at Minimums" ) << QStringLiteral( "water depth" );
6244 
6245   for ( int i = 0; i < fieldsName.count(); ++i )
6246     QCOMPARE( fieldsName.at( i ), resultLayer->fields().at( i ).name() );
6247 
6248   QgsFeatureIterator featIt = resultLayer->getFeatures();
6249   QgsFeature feat;
6250   for ( int i = 0; i < 8; ++i )
6251     featIt.nextFeature( feat );
6252   QCOMPARE( QStringLiteral( "Point (25 50)" ), feat.geometry().asWkt() );
6253   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 0 ).toDouble(), -5.025, 2 ) );
6254   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 1 ).toDouble(), 1.424, 2 ) );
6255   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 2 ).toDouble(), 5.00, 2 ) );
6256   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 3 ).toDouble(), 1.32e-36, 2 ) );
6257   QVERIFY( qgsDoubleNearSig( feat.attributes().at( 4 ).toDouble(), 0.02776, 2 ) );
6258 
6259 }
6260 
rasterizeMesh()6261 void TestQgsProcessingAlgs::rasterizeMesh()
6262 {
6263   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:meshrasterize" ) ) );
6264   QVERIFY( alg != nullptr );
6265 
6266   QVariantMap parameters;
6267   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
6268 
6269   QVariantList datasetGroup;
6270   datasetGroup << 1 << 2 << 3;
6271   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6272 
6273   QVariantMap datasetTime;
6274   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6275   QVariantList datasetIndex;
6276   datasetIndex << 1 << 1;
6277   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6278   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6279 
6280   parameters.insert( QStringLiteral( "PIXEL_SIZE" ), 200.0 );
6281 
6282   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6283 
6284   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6285   context->setProject( QgsProject::instance() );
6286   QgsProcessingFeedback feedback;
6287   QVariantMap results;
6288   bool ok = false;
6289   results = alg->run( parameters, *context, &feedback, &ok );
6290   QVERIFY( ok );
6291 
6292   std::unique_ptr<QgsRasterLayer> outputRaster = std::make_unique< QgsRasterLayer >( results.value( QStringLiteral( "OUTPUT" ) ).toString(), "output", "gdal" );
6293   QVERIFY( outputRaster );
6294   QVERIFY( outputRaster->isValid() );
6295   QgsRasterDataProvider *outputProvider = outputRaster->dataProvider();
6296 
6297   QCOMPARE( outputProvider->bandCount(), 3 );
6298   QCOMPARE( outputProvider->xSize(), 10 );
6299   QCOMPARE( outputProvider->ySize(), 5 );
6300 
6301   std::unique_ptr<QgsRasterBlock> outputBlock_1( outputProvider->block( 1, outputRaster->extent(), 10, 5 ) );
6302   std::unique_ptr<QgsRasterBlock> outputBlock_2( outputProvider->block( 2, outputRaster->extent(), 10, 5 ) );
6303   std::unique_ptr<QgsRasterBlock> outputBlock_3( outputProvider->block( 3, outputRaster->extent(), 10, 5 ) );
6304 
6305   // load expected result
6306   const QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt
6307   std::unique_ptr<QgsRasterLayer> expectedRaster = std::make_unique< QgsRasterLayer >( dataDir + "/mesh/rasterized_mesh.tif", "expected", "gdal" );
6308   QVERIFY( expectedRaster );
6309   QVERIFY( expectedRaster->isValid() );
6310   QgsRasterDataProvider *expectedProvider = outputRaster->dataProvider();
6311   std::unique_ptr<QgsRasterBlock> expectedBlock_1( expectedProvider->block( 1, expectedRaster->extent(), 10, 5 ) );
6312   std::unique_ptr<QgsRasterBlock> expectedBlock_2( expectedProvider->block( 2, expectedRaster->extent(), 10, 5 ) );
6313   std::unique_ptr<QgsRasterBlock> expectedBlock_3( expectedProvider->block( 3, expectedRaster->extent(), 10, 5 ) );
6314 
6315   for ( int ix = 0; ix < 10; ++ix )
6316   {
6317     for ( int iy = 0; iy < 5; ++iy )
6318     {
6319       if ( !( std::isnan( outputBlock_1->value( iy, ix ) ) && std::isnan( expectedBlock_1->value( iy, ix ) ) ) )
6320         QCOMPARE( outputBlock_1->value( iy, ix ), expectedBlock_1->value( iy, ix ) );
6321       if ( !( std::isnan( outputBlock_2->value( iy, ix ) ) && std::isnan( expectedBlock_2->value( iy, ix ) ) ) )
6322         QCOMPARE( outputBlock_2->value( iy, ix ), expectedBlock_2->value( iy, ix ) );
6323       if ( !( std::isnan( outputBlock_2->value( iy, ix ) ) && std::isnan( expectedBlock_2->value( iy, ix ) ) ) )
6324         QCOMPARE( outputBlock_3->value( iy, ix ), expectedBlock_3->value( iy, ix ) );
6325     }
6326   }
6327 }
6328 
exportMeshContours()6329 void TestQgsProcessingAlgs::exportMeshContours()
6330 {
6331   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:meshcontours" ) ) );
6332   QVERIFY( alg != nullptr );
6333 
6334   QVariantMap parameters;
6335   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
6336 
6337   QVariantList datasetGroup;
6338   datasetGroup << 1 << 2 << 3;
6339   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6340 
6341   QVariantMap datasetTime;
6342   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6343   QVariantList datasetIndex;
6344   datasetIndex << 1 << 1;
6345   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6346   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6347 
6348   parameters.insert( QStringLiteral( "OUTPUT_LINES" ), QgsProcessing::TEMPORARY_OUTPUT );
6349   parameters.insert( QStringLiteral( "OUTPUT_POLYGONS" ), QgsProcessing::TEMPORARY_OUTPUT );
6350 
6351   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6352   context->setProject( QgsProject::instance() );
6353   QgsProcessingFeedback feedback;
6354   QVariantMap results;
6355   bool ok = false;
6356 
6357   results = alg->run( parameters, *context, &feedback, &ok );
6358   QVERIFY( !ok );
6359 
6360   // min>max
6361   parameters.insert( QStringLiteral( "INCREMENT" ), 0.5 );
6362   parameters.insert( QStringLiteral( "MINIMUM" ), 5.0 );
6363   parameters.insert( QStringLiteral( "MAXIMUM" ), 2.0 );
6364 
6365   results = alg->run( parameters, *context, &feedback, &ok );
6366   QVERIFY( !ok );
6367 
6368   // min-max<increrment
6369   parameters.insert( QStringLiteral( "INCREMENT" ), 10 );
6370   parameters.insert( QStringLiteral( "MINIMUM" ), 5.0 );
6371   parameters.insert( QStringLiteral( "MAXIMUM" ), 2.0 );
6372 
6373   results = alg->run( parameters, *context, &feedback, &ok );
6374   QVERIFY( !ok );
6375 
6376   // min-max<increrment
6377   parameters.insert( QStringLiteral( "INCREMENT" ), 2 );
6378   parameters.insert( QStringLiteral( "MINIMUM" ), 0.25 );
6379   parameters.insert( QStringLiteral( "MAXIMUM" ), 6.25 );
6380 
6381   results = alg->run( parameters, *context, &feedback, &ok );
6382   QVERIFY( ok );
6383 
6384   QgsVectorLayer *resultLinesLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT_LINES" ) ).toString() ) );
6385   QVERIFY( resultLinesLayer );
6386   QVERIFY( resultLinesLayer->isValid() );
6387   QgsAttributeList attributeList = resultLinesLayer->attributeList();
6388   QCOMPARE( resultLinesLayer->fields().count(), 3 );
6389   QCOMPARE( resultLinesLayer->fields().at( 0 ).name(), QStringLiteral( "group" ) );
6390   QCOMPARE( resultLinesLayer->fields().at( 1 ).name(), QStringLiteral( "time" ) );
6391   QCOMPARE( resultLinesLayer->fields().at( 2 ).name(), QStringLiteral( "value" ) );
6392 
6393   QCOMPARE( resultLinesLayer->featureCount(), 4l );
6394   QgsFeatureIterator featIt = resultLinesLayer->getFeatures();
6395   QgsFeature feat;
6396   featIt.nextFeature( feat );
6397   QCOMPARE( QStringLiteral( "LineStringZ (1250 3000 20, 1250 2250 27.5, 1250 2000 22.5)" ), feat.geometry().asWkt() );
6398   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexScalarDataset" ) );
6399   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6400   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.25 );
6401   featIt.nextFeature( feat );
6402   QCOMPARE( QStringLiteral( "LineStringZ (1006.94319345290614365 3000 10.27772773811624596, 1000 2976.48044676110157525 10.23519553238898538)" ), feat.geometry().asWkt() );
6403   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexVectorDataset" ) );
6404   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6405   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.25 );
6406   featIt.nextFeature( feat );
6407   QCOMPARE( QStringLiteral( "LineStringZ (2009.71706923721990279 2990.28293076277986984 49.90282930762779756, 2462.15304528350043256 2000 34.62153045283500319)" ), feat.geometry().asWkt() );
6408   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexVectorDataset" ) );
6409   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6410   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 4.25 );
6411   featIt.nextFeature( feat );
6412   QCOMPARE( QStringLiteral( "LineStringZ (1500 3000 30, 1500 2500 35, 1500 2000 25)" ), feat.geometry().asWkt() );
6413   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "FaceScalarDataset" ) );
6414   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6415   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.25 );
6416 
6417   QgsVectorLayer *resultpolygonLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT_POLYGONS" ) ).toString() ) );
6418   QVERIFY( resultpolygonLayer );
6419   QVERIFY( resultpolygonLayer->isValid() );
6420   attributeList = resultpolygonLayer->attributeList();
6421   QCOMPARE( resultpolygonLayer->fields().count(), 4 );
6422   QCOMPARE( resultpolygonLayer->fields().at( 0 ).name(), QStringLiteral( "group" ) );
6423   QCOMPARE( resultpolygonLayer->fields().at( 1 ).name(), QStringLiteral( "time" ) );
6424   QCOMPARE( resultpolygonLayer->fields().at( 2 ).name(), QStringLiteral( "min_value" ) );
6425   QCOMPARE( resultpolygonLayer->fields().at( 3 ).name(), QStringLiteral( "max_value" ) );
6426 
6427   QCOMPARE( resultpolygonLayer->featureCount(), 6l );
6428   featIt = resultpolygonLayer->getFeatures();
6429   featIt.nextFeature( feat );
6430   QCOMPARE( QStringLiteral( "PolygonZ ((1250 2250 27.5, 1250 2000 22.5, 1000 2000 20, 1000 3000 10, 1250 3000 20, 1250 2250 27.5))" ), feat.geometry().asWkt() );
6431   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexScalarDataset" ) );
6432   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6433   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 0.25 );
6434   QCOMPARE( feat.attributes().at( 3 ).toDouble(), 2.25 );
6435   featIt.nextFeature( feat );
6436   QCOMPARE( QStringLiteral( "PolygonZ ((2000 2000 30, 1250 2000 22.5, 1250 2250 27.5, 1250 3000 20, 2000 3000 50, 3000 2000 40, 2000 2000 30))" ), feat.geometry().asWkt() );
6437   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexScalarDataset" ) );
6438   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6439   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.25 );
6440   QCOMPARE( feat.attributes().at( 3 ).toDouble(), 4.25 );
6441   featIt.nextFeature( feat );
6442   QCOMPARE( QStringLiteral( "PolygonZ ((1006.94319345290614365 3000 10.27772773811624596, 1000 3000 10, 1000 2976.48044676110157525 10.23519553238898538, 1006.94319345290614365 3000 10.27772773811624596))" ), feat.geometry().asWkt() );
6443   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "VertexVectorDataset" ) );
6444   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6445   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 0.25 );
6446   QCOMPARE( feat.attributes().at( 3 ).toDouble(), 2.25 );
6447   featIt.nextFeature( feat );
6448   featIt.nextFeature( feat );
6449   QCOMPARE( QStringLiteral( "PolygonZ ((1500 2500 35, 1500 2000 25, 1000 2000 20, 1000 3000 10, 1500 3000 30, 1500 2500 35))" ), feat.geometry().asWkt() );
6450   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "FaceScalarDataset" ) );
6451   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6452   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 0.25 );
6453   QCOMPARE( feat.attributes().at( 3 ).toDouble(), 2.25 );
6454   featIt.nextFeature( feat );
6455   QCOMPARE( QStringLiteral( "PolygonZ ((2000 2000 30, 1500 2000 25, 1500 2500 35, 1500 3000 30, 2000 3000 50, 3000 2000 40, 2000 2000 30))" ), feat.geometry().asWkt() );
6456   QCOMPARE( feat.attributes().at( 0 ).toString(), QStringLiteral( "FaceScalarDataset" ) );
6457   QCOMPARE( feat.attributes().at( 1 ).toString(), QStringLiteral( "1950-01-01 01:00:00" ) );
6458   QCOMPARE( feat.attributes().at( 2 ).toDouble(), 2.25 );
6459   QCOMPARE( feat.attributes().at( 3 ).toDouble(), 4.25 );
6460 
6461   parameters.insert( QStringLiteral( "CONTOUR_LEVEL_LIST" ), QStringLiteral( "4,2,3" ) );
6462   results = alg->run( parameters, *context, &feedback, &ok );
6463   QVERIFY( !ok );
6464 
6465   parameters.insert( QStringLiteral( "CONTOUR_LEVEL_LIST" ), QStringLiteral( "2,2,3" ) );
6466   results = alg->run( parameters, *context, &feedback, &ok );
6467   QVERIFY( !ok );
6468 
6469   parameters.insert( QStringLiteral( "CONTOUR_LEVEL_LIST" ), QStringLiteral( "1,2,3" ) );
6470   results = alg->run( parameters, *context, &feedback, &ok );
6471   QVERIFY( ok );
6472 }
6473 
exportMeshCrossSection()6474 void TestQgsProcessingAlgs::exportMeshCrossSection()
6475 {
6476   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:meshexportcrosssection" ) ) );
6477   QVERIFY( alg != nullptr );
6478 
6479   QVariantMap parameters;
6480   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
6481 
6482   QVariantList datasetGroup;
6483   datasetGroup << 1 << 2 << 3;
6484   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6485 
6486   QVariantMap datasetTime;
6487   datasetTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6488   QVariantList datasetIndex;
6489   datasetIndex << 1 << 1;
6490   datasetTime[QStringLiteral( "value" )] = datasetIndex;
6491   parameters.insert( QStringLiteral( "DATASET_TIME" ), datasetTime );
6492 
6493   parameters.insert( QStringLiteral( "RESOLUTION" ), 100 );
6494 
6495   const QString outputPath = QDir::tempPath() + "/test_mesh_xs.csv";
6496   parameters.insert( QStringLiteral( "OUTPUT" ), outputPath );
6497 
6498   QgsVectorLayer *layerLine = new QgsVectorLayer( QStringLiteral( "LineString" ),
6499       QStringLiteral( "lines_for_xs" ),
6500       QStringLiteral( "memory" ) );
6501 
6502   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6503   context->setProject( QgsProject::instance() );
6504   QgsProcessingFeedback feedback;
6505   QVariantMap results;
6506   bool ok = false;
6507 
6508   results = alg->run( parameters, *context, &feedback, &ok );
6509   QVERIFY( !ok );
6510 
6511   QStringList wktLines;
6512   wktLines << QStringLiteral( "LineString (1500 2200, 2500 2200)" );
6513   wktLines << QStringLiteral( "LineString (1500 1500, 1500 3200)" );
6514 
6515   QgsFeatureList flist;
6516   for ( const QString &wkt : wktLines )
6517   {
6518     QgsFeature feat;
6519     feat.setGeometry( QgsGeometry::fromWkt( wkt ) );
6520     flist << feat;
6521   }
6522   layerLine->dataProvider()->addFeatures( flist );
6523   QgsProject::instance()->addMapLayer( layerLine );  QgsProject::instance()->addMapLayer( layerLine );
6524   parameters.insert( QStringLiteral( "INPUT_LINES" ), layerLine->name() );
6525 
6526   results = alg->run( parameters, *context, &feedback, &ok );
6527   QVERIFY( ok );
6528 
6529   QFile outputFile( outputPath );
6530   QVERIFY( outputFile.open( QIODevice::ReadOnly ) );
6531   QTextStream textStream( &outputFile );
6532   const QString header = textStream.readLine();
6533   QCOMPARE( header, QStringLiteral( "fid,x,y,offset,VertexScalarDataset,VertexVectorDataset,FaceScalarDataset" ) );
6534 
6535   QStringList expectedLines;
6536   expectedLines << QStringLiteral( "1,1500.00,2200.00,0.00,2.50,3.33,2.00" )
6537                 << QStringLiteral( "1,1600.00,2200.00,100.00,2.60,3.41,2.00" )
6538                 << QStringLiteral( "1,1700.00,2200.00,200.00,2.70,3.48,2.00" )
6539                 << QStringLiteral( "1,1800.00,2200.00,300.00,2.80,3.56,2.00" )
6540                 << QStringLiteral( "1,1900.00,2200.00,400.00,2.90,3.64,2.00" )
6541                 << QStringLiteral( "1,2000.00,2200.00,500.00,3.00,3.72,2.00" )
6542                 << QStringLiteral( "1,2100.00,2200.00,600.00,3.10,3.86,3.00" )
6543                 << QStringLiteral( "1,2200.00,2200.00,700.00,3.20,4.00,3.00" )
6544                 << QStringLiteral( "1,2300.00,2200.00,800.00,3.30,4.14,3.00" )
6545                 << QStringLiteral( "1,2400.00,2200.00,900.00,3.40,4.28,3.00" )
6546                 << QStringLiteral( "1,2500.00,2200.00,1000.00,3.50,4.42,3.00" )
6547                 << QStringLiteral( "2,1500.00,1500.00,0.00, , , " )
6548                 << QStringLiteral( "2,1500.00,1600.00,100.00, , , " )
6549                 << QStringLiteral( "2,1500.00,1700.00,200.00, , , " )
6550                 << QStringLiteral( "2,1500.00,1800.00,300.00, , , " )
6551                 << QStringLiteral( "2,1500.00,1900.00,400.00, , , " )
6552                 << QStringLiteral( "2,1500.00,2000.00,500.00,2.50,3.20,2.00" )
6553                 << QStringLiteral( "2,1500.00,2100.00,600.00,2.50,3.26,2.00" )
6554                 << QStringLiteral( "2,1500.00,2200.00,700.00,2.50,3.33,2.00" )
6555                 << QStringLiteral( "2,1500.00,2300.00,800.00,2.50,3.40,2.00" )
6556                 << QStringLiteral( "2,1500.00,2400.00,900.00,2.50,3.47,2.00" )
6557                 << QStringLiteral( "2,1500.00,2500.00,1000.00,2.50,3.54,2.00" )
6558                 << QStringLiteral( "2,1500.00,2600.00,1100.00,2.50,3.33,2.00" )
6559                 << QStringLiteral( "2,1500.00,2700.00,1200.00,2.50,3.14,2.00" )
6560                 << QStringLiteral( "2,1500.00,2800.00,1300.00,2.50,2.97,2.00" )
6561                 << QStringLiteral( "2,1500.00,2900.00,1400.00,2.50,2.82,2.00" )
6562                 << QStringLiteral( "2,1500.00,3000.00,1500.00,2.50,2.69,2.00" )
6563                 << QStringLiteral( "2,1500.00,3100.00,1600.00, , , " )
6564                 << QStringLiteral( "2,1500.00,3200.00,1700.00, , , " );
6565   QString line = textStream.readLine();
6566   int i = 0;
6567   QVERIFY( !line.isEmpty() );
6568   while ( !line.isEmpty() )
6569   {
6570     QCOMPARE( line, expectedLines.at( i ) );
6571     ++i;
6572     line = textStream.readLine();
6573   }
6574 
6575   QVERIFY( i == expectedLines.count() );
6576 }
6577 
fileDownloader()6578 void TestQgsProcessingAlgs::fileDownloader()
6579 {
6580   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:filedownloader" ) ) );
6581   QVERIFY( alg != nullptr );
6582 
6583   QVariantMap parameters;
6584   parameters.insert( QStringLiteral( "URL" ), QStringLiteral( "https://version.qgis.org/version.txt" ) );
6585   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6586 
6587   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6588   QgsProcessingFeedback feedback;
6589   QVariantMap results;
6590   bool ok = false;
6591 
6592   results = alg->run( parameters, *context, &feedback, &ok );
6593   QVERIFY( ok );
6594   // verify that temporary outputs have the URL file extension appended
6595   QVERIFY( results.value( QStringLiteral( "OUTPUT" ) ).toString().endsWith( QLatin1String( ".txt" ) ) );
6596 }
6597 
rasterize()6598 void TestQgsProcessingAlgs::rasterize()
6599 {
6600   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:rasterize" ) ) );
6601   QVERIFY( alg != nullptr );
6602 
6603   const QString outputTif = QDir::tempPath() + "/rasterize_output.tif";
6604   if ( QFile::exists( outputTif ) )
6605     QFile::remove( outputTif );
6606 
6607   QVariantMap parameters;
6608   parameters.insert( QStringLiteral( "EXTENT" ), QStringLiteral( "-120,-80,15,55" ) );
6609   parameters.insert( QStringLiteral( "TILE_SIZE" ), 320 );
6610   parameters.insert( QStringLiteral( "MAP_UNITS_PER_PIXEL" ), 0.125 );
6611   parameters.insert( QStringLiteral( "OUTPUT" ), outputTif );
6612 
6613   // create a temporary project with three layers, but only two are visible
6614   // (to test that the algorithm in the default setup without defined LAYERS or MAP_THEME uses only vsisible
6615   // layers that and in the correct order)
6616   QgsProject project;
6617   const QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
6618   QgsVectorLayer *pointsLayer = new QgsVectorLayer( dataDir + "/points.shp", QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
6619   QgsVectorLayer *linesLayer = new QgsVectorLayer( dataDir + "/lines.shp", QStringLiteral( "lines" ), QStringLiteral( "ogr" ) );
6620   QgsVectorLayer *polygonLayer = new QgsVectorLayer( dataDir + "/polys.shp", QStringLiteral( "polygons" ), QStringLiteral( "ogr" ) );
6621   QVERIFY( pointsLayer->isValid() && linesLayer->isValid() && polygonLayer->isValid() );
6622   project.addMapLayers( QList<QgsMapLayer *>() << pointsLayer << linesLayer << polygonLayer );
6623   QgsLayerTreeLayer *nodePolygons = project.layerTreeRoot()->findLayer( polygonLayer );
6624   QVERIFY( nodePolygons );
6625   nodePolygons->setItemVisibilityChecked( false );
6626 
6627   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6628   context->setProject( &project );
6629   QgsProcessingFeedback feedback;
6630   QVariantMap results;
6631   bool ok = false;
6632 
6633   results = alg->run( parameters, *context, &feedback, &ok );
6634   QVERIFY( ok );
6635   QVERIFY( QFile::exists( outputTif ) );
6636 
6637   QgsRenderChecker checker;
6638   checker.setControlPathPrefix( QStringLiteral( "processing_algorithm" ) );
6639   checker.setControlExtension( "tif" );
6640   checker.setControlName( "expected_rasterize" );
6641   checker.setRenderedImage( outputTif );
6642   QVERIFY( checker.compareImages( "rasterize", 500 ) );
6643 }
6644 
convertGpxFeatureType()6645 void TestQgsProcessingAlgs::convertGpxFeatureType()
6646 {
6647   // test generation of babel argument lists
6648   QStringList processArgs;
6649   QStringList logArgs;
6650 
6651   QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( QStringLiteral( "/home/me/my input file.gpx" ),
6652       QStringLiteral( "/home/me/my output file.gpx" ),
6653       QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute,
6654       processArgs, logArgs );
6655   QCOMPARE( processArgs, QStringList(
6656   {
6657     QStringLiteral( "-i" ),
6658     QStringLiteral( "gpx" ),
6659     QStringLiteral( "-f" ),
6660     QStringLiteral( "/home/me/my input file.gpx" ),
6661     QStringLiteral( "-x" ),
6662     QStringLiteral( "transform,wpt=rte,del" ),
6663     QStringLiteral( "-o" ),
6664     QStringLiteral( "gpx" ),
6665     QStringLiteral( "-F" ),
6666     QStringLiteral( "/home/me/my output file.gpx" )
6667   } ) );
6668   // when showing the babel command, filenames should be wrapped in "", which is what QProcess does internally (hence the processArgs don't have these)
6669   QCOMPARE( logArgs, QStringList(
6670   {
6671     QStringLiteral( "-i" ),
6672     QStringLiteral( "gpx" ),
6673     QStringLiteral( "-f" ),
6674     QStringLiteral( "\"/home/me/my input file.gpx\"" ),
6675     QStringLiteral( "-x" ),
6676     QStringLiteral( "transform,wpt=rte,del" ),
6677     QStringLiteral( "-o" ),
6678     QStringLiteral( "gpx" ),
6679     QStringLiteral( "-F" ),
6680     QStringLiteral( "\"/home/me/my output file.gpx\"" )
6681   } ) );
6682 
6683   logArgs.clear();
6684   processArgs.clear();
6685   QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( QStringLiteral( "/home/me/my input file.gpx" ),
6686       QStringLiteral( "/home/me/my output file.gpx" ),
6687       QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack,
6688       processArgs, logArgs );
6689   QCOMPARE( processArgs, QStringList(
6690   {
6691     QStringLiteral( "-i" ),
6692     QStringLiteral( "gpx" ),
6693     QStringLiteral( "-f" ),
6694     QStringLiteral( "/home/me/my input file.gpx" ),
6695     QStringLiteral( "-x" ),
6696     QStringLiteral( "transform,wpt=trk,del" ),
6697     QStringLiteral( "-o" ),
6698     QStringLiteral( "gpx" ),
6699     QStringLiteral( "-F" ),
6700     QStringLiteral( "/home/me/my output file.gpx" )
6701   } ) );
6702   // when showing the babel command, filenames should be wrapped in "", which is what QProcess does internally (hence the processArgs don't have these)
6703   QCOMPARE( logArgs, QStringList(
6704   {
6705     QStringLiteral( "-i" ),
6706     QStringLiteral( "gpx" ),
6707     QStringLiteral( "-f" ),
6708     QStringLiteral( "\"/home/me/my input file.gpx\"" ),
6709     QStringLiteral( "-x" ),
6710     QStringLiteral( "transform,wpt=trk,del" ),
6711     QStringLiteral( "-o" ),
6712     QStringLiteral( "gpx" ),
6713     QStringLiteral( "-F" ),
6714     QStringLiteral( "\"/home/me/my output file.gpx\"" )
6715   } ) );
6716 
6717   logArgs.clear();
6718   processArgs.clear();
6719 
6720   QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( QStringLiteral( "/home/me/my input file.gpx" ),
6721       QStringLiteral( "/home/me/my output file.gpx" ),
6722       QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints,
6723       processArgs, logArgs );
6724   QCOMPARE( processArgs, QStringList(
6725   {
6726     QStringLiteral( "-i" ),
6727     QStringLiteral( "gpx" ),
6728     QStringLiteral( "-f" ),
6729     QStringLiteral( "/home/me/my input file.gpx" ),
6730     QStringLiteral( "-x" ),
6731     QStringLiteral( "transform,rte=wpt,del" ),
6732     QStringLiteral( "-o" ),
6733     QStringLiteral( "gpx" ),
6734     QStringLiteral( "-F" ),
6735     QStringLiteral( "/home/me/my output file.gpx" )
6736   } ) );
6737   // when showing the babel command, filenames should be wrapped in "", which is what QProcess does internally (hence the processArgs don't have these)
6738   QCOMPARE( logArgs, QStringList(
6739   {
6740     QStringLiteral( "-i" ),
6741     QStringLiteral( "gpx" ),
6742     QStringLiteral( "-f" ),
6743     QStringLiteral( "\"/home/me/my input file.gpx\"" ),
6744     QStringLiteral( "-x" ),
6745     QStringLiteral( "transform,rte=wpt,del" ),
6746     QStringLiteral( "-o" ),
6747     QStringLiteral( "gpx" ),
6748     QStringLiteral( "-F" ),
6749     QStringLiteral( "\"/home/me/my output file.gpx\"" )
6750   } ) );
6751 
6752 
6753   logArgs.clear();
6754   processArgs.clear();
6755 
6756   QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( QStringLiteral( "/home/me/my input file.gpx" ),
6757       QStringLiteral( "/home/me/my output file.gpx" ),
6758       QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints,
6759       processArgs, logArgs );
6760   QCOMPARE( processArgs, QStringList(
6761   {
6762     QStringLiteral( "-i" ),
6763     QStringLiteral( "gpx" ),
6764     QStringLiteral( "-f" ),
6765     QStringLiteral( "/home/me/my input file.gpx" ),
6766     QStringLiteral( "-x" ),
6767     QStringLiteral( "transform,trk=wpt,del" ),
6768     QStringLiteral( "-o" ),
6769     QStringLiteral( "gpx" ),
6770     QStringLiteral( "-F" ),
6771     QStringLiteral( "/home/me/my output file.gpx" )
6772   } ) );
6773   // when showing the babel command, filenames should be wrapped in "", which is what QProcess does internally (hence the processArgs don't have these)
6774   QCOMPARE( logArgs, QStringList(
6775   {
6776     QStringLiteral( "-i" ),
6777     QStringLiteral( "gpx" ),
6778     QStringLiteral( "-f" ),
6779     QStringLiteral( "\"/home/me/my input file.gpx\"" ),
6780     QStringLiteral( "-x" ),
6781     QStringLiteral( "transform,trk=wpt,del" ),
6782     QStringLiteral( "-o" ),
6783     QStringLiteral( "gpx" ),
6784     QStringLiteral( "-F" ),
6785     QStringLiteral( "\"/home/me/my output file.gpx\"" )
6786   } ) );
6787 }
6788 
convertGpsData()6789 void TestQgsProcessingAlgs::convertGpsData()
6790 {
6791   TestProcessingFeedback feedback;
6792 
6793   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:convertgpsdata" ) ) );
6794   QVERIFY( alg != nullptr );
6795 
6796   QVariantMap parameters;
6797   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "%1/GARMIN_ATRK.NVM" ).arg( TEST_DATA_DIR ) );
6798   parameters.insert( QStringLiteral( "FORMAT" ), QStringLiteral( "garmin_xt" ) );
6799   parameters.insert( QStringLiteral( "FEATURE_TYPE" ), 0 ); // waypoints
6800   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6801 
6802   bool ok = false;
6803   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6804 
6805   QVariantMap results;
6806   results = alg->run( parameters, *context, &feedback, &ok );
6807   // garmin_xt format does not support waypoints, exception should have been raised
6808   QVERIFY( !ok );
6809 
6810   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "The GPSBabel format \u201Cgarmin_xt\u201D does not support converting waypoints." ) );
6811   feedback.errors.clear();
6812 
6813   parameters.insert( QStringLiteral( "FEATURE_TYPE" ), 1 ); // routes
6814   ok = false;
6815   results = alg->run( parameters, *context, &feedback, &ok );
6816   // garmin_xt format does not support routes, exception should have been raised
6817   QVERIFY( !ok );
6818   QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "The GPSBabel format \u201Cgarmin_xt\u201D does not support converting routes." ) );
6819   feedback.errors.clear();
6820 
6821   parameters.insert( QStringLiteral( "FEATURE_TYPE" ), 2 ); // tracks
6822   ok = false;
6823   results = alg->run( parameters, *context, &feedback, &ok );
6824   // garmin_xt format does support tracks!
6825   QVERIFY( ok );
6826 
6827   QgsVectorLayer *resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT_LAYER" ) ).toString() ) );
6828   QVERIFY( resultLayer );
6829   QCOMPARE( resultLayer->providerType(), QStringLiteral( "gpx" ) );
6830   QCOMPARE( resultLayer->wkbType(), QgsWkbTypes::LineString );
6831   QCOMPARE( resultLayer->featureCount(), 1LL );
6832 
6833   // algorithm should also run when given the description for a format, not the format name
6834   parameters.insert( QStringLiteral( "FORMAT" ), QStringLiteral( "Mobile Garmin XT Track files" ) );
6835   ok = false;
6836   results = alg->run( parameters, *context, &feedback, &ok );
6837   QVERIFY( ok );
6838   resultLayer = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT_LAYER" ) ).toString() ) );
6839   QVERIFY( resultLayer );
6840   QCOMPARE( resultLayer->providerType(), QStringLiteral( "gpx" ) );
6841   QCOMPARE( resultLayer->wkbType(), QgsWkbTypes::LineString );
6842   QCOMPARE( resultLayer->featureCount(), 1LL );
6843 
6844   // try with a format which doesn't exist
6845   feedback.errors.clear();
6846   parameters.insert( QStringLiteral( "FORMAT" ), QStringLiteral( "not a format" ) );
6847   ok = false;
6848   results = alg->run( parameters, *context, &feedback, &ok );
6849   QVERIFY( !ok );
6850   QVERIFY( feedback.errors.value( 0 ).startsWith( QStringLiteral( "Unknown GPSBabel format \u201Cnot a format\u201D." ) ) );
6851 }
6852 
downloadGpsData()6853 void TestQgsProcessingAlgs::downloadGpsData()
6854 {
6855   TestProcessingFeedback feedback;
6856 
6857   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:downloadgpsdata" ) ) );
6858   QVERIFY( alg != nullptr );
6859 
6860   QVariantMap parameters;
6861   parameters.insert( QStringLiteral( "DEVICE" ), QStringLiteral( "xxx" ) );
6862   parameters.insert( QStringLiteral( "PORT" ), QStringLiteral( "usb:" ) );
6863   parameters.insert( QStringLiteral( "FEATURE_TYPE" ), 0 ); // waypoints
6864   parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
6865 
6866   bool ok = false;
6867   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6868 
6869   QVariantMap results;
6870   results = alg->run( parameters, *context, &feedback, &ok );
6871   // invalid device
6872   QVERIFY( !ok );
6873 
6874   QVERIFY( feedback.errors.value( 0 ).startsWith( QStringLiteral( "Unknown GPSBabel device \u201Cxxx\u201D. Valid devices are:" ) ) );
6875   feedback.errors.clear();
6876 
6877   parameters.insert( QStringLiteral( "DEVICE" ), QStringLiteral( "Garmin serial" ) );
6878   parameters.insert( QStringLiteral( "PORT" ), QStringLiteral( "not a port" ) );
6879   ok = false;
6880   results = alg->run( parameters, *context, &feedback, &ok );
6881   // invalid port
6882   QVERIFY( !ok );
6883   QVERIFY( feedback.errors.value( 0 ).startsWith( QStringLiteral( "Unknown port \u201Cnot a port\u201D. Valid ports are:" ) ) );
6884   feedback.errors.clear();
6885 }
6886 
uploadGpsData()6887 void TestQgsProcessingAlgs::uploadGpsData()
6888 {
6889   TestProcessingFeedback feedback;
6890 
6891   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:downloadgpsdata" ) ) );
6892   QVERIFY( alg != nullptr );
6893 
6894   QVariantMap parameters;
6895   parameters.insert( QStringLiteral( "DEVICE" ), QStringLiteral( "xxx" ) );
6896   parameters.insert( QStringLiteral( "PORT" ), QStringLiteral( "usb:" ) );
6897   parameters.insert( QStringLiteral( "FEATURE_TYPE" ), 0 ); // waypoints
6898   parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "%1/layers.gpx" ).arg( TEST_DATA_DIR ) );
6899 
6900   bool ok = false;
6901   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6902 
6903   QVariantMap results;
6904   results = alg->run( parameters, *context, &feedback, &ok );
6905   // invalid device
6906   QVERIFY( !ok );
6907 
6908   QVERIFY( feedback.errors.value( 0 ).startsWith( QStringLiteral( "Unknown GPSBabel device \u201Cxxx\u201D. Valid devices are:" ) ) );
6909   feedback.errors.clear();
6910 
6911   parameters.insert( QStringLiteral( "DEVICE" ), QStringLiteral( "Garmin serial" ) );
6912   parameters.insert( QStringLiteral( "PORT" ), QStringLiteral( "not a port" ) );
6913   ok = false;
6914   results = alg->run( parameters, *context, &feedback, &ok );
6915   // invalid port
6916   QVERIFY( !ok );
6917   QVERIFY( feedback.errors.value( 0 ).startsWith( QStringLiteral( "Unknown port \u201Cnot a port\u201D. Valid ports are:" ) ) );
6918   feedback.errors.clear();
6919 }
6920 
transferMainAnnotationLayer()6921 void TestQgsProcessingAlgs::transferMainAnnotationLayer()
6922 {
6923   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:transferannotationsfrommain" ) ) );
6924   QVERIFY( alg != nullptr );
6925 
6926   QgsProject p;
6927   p.mainAnnotationLayer()->addItem( new QgsAnnotationMarkerItem( QgsPoint( 1, 2 ) ) );
6928 
6929   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6930   context->setProject( &p );
6931   QgsProcessingFeedback feedback;
6932   QVariantMap results;
6933   bool ok = false;
6934 
6935   QVariantMap parameters;
6936   parameters.insert( QStringLiteral( "LAYER_NAME" ), QStringLiteral( "my annotations" ) );
6937   results = alg->run( parameters, *context, &feedback, &ok );
6938   QVERIFY( ok );
6939 
6940   QCOMPARE( p.mainAnnotationLayer()->items().size(), 0 );
6941   QgsAnnotationLayer *newLayer = qobject_cast< QgsAnnotationLayer * >( p.mapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) );
6942   QCOMPARE( newLayer->name(), QStringLiteral( "my annotations" ) );
6943   QCOMPARE( newLayer->items().size(), 1 );
6944 }
6945 
exportMeshTimeSeries()6946 void TestQgsProcessingAlgs::exportMeshTimeSeries()
6947 {
6948   std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:meshexporttimeseries" ) ) );
6949   QVERIFY( alg != nullptr );
6950 
6951   QVariantMap parameters;
6952   parameters.insert( QStringLiteral( "INPUT" ), "mesh layer" );
6953 
6954   QVariantList datasetGroup;
6955   datasetGroup << 1 << 2 << 3;
6956   parameters.insert( QStringLiteral( "DATASET_GROUPS" ), datasetGroup );
6957 
6958   QVariantMap datasetStartTime;
6959   datasetStartTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6960   QVariantList datasetIndexStart;
6961   datasetIndexStart << 1 << 0;
6962   datasetStartTime[QStringLiteral( "value" )] = datasetIndexStart;
6963   parameters.insert( QStringLiteral( "STARTING_TIME" ), datasetStartTime );
6964 
6965   QVariantMap datasetEndTime;
6966   datasetEndTime[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
6967   QVariantList datasetIndexEnd;
6968   datasetIndexEnd << 1 << 1;
6969   datasetEndTime[QStringLiteral( "value" )] = datasetIndexEnd;
6970   parameters.insert( QStringLiteral( "FINISHING_TIME" ), datasetEndTime );
6971 
6972   const QString outputPath = QDir::tempPath() + "/test_mesh_ts.csv";
6973   parameters.insert( QStringLiteral( "OUTPUT" ), outputPath );
6974 
6975   QgsVectorLayer *layerPoints = new QgsVectorLayer( QStringLiteral( "Point" ),
6976       QStringLiteral( "points_for_ts" ),
6977       QStringLiteral( "memory" ) );
6978 
6979   std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
6980   context->setProject( QgsProject::instance() );
6981   QgsProcessingFeedback feedback;
6982   QVariantMap results;
6983   bool ok = false;
6984 
6985   results = alg->run( parameters, *context, &feedback, &ok );
6986   QVERIFY( !ok );
6987 
6988   QStringList wktPoints;
6989   wktPoints << QStringLiteral( "Point (1500 2200)" );
6990   wktPoints << QStringLiteral( "Point (1500 1500)" );
6991   wktPoints << QStringLiteral( "Point (2500 2100)" );
6992 
6993   QgsFeatureList flist;
6994   for ( const QString &wkt : wktPoints )
6995   {
6996     QgsFeature feat;
6997     feat.setGeometry( QgsGeometry::fromWkt( wkt ) );
6998     flist << feat;
6999   }
7000   layerPoints->dataProvider()->addFeatures( flist );
7001   QgsProject::instance()->addMapLayer( layerPoints );  QgsProject::instance()->addMapLayer( layerPoints );
7002   parameters.insert( QStringLiteral( "INPUT_POINTS" ), layerPoints->name() );
7003 
7004   results = alg->run( parameters, *context, &feedback, &ok );
7005   QVERIFY( ok );
7006 
7007   QFile outputFile( outputPath );
7008   QVERIFY( outputFile.open( QIODevice::ReadOnly ) );
7009   QTextStream textStream( &outputFile );
7010   QString header = textStream.readLine();
7011   QCOMPARE( header, QStringLiteral( "fid,x,y,time,VertexScalarDataset,VertexVectorDataset,FaceScalarDataset" ) );
7012 
7013   QStringList expectedLines;
7014   expectedLines << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:00:00,1.50,1.92,1.00" )
7015                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 01:00:00,2.50,3.33,2.00" )
7016                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:00:00,2.50,2.97,2.00" )
7017                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 01:00:00,3.50,4.36,3.00" );
7018 
7019   QString line = textStream.readLine();
7020   int i = 0;
7021   QVERIFY( !line.isEmpty() );
7022   while ( !line.isEmpty() )
7023   {
7024     QCOMPARE( line, expectedLines.at( i ) );
7025     ++i;
7026     line = textStream.readLine();
7027   }
7028   QVERIFY( i == expectedLines.count() );
7029   outputFile.close();
7030 
7031   parameters.insert( QStringLiteral( "TIME_STEP" ), 0.1 );
7032 
7033   results = alg->run( parameters, *context, &feedback, &ok );
7034   QVERIFY( ok );
7035 
7036   QVERIFY( outputFile.open( QIODevice::ReadOnly ) );
7037   header = textStream.readLine();
7038   QCOMPARE( header, QStringLiteral( "fid,x,y,time,VertexScalarDataset,VertexVectorDataset,FaceScalarDataset" ) );
7039 
7040   expectedLines.clear();
7041   expectedLines << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:00:00,1.50,1.92,1.00" )
7042                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:06:00,1.50,1.92,1.00" )
7043                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:12:00,1.50,1.92,1.00" )
7044                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:18:00,1.50,1.92,1.00" )
7045                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:24:00,1.50,1.92,1.00" )
7046                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:30:00,1.50,1.92,1.00" )
7047                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:36:00,1.50,1.92,1.00" )
7048                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:42:00,1.50,1.92,1.00" )
7049                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:48:00,1.50,1.92,1.00" )
7050                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 00:54:00,1.50,1.92,1.00" )
7051                 << QStringLiteral( "1,1500.00,2200.00,1950-01-01 01:00:00,2.50,3.33,2.00" )
7052                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:00:00,2.50,2.97,2.00" )
7053                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:06:00,2.50,2.97,2.00" )
7054                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:12:00,2.50,2.97,2.00" )
7055                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:18:00,2.50,2.97,2.00" )
7056                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:24:00,2.50,2.97,2.00" )
7057                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:30:00,2.50,2.97,2.00" )
7058                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:36:00,2.50,2.97,2.00" )
7059                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:42:00,2.50,2.97,2.00" )
7060                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:48:00,2.50,2.97,2.00" )
7061                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 00:54:00,2.50,2.97,2.00" )
7062                 << QStringLiteral( "3,2500.00,2100.00,1950-01-01 01:00:00,3.50,4.36,3.00" );
7063 
7064   line = textStream.readLine();
7065   i = 0;
7066   QVERIFY( !line.isEmpty() );
7067   while ( !line.isEmpty() )
7068   {
7069     QCOMPARE( line, expectedLines.at( i ) );
7070     ++i;
7071     line = textStream.readLine();
7072   }
7073   QVERIFY( i == expectedLines.count() );
7074   outputFile.close();
7075 }
7076 
7077 
imageCheck(const QString & testName,const QString & renderedImage)7078 bool TestQgsProcessingAlgs::imageCheck( const QString &testName, const QString &renderedImage )
7079 {
7080   QgsRenderChecker checker;
7081   checker.setControlPathPrefix( QStringLiteral( "processing_algorithm" ) );
7082   checker.setControlName( "expected_" + testName );
7083   checker.setRenderedImage( renderedImage );
7084   checker.setSizeTolerance( 3, 3 );
7085   const bool equal = checker.compareImages( testName, 500 );
7086   return equal;
7087 }
7088 
7089 QGSTEST_MAIN( TestQgsProcessingAlgs )
7090 #include "testqgsprocessingalgs.moc"
7091