Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE][processing] Export print layout atlas as image(s) algorithm
- Loading branch information
Showing
5 changed files
with
324 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
250 changes: 250 additions & 0 deletions
250
src/analysis/processing/qgsalgorithmlayoutatlastoimage.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
/*************************************************************************** | ||
qgsalgorithmlayoutatlastoimage.cpp | ||
--------------------- | ||
begin : June 2020 | ||
copyright : (C) 2020 by Mathieu Pellerin | ||
email : nirvn dot asia at gmail dot com | ||
***************************************************************************/ | ||
|
||
/*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
#include "qgsalgorithmlayoutatlastoimage.h" | ||
#include "qgslayout.h" | ||
#include "qgslayoutatlas.h" | ||
#include "qgsprintlayout.h" | ||
#include "qgsprocessingoutputs.h" | ||
#include "qgslayoutexporter.h" | ||
|
||
#include <QImageWriter> | ||
|
||
///@cond PRIVATE | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::name() const | ||
{ | ||
return QStringLiteral( "printlayoutatlastoimage" ); | ||
} | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::displayName() const | ||
{ | ||
return QObject::tr( "Export print layout atlas as image(s)" ); | ||
} | ||
|
||
QStringList QgsLayoutAtlasToImageAlgorithm::tags() const | ||
{ | ||
return QObject::tr( "layout,atlas,composer,composition,save,png,jpeg,jpg" ).split( ',' ); | ||
} | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::group() const | ||
{ | ||
return QObject::tr( "Cartography" ); | ||
} | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::groupId() const | ||
{ | ||
return QStringLiteral( "cartography" ); | ||
} | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::shortDescription() const | ||
{ | ||
return QObject::tr( "Exports a print layout atlas as one or more images." ); | ||
} | ||
|
||
QString QgsLayoutAtlasToImageAlgorithm::shortHelpString() const | ||
{ | ||
return QObject::tr( "This algorithm outputs a print layout atkas as one or more images file (e.g. PNG or JPEG images)." ); | ||
} | ||
|
||
void QgsLayoutAtlasToImageAlgorithm::initAlgorithm( const QVariantMap & ) | ||
{ | ||
addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Print layout" ) ) ); | ||
|
||
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "COVERAGE_LAYER" ), QObject::tr( "Atlas coverage layer" ) ) ); | ||
|
||
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILTER_EXPRESSION" ), QObject::tr( "Atlas coverage layer's filter expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) ); | ||
|
||
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Atlas coverage layer's feature sort expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) ); | ||
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SORTBY_REVERSE" ), QObject::tr( "Reverse sort order (used when a sort expression is provided)" ), false, true ) ); | ||
|
||
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILENAME_EXPRESSION" ), QObject::tr( "Output filename expression" ), QStringLiteral( "'output_'||@atlas_featurenumber" ), QStringLiteral( "COVERAGE_LAYER" ) ) ); | ||
|
||
addParameter( new QgsProcessingParameterFile( QStringLiteral( "FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) ); | ||
|
||
QStringList imageFormats; | ||
const auto supportedImageFormats { QImageWriter::supportedImageFormats() }; | ||
for ( const auto format : QImageWriter::supportedImageFormats() ) | ||
{ | ||
if ( format == QByteArray( "svg" ) ) | ||
continue; | ||
imageFormats << QString( format ); | ||
} | ||
std::unique_ptr< QgsProcessingParameterEnum > extensionParam = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "EXTENSION" ), QObject::tr( "Image format" ), imageFormats, false, imageFormats.indexOf( QStringLiteral( "png" ) ) ); | ||
extensionParam->setFlags( extensionParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( extensionParam.release() ); | ||
|
||
std::unique_ptr< QgsProcessingParameterNumber > dpiParam = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DPI" ), QObject::tr( "DPI (leave blank for default layout DPI)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0 ); | ||
dpiParam->setFlags( dpiParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( dpiParam.release() ); | ||
|
||
std::unique_ptr< QgsProcessingParameterBoolean > appendGeorefParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "GEOREFERENCE" ), QObject::tr( "Generate world file" ), true ); | ||
appendGeorefParam->setFlags( appendGeorefParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( appendGeorefParam.release() ); | ||
|
||
std::unique_ptr< QgsProcessingParameterBoolean > exportRDFParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "INCLUDE_METADATA" ), QObject::tr( "Export RDF metadata (title, author, etc.)" ), true ); | ||
exportRDFParam->setFlags( exportRDFParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( exportRDFParam.release() ); | ||
|
||
std::unique_ptr< QgsProcessingParameterBoolean > antialias = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true ); | ||
antialias->setFlags( antialias->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( antialias.release() ); | ||
} | ||
|
||
QgsProcessingAlgorithm::Flags QgsLayoutAtlasToImageAlgorithm::flags() const | ||
{ | ||
return QgsProcessingAlgorithm::flags() | FlagNoThreading; | ||
} | ||
|
||
QgsLayoutAtlasToImageAlgorithm *QgsLayoutAtlasToImageAlgorithm::createInstance() const | ||
{ | ||
return new QgsLayoutAtlasToImageAlgorithm(); | ||
} | ||
|
||
QVariantMap QgsLayoutAtlasToImageAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) | ||
{ | ||
// this needs to be done in main thread, layouts are not thread safe | ||
QgsPrintLayout *layout = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context ); | ||
if ( !layout ) | ||
throw QgsProcessingException( QObject::tr( "Cannot find layout with name \"%1\"" ).arg( parameters.value( QStringLiteral( "LAYOUT" ) ).toString() ) ); | ||
|
||
QString error; | ||
QgsLayoutAtlas *atlas = layout->atlas(); | ||
|
||
QgsVectorLayer *previousCoverageLayer = atlas->coverageLayer(); | ||
const bool previousEnabled = atlas->enabled(); | ||
const QString previousFilenameExpression = atlas->filenameExpression(); | ||
const QString previousFilterExpression = atlas->filterExpression(); | ||
const bool previousSortFeatures = atlas->sortFeatures(); | ||
const bool previousSortAscending = atlas->sortAscending(); | ||
const QString previousSortExpression = atlas->sortExpression(); | ||
|
||
auto restoreAtlas = [ atlas, | ||
previousCoverageLayer, | ||
previousEnabled, | ||
previousFilenameExpression, | ||
previousFilterExpression, | ||
previousSortFeatures, | ||
previousSortAscending, | ||
previousSortExpression ]() | ||
{ | ||
QString error; | ||
atlas->setEnabled( previousEnabled ); | ||
atlas->setCoverageLayer( previousCoverageLayer ); | ||
atlas->setFilenameExpression( previousFilenameExpression, error ); | ||
atlas->setFilterExpression( previousFilterExpression, error ); | ||
atlas->setSortFeatures( previousSortFeatures ); | ||
atlas->setSortAscending( previousSortAscending ); | ||
atlas->setSortExpression( previousSortExpression ); | ||
}; | ||
|
||
QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "COVERAGE_LAYER" ), context ); | ||
atlas->setEnabled( true ); | ||
atlas->setCoverageLayer( layer ); | ||
|
||
QString expression = parameterAsString( parameters, QStringLiteral( "FILENAME_EXPRESSION" ), context ); | ||
atlas->setFilenameExpression( expression, error ); | ||
if ( !error.isEmpty() ) | ||
{ | ||
restoreAtlas(); | ||
throw QgsProcessingException( QObject::tr( "Error setting atlas filename expression" ) ); | ||
} | ||
|
||
expression = parameterAsString( parameters, QStringLiteral( "FILTER_EXPRESSION" ), context ); | ||
atlas->setFilterExpression( expression, error ); | ||
|
||
expression = parameterAsString( parameters, QStringLiteral( "SORTBY_EXPRESSION" ), context ); | ||
if ( !expression.isEmpty() ) | ||
{ | ||
const bool sortByReverse = parameterAsBool( parameters, QStringLiteral( "SORTBY_REVERSE" ), context ); | ||
atlas->setSortFeatures( true ); | ||
atlas->setSortExpression( expression ); | ||
atlas->setSortAscending( !sortByReverse ); | ||
} | ||
else | ||
{ | ||
atlas->setSortFeatures( false ); | ||
} | ||
|
||
const QString directory = parameterAsFileOutput( parameters, QStringLiteral( "FOLDER" ), context ); | ||
QString fileName = QDir( directory ).filePath( QStringLiteral( "atlas" ) ); | ||
|
||
QStringList imageFormats; | ||
const auto supportedImageFormats { QImageWriter::supportedImageFormats() }; | ||
for ( const auto format : QImageWriter::supportedImageFormats() ) | ||
{ | ||
if ( format == QByteArray( "svg" ) ) | ||
continue; | ||
imageFormats << QString( format ); | ||
} | ||
int idx = parameterAsEnum( parameters, QStringLiteral( "EXTENSION" ), context ); | ||
QString extension = '.' + imageFormats.at( idx ); | ||
|
||
QgsLayoutExporter exporter( layout ); | ||
QgsLayoutExporter::ImageExportSettings settings; | ||
|
||
if ( parameters.value( QStringLiteral( "DPI" ) ).isValid() ) | ||
{ | ||
settings.dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context ); | ||
} | ||
|
||
settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context ); | ||
settings.generateWorldFile = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context ); | ||
|
||
if ( parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context ) ) | ||
settings.flags = settings.flags | QgsLayoutRenderContext::FlagAntialiasing; | ||
else | ||
settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagAntialiasing; | ||
|
||
QgsLayoutExporter::ExportResult result = exporter.exportToImage( atlas, fileName, extension, settings, error, feedback ); | ||
restoreAtlas(); | ||
|
||
switch ( result ) | ||
{ | ||
case QgsLayoutExporter::Success: | ||
{ | ||
feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( directory ) ) ); | ||
break; | ||
} | ||
|
||
case QgsLayoutExporter::FileError: | ||
throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( directory ) ) ); | ||
|
||
case QgsLayoutExporter::MemoryError: | ||
throw QgsProcessingException( QObject::tr( "Trying to create the image " | ||
"resulted in a memory overflow.\n\n" | ||
"Please try a lower resolution or a smaller paper size." ) ); | ||
|
||
case QgsLayoutExporter::IteratorError: | ||
throw QgsProcessingException( QObject::tr( "Error encountered while exporting atlas." ) ); | ||
|
||
case QgsLayoutExporter::SvgLayerError: | ||
case QgsLayoutExporter::PrintError: | ||
case QgsLayoutExporter::Canceled: | ||
// no meaning for imageexports, will not be encountered | ||
break; | ||
} | ||
|
||
feedback->setProgress( 100 ); | ||
|
||
QVariantMap outputs; | ||
outputs.insert( QStringLiteral( "FOLDER" ), directory ); | ||
return outputs; | ||
} | ||
|
||
///@endcond | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/*************************************************************************** | ||
qgsalgorithmlayoutatlastoimage.h | ||
--------------------- | ||
begin : June 2020 | ||
copyright : (C) 2020 by Mathieu Pellerin | ||
email : nirvn dot asia at gmail dot com | ||
***************************************************************************/ | ||
|
||
/*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
#ifndef QGSALGORITHMLAYOUTATLASTOIMAGE_H | ||
#define QGSALGORITHMLAYOUTATLASTOIMAGE_H | ||
|
||
#define SIP_NO_FILE | ||
|
||
#include "qgis_sip.h" | ||
#include "qgsprocessingalgorithm.h" | ||
|
||
///@cond PRIVATE | ||
|
||
/** | ||
* Native export layout to image algorithm. | ||
*/ | ||
class QgsLayoutAtlasToImageAlgorithm : public QgsProcessingAlgorithm | ||
{ | ||
|
||
public: | ||
|
||
QgsLayoutAtlasToImageAlgorithm() = default; | ||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; | ||
Flags flags() const override; | ||
QString name() const override; | ||
QString displayName() const override; | ||
QStringList tags() const override; | ||
QString group() const override; | ||
QString groupId() const override; | ||
QString shortDescription() const override; | ||
QString shortHelpString() const override; | ||
QgsLayoutAtlasToImageAlgorithm *createInstance() const override SIP_FACTORY; | ||
|
||
protected: | ||
|
||
QVariantMap processAlgorithm( const QVariantMap ¶meters, | ||
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; | ||
|
||
|
||
}; | ||
|
||
///@endcond PRIVATE | ||
|
||
#endif // QGSALGORITHMLAYOUTATLASTOIMAGE_H | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters