Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[feature] Add processing algorithm for exporting an atlas to multiple
PDF files

Follows the same logic as the existing "Export atlas layout as PDF"
algorithm, but exports each atlas feature as a separate PDF file
  • Loading branch information
Samweli authored and nyalldawson committed Nov 29, 2021
1 parent eb871be commit 0ab5f17
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 42 deletions.
229 changes: 192 additions & 37 deletions src/analysis/processing/qgsalgorithmlayoutatlastopdf.cpp
Expand Up @@ -26,45 +26,29 @@

///@cond PRIVATE

QString QgsLayoutAtlasToPdfAlgorithm::name() const
{
return QStringLiteral( "atlaslayouttopdf" );
}

QString QgsLayoutAtlasToPdfAlgorithm::displayName() const
{
return QObject::tr( "Export atlas layout as PDF" );
}
// QgsLayoutAtlasToPdfAlgorithmBase

QStringList QgsLayoutAtlasToPdfAlgorithm::tags() const
QStringList QgsLayoutAtlasToPdfAlgorithmBase::tags() const
{
return QObject::tr( "layout,atlas,composer,composition,save" ).split( ',' );
}

QString QgsLayoutAtlasToPdfAlgorithm::group() const
QString QgsLayoutAtlasToPdfAlgorithmBase::group() const
{
return QObject::tr( "Cartography" );
}

QString QgsLayoutAtlasToPdfAlgorithm::groupId() const
QString QgsLayoutAtlasToPdfAlgorithmBase::groupId() const
{
return QStringLiteral( "cartography" );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortDescription() const
{
return QObject::tr( "Exports an atlas layout as a PDF." );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortHelpString() const
QgsProcessingAlgorithm::Flags QgsLayoutAtlasToPdfAlgorithmBase::flags() const
{
return QObject::tr( "This algorithm outputs an atlas layout as a PDF file.\n\n"
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
"settings off." );
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
}

void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
void QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Atlas layout" ) ) );

Expand All @@ -73,8 +57,6 @@ void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "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 QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "PDF file" ), QObject::tr( "PDF Format" ) + " (*.pdf *.PDF)" ) );

std::unique_ptr< QgsProcessingParameterMultipleLayers > layersParam = std::make_unique< QgsProcessingParameterMultipleLayers>( QStringLiteral( "LAYERS" ), QObject::tr( "Map layers to assign to unlocked map item(s)" ), QgsProcessing::TypeMapLayer, QVariant(), true );
layersParam->setFlags( layersParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( layersParam.release() );
Expand Down Expand Up @@ -114,17 +96,7 @@ void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( textFormat.release() );
}

QgsProcessingAlgorithm::Flags QgsLayoutAtlasToPdfAlgorithm::flags() const
{
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
}

QgsLayoutAtlasToPdfAlgorithm *QgsLayoutAtlasToPdfAlgorithm::createInstance() const
{
return new QgsLayoutAtlasToPdfAlgorithm();
}

QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
bool QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
// this needs to be done in main thread, layouts are not thread safe
QgsPrintLayout *l = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context );
Expand Down Expand Up @@ -179,7 +151,7 @@ QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &p
settings.appendGeoreference = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context );
settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context );
settings.simplifyGeometries = parameterAsBool( parameters, QStringLiteral( "SIMPLIFY" ), context );
settings.textRenderFormat = parameterAsEnum( parameters, QStringLiteral( "TEXT_FORMAT" ), context ) == 0 ? Qgis::TextRenderFormat::AlwaysOutlines : Qgis::TextRenderFormat::AlwaysText;
settings.textRenderFormat = parameterAsEnum( parameters, QStringLiteral( "TEXT_FORMAT" ), context ) == 0 ? QgsRenderContext::TextFormatAlwaysOutlines : QgsRenderContext::TextFormatAlwaysText;

if ( parameterAsBool( parameters, QStringLiteral( "DISABLE_TILED" ), context ) )
settings.flags = settings.flags | QgsLayoutRenderContext::FlagDisableTiledRasterLayerRenders;
Expand All @@ -204,6 +176,81 @@ QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &p
}
}

mAtlas = atlas;
mExporter = exporter;
mSettings = settings;
mError = error;

return true;
}

QgsLayoutAtlas *QgsLayoutAtlasToPdfAlgorithmBase::atlas()
{
return mAtlas;
}

QgsLayoutExporter QgsLayoutAtlasToPdfAlgorithmBase::exporter()
{
return mExporter;
}

QgsLayoutExporter::PdfExportSettings QgsLayoutAtlasToPdfAlgorithmBase::settings()
{
return mSettings;
}

QString QgsLayoutAtlasToPdfAlgorithmBase::error()
{
return mError;
}



// QgsLayoutAtlasToPdfAlgorithm

QString QgsLayoutAtlasToPdfAlgorithm::name() const
{
return QStringLiteral( "atlaslayouttopdf" );
}

QString QgsLayoutAtlasToPdfAlgorithm::displayName() const
{
return QObject::tr( "Export atlas layout as PDF (single file)" );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortDescription() const
{
return QObject::tr( "Exports an atlas layout as a single PDF file." );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm outputs an atlas layout as a single PDF file.\n\n"
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
"settings off." );
}

void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
{
QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm();
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "PDF file" ), QObject::tr( "PDF Format" ) + " (*.pdf *.PDF)" ) );
}

QgsLayoutAtlasToPdfAlgorithm *QgsLayoutAtlasToPdfAlgorithm::createInstance() const
{
return new QgsLayoutAtlasToPdfAlgorithm();
}

QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( parameters, context, feedback );

QgsLayoutAtlas *atlas = QgsLayoutAtlasToPdfAlgorithmBase::atlas();
QgsLayoutExporter exporter = QgsLayoutAtlasToPdfAlgorithmBase::exporter();
QgsLayoutExporter::PdfExportSettings settings = QgsLayoutAtlasToPdfAlgorithmBase::settings();
QString error = QgsLayoutAtlasToPdfAlgorithmBase::error();

const QString dest = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT" ), context );
if ( atlas->updateFeatures() )
{
Expand Down Expand Up @@ -248,5 +295,113 @@ QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &p
return outputs;
}

// QgsLayoutAtlasToMultiplePdfAlgorithm

QString QgsLayoutAtlasToMultiplePdfAlgorithm::name() const
{
return QStringLiteral( "atlaslayouttomultiplepdf" );
}

QString QgsLayoutAtlasToMultiplePdfAlgorithm::displayName() const
{
return QObject::tr( "Export atlas layout as PDF (multiple files)" );
}

QString QgsLayoutAtlasToMultiplePdfAlgorithm::shortDescription() const
{
return QObject::tr( "Exports an atlas layout to multiple PDF files." );
}

QString QgsLayoutAtlasToMultiplePdfAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm outputs an atlas layout to multiple PDF files.\n\n"
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
"settings off.\n"
);
}

void QgsLayoutAtlasToMultiplePdfAlgorithm::initAlgorithm( const QVariantMap & )
{
QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm();
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "OUTPUT_FILENAME" ), QObject::tr( "Output filename" ), QString( "'output_'||@atlas_featurenumber" ), QString(), false ) );
addParameter( new QgsProcessingParameterFile( QStringLiteral( "OUTPUT_FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) );
}


QgsLayoutAtlasToMultiplePdfAlgorithm *QgsLayoutAtlasToMultiplePdfAlgorithm::createInstance() const
{
return new QgsLayoutAtlasToMultiplePdfAlgorithm();
}


QVariantMap QgsLayoutAtlasToMultiplePdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( parameters, context, feedback );

QgsLayoutAtlas *atlas = QgsLayoutAtlasToPdfAlgorithmBase::atlas();
QgsLayoutExporter exporter = QgsLayoutAtlasToPdfAlgorithmBase::exporter();
QgsLayoutExporter::PdfExportSettings settings = QgsLayoutAtlasToPdfAlgorithmBase::settings();
QString error = QgsLayoutAtlasToPdfAlgorithmBase::error();

const QString filename = parameterAsString( parameters, QStringLiteral( "OUTPUT_FILENAME" ), context );

const QString destFolder = parameterAsFile( parameters, QStringLiteral( "OUTPUT_FOLDER" ), context );

const QString dest = QStringLiteral( "%1/%2.pdf" ).arg( destFolder, filename );

if ( atlas->updateFeatures() )
{
feedback->pushInfo( QObject::tr( "Exporting %n atlas feature(s)", "", atlas->count() ) );

QgsLayoutExporter::ExportResult result;
if ( !atlas->setFilenameExpression( filename, error ) )
{
throw QgsProcessingException( QObject::tr( "Output filename could not be set to create filenames for the files, \n"
"use a correct QGIS expression" ) );
}

result = exporter.exportToPdfs( atlas, dest, settings, error, feedback );

switch ( result )
{
case QgsLayoutExporter::Success:
{
feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( destFolder ) ) );
break;
}

case QgsLayoutExporter::FileError:
throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( dest ) ) );

