Skip to content

Commit 0ab5f17

Browse files
Samwelinyalldawson
authored andcommittedNov 29, 2021
[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
1 parent eb871be commit 0ab5f17

File tree

3 files changed

+255
-42
lines changed

3 files changed

+255
-42
lines changed
 

‎src/analysis/processing/qgsalgorithmlayoutatlastopdf.cpp

Lines changed: 192 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,29 @@
2626

2727
///@cond PRIVATE
2828

29-
QString QgsLayoutAtlasToPdfAlgorithm::name() const
30-
{
31-
return QStringLiteral( "atlaslayouttopdf" );
32-
}
33-
34-
QString QgsLayoutAtlasToPdfAlgorithm::displayName() const
35-
{
36-
return QObject::tr( "Export atlas layout as PDF" );
37-
}
29+
// QgsLayoutAtlasToPdfAlgorithmBase
3830

39-
QStringList QgsLayoutAtlasToPdfAlgorithm::tags() const
31+
QStringList QgsLayoutAtlasToPdfAlgorithmBase::tags() const
4032
{
4133
return QObject::tr( "layout,atlas,composer,composition,save" ).split( ',' );
4234
}
4335

44-
QString QgsLayoutAtlasToPdfAlgorithm::group() const
36+
QString QgsLayoutAtlasToPdfAlgorithmBase::group() const
4537
{
4638
return QObject::tr( "Cartography" );
4739
}
4840

49-
QString QgsLayoutAtlasToPdfAlgorithm::groupId() const
41+
QString QgsLayoutAtlasToPdfAlgorithmBase::groupId() const
5042
{
5143
return QStringLiteral( "cartography" );
5244
}
5345

54-
QString QgsLayoutAtlasToPdfAlgorithm::shortDescription() const
55-
{
56-
return QObject::tr( "Exports an atlas layout as a PDF." );
57-
}
58-
59-
QString QgsLayoutAtlasToPdfAlgorithm::shortHelpString() const
46+
QgsProcessingAlgorithm::Flags QgsLayoutAtlasToPdfAlgorithmBase::flags() const
6047
{
61-
return QObject::tr( "This algorithm outputs an atlas layout as a PDF file.\n\n"
62-
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
63-
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
64-
"settings off." );
48+
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
6549
}
6650

67-
void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
51+
void QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm( const QVariantMap & )
6852
{
6953
addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Atlas layout" ) ) );
7054

@@ -73,8 +57,6 @@ void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
7357
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Sort expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
7458
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SORTBY_REVERSE" ), QObject::tr( "Reverse sort order (used when a sort expression is provided)" ), false, true ) );
7559

76-
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "PDF file" ), QObject::tr( "PDF Format" ) + " (*.pdf *.PDF)" ) );
77-
7860
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 );
7961
layersParam->setFlags( layersParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
8062
addParameter( layersParam.release() );
@@ -114,17 +96,7 @@ void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
11496
addParameter( textFormat.release() );
11597
}
11698

117-
QgsProcessingAlgorithm::Flags QgsLayoutAtlasToPdfAlgorithm::flags() const
118-
{
119-
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
120-
}
121-
122-
QgsLayoutAtlasToPdfAlgorithm *QgsLayoutAtlasToPdfAlgorithm::createInstance() const
123-
{
124-
return new QgsLayoutAtlasToPdfAlgorithm();
125-
}
126-
127-
QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
99+
bool QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
128100
{
129101
// this needs to be done in main thread, layouts are not thread safe
130102
QgsPrintLayout *l = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context );
@@ -179,7 +151,7 @@ QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &p
179151
settings.appendGeoreference = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context );
180152
settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context );
181153
settings.simplifyGeometries = parameterAsBool( parameters, QStringLiteral( "SIMPLIFY" ), context );
182-
settings.textRenderFormat = parameterAsEnum( parameters, QStringLiteral( "TEXT_FORMAT" ), context ) == 0 ? Qgis::TextRenderFormat::AlwaysOutlines : Qgis::TextRenderFormat::AlwaysText;
154+
settings.textRenderFormat = parameterAsEnum( parameters, QStringLiteral( "TEXT_FORMAT" ), context ) == 0 ? QgsRenderContext::TextFormatAlwaysOutlines : QgsRenderContext::TextFormatAlwaysText;
183155

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

179+
mAtlas = atlas;
180+
mExporter = exporter;
181+
mSettings = settings;
182+
mError = error;
183+
184+
return true;
185+
}
186+
187+
QgsLayoutAtlas *QgsLayoutAtlasToPdfAlgorithmBase::atlas()
188+
{
189+
return mAtlas;
190+
}
191+
192+
QgsLayoutExporter QgsLayoutAtlasToPdfAlgorithmBase::exporter()
193+
{
194+
return mExporter;
195+
}
196+
197+
QgsLayoutExporter::PdfExportSettings QgsLayoutAtlasToPdfAlgorithmBase::settings()
198+
{
199+
return mSettings;
200+
}
201+
202+
QString QgsLayoutAtlasToPdfAlgorithmBase::error()
203+
{
204+
return mError;
205+
}
206+
207+
208+
209+
// QgsLayoutAtlasToPdfAlgorithm
210+
211+
QString QgsLayoutAtlasToPdfAlgorithm::name() const
212+
{
213+
return QStringLiteral( "atlaslayouttopdf" );
214+
}
215+
216+
QString QgsLayoutAtlasToPdfAlgorithm::displayName() const
217+
{
218+
return QObject::tr( "Export atlas layout as PDF (single file)" );
219+
}
220+
221+
QString QgsLayoutAtlasToPdfAlgorithm::shortDescription() const
222+
{
223+
return QObject::tr( "Exports an atlas layout as a single PDF file." );
224+
}
225+
226+
QString QgsLayoutAtlasToPdfAlgorithm::shortHelpString() const
227+
{
228+
return QObject::tr( "This algorithm outputs an atlas layout as a single PDF file.\n\n"
229+
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
230+
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
231+
"settings off." );
232+
}
233+
234+
void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
235+
{
236+
QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm();
237+
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "PDF file" ), QObject::tr( "PDF Format" ) + " (*.pdf *.PDF)" ) );
238+
}
239+
240+
QgsLayoutAtlasToPdfAlgorithm *QgsLayoutAtlasToPdfAlgorithm::createInstance() const
241+
{
242+
return new QgsLayoutAtlasToPdfAlgorithm();
243+
}
244+
245+
QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
246+
{
247+
QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( parameters, context, feedback );
248+
249+
QgsLayoutAtlas *atlas = QgsLayoutAtlasToPdfAlgorithmBase::atlas();
250+
QgsLayoutExporter exporter = QgsLayoutAtlasToPdfAlgorithmBase::exporter();
251+
QgsLayoutExporter::PdfExportSettings settings = QgsLayoutAtlasToPdfAlgorithmBase::settings();
252+
QString error = QgsLayoutAtlasToPdfAlgorithmBase::error();
253+
207254
const QString dest = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT" ), context );
208255
if ( atlas->updateFeatures() )
209256
{
@@ -248,5 +295,113 @@ QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &p
248295
return outputs;
249296
}
250297

298+
// QgsLayoutAtlasToMultiplePdfAlgorithm
299+
300+
QString QgsLayoutAtlasToMultiplePdfAlgorithm::name() const
301+
{
302+
return QStringLiteral( "atlaslayouttomultiplepdf" );
303+
}
304+
305+
QString QgsLayoutAtlasToMultiplePdfAlgorithm::displayName() const
306+
{
307+
return QObject::tr( "Export atlas layout as PDF (multiple files)" );
308+
}
309+
310+
QString QgsLayoutAtlasToMultiplePdfAlgorithm::shortDescription() const
311+
{
312+
return QObject::tr( "Exports an atlas layout to multiple PDF files." );
313+
}
314+
315+
QString QgsLayoutAtlasToMultiplePdfAlgorithm::shortHelpString() const
316+
{
317+
return QObject::tr( "This algorithm outputs an atlas layout to multiple PDF files.\n\n"
318+
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
319+
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
320+
"settings off.\n"
321+
);
322+
}
323+
324+
void QgsLayoutAtlasToMultiplePdfAlgorithm::initAlgorithm( const QVariantMap & )
325+
{
326+
QgsLayoutAtlasToPdfAlgorithmBase::initAlgorithm();
327+
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "OUTPUT_FILENAME" ), QObject::tr( "Output filename" ), QString( "'output_'||@atlas_featurenumber" ), QString(), false ) );
328+
addParameter( new QgsProcessingParameterFile( QStringLiteral( "OUTPUT_FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) );
329+
}
330+
331+
332+
QgsLayoutAtlasToMultiplePdfAlgorithm *QgsLayoutAtlasToMultiplePdfAlgorithm::createInstance() const
333+
{
334+
return new QgsLayoutAtlasToMultiplePdfAlgorithm();
335+
}
336+
337+
338+
QVariantMap QgsLayoutAtlasToMultiplePdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
339+
{
340+
QgsLayoutAtlasToPdfAlgorithmBase::prepareAlgorithm( parameters, context, feedback );
341+
342+
QgsLayoutAtlas *atlas = QgsLayoutAtlasToPdfAlgorithmBase::atlas();
343+
QgsLayoutExporter exporter = QgsLayoutAtlasToPdfAlgorithmBase::exporter();
344+
QgsLayoutExporter::PdfExportSettings settings = QgsLayoutAtlasToPdfAlgorithmBase::settings();
345+
QString error = QgsLayoutAtlasToPdfAlgorithmBase::error();
346+
347+
const QString filename = parameterAsString( parameters, QStringLiteral( "OUTPUT_FILENAME" ), context );
348+
349+
const QString destFolder = parameterAsFile( parameters, QStringLiteral( "OUTPUT_FOLDER" ), context );
350+
351+
const QString dest = QStringLiteral( "%1/%2.pdf" ).arg( destFolder, filename );
352+
353+
if ( atlas->updateFeatures() )
354+
{
355+
feedback->pushInfo( QObject::tr( "Exporting %n atlas feature(s)", "", atlas->count() ) );
356+
357+
QgsLayoutExporter::ExportResult result;
358+
if ( !atlas->setFilenameExpression( filename, error ) )
359+
{
360+
throw QgsProcessingException( QObject::tr( "Output filename could not be set to create filenames for the files, \n"
361+
"use a correct QGIS expression" ) );
362+
}
363+
364+
result = exporter.exportToPdfs( atlas, dest, settings, error, feedback );
365+
366+
switch ( result )
367+
{
368+
case QgsLayoutExporter::Success:
369+
{
370+
feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( destFolder ) ) );
371+
break;
372+
}
373+
374+
case QgsLayoutExporter::FileError:
375+
throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( dest ) ) );
376+
377+
case QgsLayoutExporter::PrintError:
378+
throw QgsProcessingException( QObject::tr( "Could not create print device." ) );
379+
380+
case QgsLayoutExporter::MemoryError:
381+
throw QgsProcessingException( QObject::tr( "Trying to create the image "
382+
"resulted in a memory overflow.\n\n"
383+
"Please try a lower resolution or a smaller paper size." ) );
384+
385+
case QgsLayoutExporter::IteratorError:
386+
throw QgsProcessingException( QObject::tr( "Error encountered while exporting atlas." ) );
387+
388+
case QgsLayoutExporter::SvgLayerError:
389+
case QgsLayoutExporter::Canceled:
390+
// no meaning for imageexports, will not be encountered
391+
break;
392+
}
393+
}
394+
else
395+
{
396+
feedback->reportError( QObject::tr( "No atlas features found" ) );
397+
}
398+
399+
feedback->setProgress( 100 );
400+
401+
QVariantMap outputs;
402+
outputs.insert( QStringLiteral( "OUTPUT_FOLDER" ), destFolder );
403+
return outputs;
404+
}
405+
251406
///@endcond
252407

‎src/analysis/processing/qgsalgorithmlayoutatlastopdf.h

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,58 @@
2222

2323
#include "qgis_sip.h"
2424
#include "qgsprocessingalgorithm.h"
25+
#include "qgslayoutexporter.h"
26+
27+
class QgsLayoutAtlas;
2528

2629
///@cond PRIVATE
2730

2831
/**
29-
* Native export layout to image algorithm.
32+
* Base class for atlas layout to pdf algorithms
3033
*/
31-
class QgsLayoutAtlasToPdfAlgorithm : public QgsProcessingAlgorithm
34+
class QgsLayoutAtlasToPdfAlgorithmBase : public QgsProcessingAlgorithm
3235
{
3336

3437
public:
3538

36-
QgsLayoutAtlasToPdfAlgorithm() = default;
39+
QgsLayoutAtlasToPdfAlgorithmBase() = default;
3740
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
3841
Flags flags() const override;
39-
QString name() const override;
40-
QString displayName() const override;
4142
QStringList tags() const override;
4243
QString group() const override;
4344
QString groupId() const override;
45+
46+
QgsLayoutAtlas *atlas();
47+
QgsLayoutExporter exporter();
48+
QgsLayoutExporter::PdfExportSettings settings();
49+
QString error();
50+
51+
52+
53+
protected:
54+
55+
bool prepareAlgorithm( const QVariantMap &parameters,
56+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
57+
private:
58+
QgsLayoutAtlas *mAtlas;
59+
QgsLayoutExporter mExporter = QgsLayoutExporter( nullptr );
60+
QgsLayoutExporter::PdfExportSettings mSettings;
61+
QString mError;
62+
};
63+
64+
65+
/**
66+
* Export atlas layout to single pdf file algorithm.
67+
*/
68+
class QgsLayoutAtlasToPdfAlgorithm : public QgsLayoutAtlasToPdfAlgorithmBase
69+
{
70+
71+
public:
72+
73+
QgsLayoutAtlasToPdfAlgorithm() = default;
74+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
75+
QString name() const override;
76+
QString displayName() const override;
4477
QString shortDescription() const override;
4578
QString shortHelpString() const override;
4679
QgsLayoutAtlasToPdfAlgorithm *createInstance() const override SIP_FACTORY;
@@ -51,6 +84,30 @@ class QgsLayoutAtlasToPdfAlgorithm : public QgsProcessingAlgorithm
5184
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
5285

5386

87+
};
88+
89+
/**
90+
* Export atlas layout to multiple pdf files algorithm.
91+
*/
92+
class QgsLayoutAtlasToMultiplePdfAlgorithm : public QgsLayoutAtlasToPdfAlgorithmBase
93+
{
94+
95+
public:
96+
97+
QgsLayoutAtlasToMultiplePdfAlgorithm() = default;
98+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
99+
QString name() const override;
100+
QString displayName() const override;
101+
QString shortDescription() const override;
102+
QString shortHelpString() const override;
103+
QgsLayoutAtlasToMultiplePdfAlgorithm *createInstance() const override SIP_FACTORY;
104+
105+
protected:
106+
107+
QVariantMap processAlgorithm( const QVariantMap &parameters,
108+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
109+
110+
54111
};
55112

56113
///@endcond PRIVATE

‎src/analysis/processing/qgsnativealgorithms.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
365365
#ifndef QT_NO_PRINTER
366366
addAlgorithm( new QgsLayoutAtlasToImageAlgorithm() );
367367
addAlgorithm( new QgsLayoutAtlasToPdfAlgorithm() );
368+
addAlgorithm( new QgsLayoutAtlasToMultiplePdfAlgorithm() );
368369
addAlgorithm( new QgsLayoutToImageAlgorithm() );
369370
addAlgorithm( new QgsLayoutToPdfAlgorithm() );
370371
#endif

0 commit comments

Comments
 (0)
Please sign in to comment.