From d8d18b20ce4cd2985de0bab6b6bb12587904628c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 16 Mar 2021 18:46:18 +1000 Subject: [PATCH] Merge pull request #42187 from wonder-sk/fix-rasterize-alg-layer-list Fix wrong list of layers in Rasterize alg (Convert map to raster) --- .docker/qgis3-build-deps.dockerfile | 1 + .../auto_generated/qgsrenderchecker.sip.in | 9 +++- .../processing/qgsalgorithmrasterize.cpp | 13 ++++- src/core/qgsrenderchecker.cpp | 2 +- src/core/qgsrenderchecker.h | 9 +++- tests/src/analysis/testqgsprocessingalgs.cpp | 50 ++++++++++++++++++ .../expected_rasterize/expected_rasterize.tif | Bin 0 -> 307694 bytes 7 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/testdata/control_images/processing_algorithm/expected_rasterize/expected_rasterize.tif diff --git a/.docker/qgis3-build-deps.dockerfile b/.docker/qgis3-build-deps.dockerfile index 9e8476e82bd..776a57def10 100644 --- a/.docker/qgis3-build-deps.dockerfile +++ b/.docker/qgis3-build-deps.dockerfile @@ -98,6 +98,7 @@ RUN apt-get update \ qt3d-defaultgeometryloader-plugin \ qt3d-gltfsceneio-plugin \ qt3d-scene2d-plugin \ + qt5-image-formats-plugins \ qt5keychain-dev \ qtbase5-dev \ qtdeclarative5-dev-tools \ diff --git a/python/core/auto_generated/qgsrenderchecker.sip.in b/python/core/auto_generated/qgsrenderchecker.sip.in index 48b48cdaa42..162180b6fef 100644 --- a/python/core/auto_generated/qgsrenderchecker.sip.in +++ b/python/core/auto_generated/qgsrenderchecker.sip.in @@ -82,7 +82,14 @@ Sets the base directory ``name`` for the control image (with control image path suffixed). The path to the image will be constructed like this: -:py:func:`~QgsRenderChecker.controlImagePath` + '/' + control name + '/' + control name + '.png' +:py:func:`~QgsRenderChecker.controlImagePath` + '/' + control name + '/' + control name + '.' + extension ('png' by default) +%End + + void setControlExtension( const QString &extension ); +%Docstring +Sets file extension for the control image. By default it is "png" + +.. versionadded:: 3.20 %End void setControlPathPrefix( const QString &name ); diff --git a/src/analysis/processing/qgsalgorithmrasterize.cpp b/src/analysis/processing/qgsalgorithmrasterize.cpp index 7bee3189015..8e3f7d80765 100644 --- a/src/analysis/processing/qgsalgorithmrasterize.cpp +++ b/src/analysis/processing/qgsalgorithmrasterize.cpp @@ -29,6 +29,7 @@ #include "qgsmaprenderercustompainterjob.h" #include "gdal.h" #include "qgsgdalutils.h" +#include "qgslayertree.h" #include @@ -333,8 +334,16 @@ bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, Qgs // Still no layers? Get them all from the project if ( mMapLayers.size() == 0 ) { - const auto constLayers { context.project()->mapLayers().values() }; - for ( const QgsMapLayer *ml : constLayers ) + QList layers; + QgsLayerTree *root = context.project()->layerTreeRoot(); + for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() ) + { + QgsMapLayer *layer = nodeLayer->layer(); + if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) ) + layers << layer; + } + + for ( const QgsMapLayer *ml : qgis::as_const( layers ) ) { mMapLayers.push_back( std::unique_ptr( ml->clone( ) ) ); } diff --git a/src/core/qgsrenderchecker.cpp b/src/core/qgsrenderchecker.cpp index 92ebe68f7d9..d67b96241f6 100644 --- a/src/core/qgsrenderchecker.cpp +++ b/src/core/qgsrenderchecker.cpp @@ -46,7 +46,7 @@ void QgsRenderChecker::setControlImagePath( const QString &path ) void QgsRenderChecker::setControlName( const QString &name ) { mControlName = name; - mExpectedImageFile = controlImagePath() + name + '/' + mControlPathSuffix + name + ".png"; + mExpectedImageFile = controlImagePath() + name + '/' + mControlPathSuffix + name + "." + mControlExtension; } void QgsRenderChecker::setControlPathSuffix( const QString &name ) diff --git a/src/core/qgsrenderchecker.h b/src/core/qgsrenderchecker.h index ffec638d850..1a47ba6092b 100644 --- a/src/core/qgsrenderchecker.h +++ b/src/core/qgsrenderchecker.h @@ -98,10 +98,16 @@ class CORE_EXPORT QgsRenderChecker * suffixed). * * The path to the image will be constructed like this: - * controlImagePath() + '/' + control name + '/' + control name + '.png' + * controlImagePath() + '/' + control name + '/' + control name + '.' + extension ('png' by default) */ void setControlName( const QString &name ); + /** + * Sets file extension for the control image. By default it is "png" + * \since QGIS 3.20 + */ + void setControlExtension( const QString &extension ) { mControlExtension = extension; } + /** * Sets the path prefix where the control images are kept. * This will be appended to controlImagePath(). @@ -237,6 +243,7 @@ class CORE_EXPORT QgsRenderChecker int mMaxSizeDifferenceY = 0; int mElapsedTimeTarget = 0; QgsMapSettings mMapSettings; + QString mControlExtension = QStringLiteral( "png" ); QString mControlPathPrefix; QString mControlPathSuffix; QVector mDashMessages; diff --git a/tests/src/analysis/testqgsprocessingalgs.cpp b/tests/src/analysis/testqgsprocessingalgs.cpp index c612282ad80..1193ecd934d 100644 --- a/tests/src/analysis/testqgsprocessingalgs.cpp +++ b/tests/src/analysis/testqgsprocessingalgs.cpp @@ -39,6 +39,7 @@ #include "qgsreclassifyutils.h" #include "qgsalgorithmrasterlogicalop.h" #include "qgsprintlayout.h" +#include "qgslayertree.h" #include "qgslayoutmanager.h" #include "qgslayoutitemmap.h" #include "qgsmarkersymbollayer.h" @@ -178,6 +179,8 @@ class TestQgsProcessingAlgs: public QObject void fileDownloader(); + void rasterize(); + private: bool imageCheck( const QString &testName, const QString &renderedImage ); @@ -6488,6 +6491,53 @@ void TestQgsProcessingAlgs::fileDownloader() QVERIFY( results.value( QStringLiteral( "OUTPUT" ) ).toString().endsWith( QLatin1String( ".txt" ) ) ); } +void TestQgsProcessingAlgs::rasterize() +{ + std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:rasterize" ) ) ); + QVERIFY( alg != nullptr ); + + QString outputTif = QDir::tempPath() + "/rasterize_output.tif"; + if ( QFile::exists( outputTif ) ) + QFile::remove( outputTif ); + + QVariantMap parameters; + parameters.insert( QStringLiteral( "EXTENT" ), QStringLiteral( "-120,-80,15,55" ) ); + parameters.insert( QStringLiteral( "TILE_SIZE" ), 320 ); + parameters.insert( QStringLiteral( "MAP_UNITS_PER_PIXEL" ), 0.125 ); + parameters.insert( QStringLiteral( "OUTPUT" ), outputTif ); + + // create a temporary project with three layers, but only two are visible + // (to test that the algorithm in the default setup without defined LAYERS or MAP_THEME uses only vsisible + // layers that and in the correct order) + QgsProject project; + QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt + QgsVectorLayer *pointsLayer = new QgsVectorLayer( dataDir + "/points.shp", QStringLiteral( "points" ), QStringLiteral( "ogr" ) ); + QgsVectorLayer *linesLayer = new QgsVectorLayer( dataDir + "/lines.shp", QStringLiteral( "lines" ), QStringLiteral( "ogr" ) ); + QgsVectorLayer *polygonLayer = new QgsVectorLayer( dataDir + "/polys.shp", QStringLiteral( "polygons" ), QStringLiteral( "ogr" ) ); + QVERIFY( pointsLayer->isValid() && linesLayer->isValid() && polygonLayer->isValid() ); + project.addMapLayers( QList() << pointsLayer << linesLayer << polygonLayer ); + QgsLayerTreeLayer *nodePolygons = project.layerTreeRoot()->findLayer( polygonLayer ); + QVERIFY( nodePolygons ); + nodePolygons->setItemVisibilityChecked( false ); + + std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >(); + context->setProject( &project ); + QgsProcessingFeedback feedback; + QVariantMap results; + bool ok = false; + + results = alg->run( parameters, *context, &feedback, &ok ); + QVERIFY( ok ); + QVERIFY( QFile::exists( outputTif ) ); + + QgsRenderChecker checker; + checker.setControlPathPrefix( QStringLiteral( "processing_algorithm" ) ); + checker.setControlExtension( "tif" ); + checker.setControlName( "expected_rasterize" ); + checker.setRenderedImage( outputTif ); + QVERIFY( checker.compareImages( "rasterize", 500 ) ); +} + void TestQgsProcessingAlgs::exportMeshTimeSeries() { std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:meshexporttimeseries" ) ) ); diff --git a/tests/testdata/control_images/processing_algorithm/expected_rasterize/expected_rasterize.tif b/tests/testdata/control_images/processing_algorithm/expected_rasterize/expected_rasterize.tif new file mode 100644 index 0000000000000000000000000000000000000000..3a7605fc2c97e474e45a58b595e5c2a96f381c5b GIT binary patch literal 307694 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 literal 0 HcmV?d00001