case QgsLayoutExporter::PrintError:
throw QgsProcessingException( QObject::tr( "Could not create print device." ) );

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::Canceled:
// no meaning for imageexports, will not be encountered
break;
}
}
else
{
feedback->reportError( QObject::tr( "No atlas features found" ) );
}

feedback->setProgress( 100 );

QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT_FOLDER" ), destFolder );
return outputs;
}

///@endcond

67 changes: 62 additions & 5 deletions src/analysis/processing/qgsalgorithmlayoutatlastopdf.h
Expand Up @@ -22,25 +22,58 @@

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"
#include "qgslayoutexporter.h"

class QgsLayoutAtlas;

///@cond PRIVATE

/**
* Native export layout to image algorithm.
* Base class for atlas layout to pdf algorithms
*/
class QgsLayoutAtlasToPdfAlgorithm : public QgsProcessingAlgorithm
class QgsLayoutAtlasToPdfAlgorithmBase : public QgsProcessingAlgorithm
{

public:

QgsLayoutAtlasToPdfAlgorithm() = default;
QgsLayoutAtlasToPdfAlgorithmBase() = 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;

QgsLayoutAtlas *atlas();
QgsLayoutExporter exporter();
QgsLayoutExporter::PdfExportSettings settings();
QString error();



protected:

bool prepareAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
private:
QgsLayoutAtlas *mAtlas;
QgsLayoutExporter mExporter = QgsLayoutExporter( nullptr );
QgsLayoutExporter::PdfExportSettings mSettings;
QString mError;
};


/**
* Export atlas layout to single pdf file algorithm.
*/
class QgsLayoutAtlasToPdfAlgorithm : public QgsLayoutAtlasToPdfAlgorithmBase
{

public:

QgsLayoutAtlasToPdfAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QString shortDescription() const override;
QString shortHelpString() const override;
QgsLayoutAtlasToPdfAlgorithm *createInstance() const override SIP_FACTORY;
Expand All @@ -51,6 +84,30 @@ class QgsLayoutAtlasToPdfAlgorithm : public QgsProcessingAlgorithm
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;


};

/**
* Export atlas layout to multiple pdf files algorithm.
*/
class QgsLayoutAtlasToMultiplePdfAlgorithm : public QgsLayoutAtlasToPdfAlgorithmBase
{

public:

QgsLayoutAtlasToMultiplePdfAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QString shortDescription() const override;
QString shortHelpString() const override;
QgsLayoutAtlasToMultiplePdfAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;


};

///@endcond PRIVATE
Expand Down
1 change: 1 addition & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -365,6 +365,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
#ifndef QT_NO_PRINTER
addAlgorithm( new QgsLayoutAtlasToImageAlgorithm() );
addAlgorithm( new QgsLayoutAtlasToPdfAlgorithm() );
addAlgorithm( new QgsLayoutAtlasToMultiplePdfAlgorithm() );
addAlgorithm( new QgsLayoutToImageAlgorithm() );
addAlgorithm( new QgsLayoutToPdfAlgorithm() );
#endif
Expand Down

0 comments on commit 0ab5f17

Please sign in to comment.