Skip to content

Commit 40a37c5

Browse files
committedAug 3, 2020
[FEATURE][processing] Export print layout atlas as image(s) algorithm
1 parent 88b3f43 commit 40a37c5

File tree

5 files changed

+324
-1
lines changed

5 files changed

+324
-1
lines changed
 

‎src/analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ SET(QGIS_ANALYSIS_SRCS
9191
processing/qgsalgorithmjoinbynearest.cpp
9292
processing/qgsalgorithmjoinwithlines.cpp
9393
processing/qgsalgorithmkmeansclustering.cpp
94+
processing/qgsalgorithmlayoutatlastoimage.cpp
9495
processing/qgsalgorithmlayouttoimage.cpp
9596
processing/qgsalgorithmlayouttopdf.cpp
9697
processing/qgsalgorithmlinedensity.cpp
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/***************************************************************************
2+
qgsalgorithmlayoutatlastoimage.cpp
3+
---------------------
4+
begin : June 2020
5+
copyright : (C) 2020 by Mathieu Pellerin
6+
email : nirvn dot asia 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+
18+
#include "qgsalgorithmlayoutatlastoimage.h"
19+
#include "qgslayout.h"
20+
#include "qgslayoutatlas.h"
21+
#include "qgsprintlayout.h"
22+
#include "qgsprocessingoutputs.h"
23+
#include "qgslayoutexporter.h"
24+
25+
#include <QImageWriter>
26+
27+
///@cond PRIVATE
28+
29+
QString QgsLayoutAtlasToImageAlgorithm::name() const
30+
{
31+
return QStringLiteral( "printlayoutatlastoimage" );
32+
}
33+
34+
QString QgsLayoutAtlasToImageAlgorithm::displayName() const
35+
{
36+
return QObject::tr( "Export print layout atlas as image(s)" );
37+
}
38+
39+
QStringList QgsLayoutAtlasToImageAlgorithm::tags() const
40+
{
41+
return QObject::tr( "layout,atlas,composer,composition,save,png,jpeg,jpg" ).split( ',' );
42+
}
43+
44+
QString QgsLayoutAtlasToImageAlgorithm::group() const
45+
{
46+
return QObject::tr( "Cartography" );
47+
}
48+
49+
QString QgsLayoutAtlasToImageAlgorithm::groupId() const
50+
{
51+
return QStringLiteral( "cartography" );
52+
}
53+
54+
QString QgsLayoutAtlasToImageAlgorithm::shortDescription() const
55+
{
56+
return QObject::tr( "Exports a print layout atlas as one or more images." );
57+
}
58+
59+
QString QgsLayoutAtlasToImageAlgorithm::shortHelpString() const
60+
{
61+
return QObject::tr( "This algorithm outputs a print layout atkas as one or more images file (e.g. PNG or JPEG images)." );
62+
}
63+
64+
void QgsLayoutAtlasToImageAlgorithm::initAlgorithm( const QVariantMap & )
65+
{
66+
addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Print layout" ) ) );
67+
68+
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "COVERAGE_LAYER" ), QObject::tr( "Atlas coverage layer" ) ) );
69+
70+
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILTER_EXPRESSION" ), QObject::tr( "Atlas coverage layer's filter expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
71+
72+
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Atlas coverage layer's feature sort expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
73+
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SORTBY_REVERSE" ), QObject::tr( "Reverse sort order (used when a sort expression is provided)" ), false, true ) );
74+
75+
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILENAME_EXPRESSION" ), QObject::tr( "Output filename expression" ), QStringLiteral( "'output_'||@atlas_featurenumber" ), QStringLiteral( "COVERAGE_LAYER" ) ) );
76+
77+
addParameter( new QgsProcessingParameterFile( QStringLiteral( "FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) );
78+
79+
QStringList imageFormats;
80+
const auto supportedImageFormats { QImageWriter::supportedImageFormats() };
81+
for ( const auto format : QImageWriter::supportedImageFormats() )
82+
{
83+
if ( format == QByteArray( "svg" ) )
84+
continue;
85+
imageFormats << QString( format );
86+
}
87+
std::unique_ptr< QgsProcessingParameterEnum > extensionParam = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "EXTENSION" ), QObject::tr( "Image format" ), imageFormats, false, imageFormats.indexOf( QStringLiteral( "png" ) ) );
88+
extensionParam->setFlags( extensionParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
89+
addParameter( extensionParam.release() );
90+
91+
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 );
92+
dpiParam->setFlags( dpiParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
93+
addParameter( dpiParam.release() );
94+
95+
std::unique_ptr< QgsProcessingParameterBoolean > appendGeorefParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "GEOREFERENCE" ), QObject::tr( "Generate world file" ), true );
96+
appendGeorefParam->setFlags( appendGeorefParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
97+
addParameter( appendGeorefParam.release() );
98+
99+
std::unique_ptr< QgsProcessingParameterBoolean > exportRDFParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "INCLUDE_METADATA" ), QObject::tr( "Export RDF metadata (title, author, etc.)" ), true );
100+
exportRDFParam->setFlags( exportRDFParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
101+
addParameter( exportRDFParam.release() );
102+
103+
std::unique_ptr< QgsProcessingParameterBoolean > antialias = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true );
104+
antialias->setFlags( antialias->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
105+
addParameter( antialias.release() );
106+
}
107+
108+
QgsProcessingAlgorithm::Flags QgsLayoutAtlasToImageAlgorithm::flags() const
109+
{
110+
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
111+
}
112+
113+
QgsLayoutAtlasToImageAlgorithm *QgsLayoutAtlasToImageAlgorithm::createInstance() const
114+
{
115+
return new QgsLayoutAtlasToImageAlgorithm();
116+
}
117+
118+
QVariantMap QgsLayoutAtlasToImageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
119+
{
120+
// this needs to be done in main thread, layouts are not thread safe
121+
QgsPrintLayout *layout = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context );
122+
if ( !layout )
123+
throw QgsProcessingException( QObject::tr( "Cannot find layout with name \"%1\"" ).arg( parameters.value( QStringLiteral( "LAYOUT" ) ).toString() ) );
124+
125+
QString error;
126+
QgsLayoutAtlas *atlas = layout->atlas();
127+
128+
QgsVectorLayer *previousCoverageLayer = atlas->coverageLayer();
129+
const bool previousEnabled = atlas->enabled();
130+
const QString previousFilenameExpression = atlas->filenameExpression();
131+
const QString previousFilterExpression = atlas->filterExpression();
132+
const bool previousSortFeatures = atlas->sortFeatures();
133+
const bool previousSortAscending = atlas->sortAscending();
134+
const QString previousSortExpression = atlas->sortExpression();
135+
136+
auto restoreAtlas = [ atlas,
137+
previousCoverageLayer,
138+
previousEnabled,
139+
previousFilenameExpression,
140+
previousFilterExpression,
141+
previousSortFeatures,
142+
previousSortAscending,
143+
previousSortExpression ]()
144+
{
145+
QString error;
146+
atlas->setEnabled( previousEnabled );
147+
atlas->setCoverageLayer( previousCoverageLayer );
148+
atlas->setFilenameExpression( previousFilenameExpression, error );
149+
atlas->setFilterExpression( previousFilterExpression, error );
150+
atlas->setSortFeatures( previousSortFeatures );
151+
atlas->setSortAscending( previousSortAscending );
152+
atlas->setSortExpression( previousSortExpression );
153+
};
154+
155+
QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "COVERAGE_LAYER" ), context );
156+
atlas->setEnabled( true );
157+
atlas->setCoverageLayer( layer );
158+
159+
QString expression = parameterAsString( parameters, QStringLiteral( "FILENAME_EXPRESSION" ), context );
160+
atlas->setFilenameExpression( expression, error );
161+
if ( !error.isEmpty() )
162+
{
163+
restoreAtlas();
164+
throw QgsProcessingException( QObject::tr( "Error setting atlas filename expression" ) );
165+
}
166+
167+
expression = parameterAsString( parameters, QStringLiteral( "FILTER_EXPRESSION" ), context );
168+
atlas->setFilterExpression( expression, error );
169+
170+
expression = parameterAsString( parameters, QStringLiteral( "SORTBY_EXPRESSION" ), context );
171+
if ( !expression.isEmpty() )
172+
{
173+
const bool sortByReverse = parameterAsBool( parameters, QStringLiteral( "SORTBY_REVERSE" ), context );
174+
atlas->setSortFeatures( true );
175+
atlas->setSortExpression( expression );
176+
atlas->setSortAscending( !sortByReverse );
177+
}
178+
else
179+
{
180+
atlas->setSortFeatures( false );
181+
}
182+
183+
const QString directory = parameterAsFileOutput( parameters, QStringLiteral( "FOLDER" ), context );
184+
QString fileName = QDir( directory ).filePath( QStringLiteral( "atlas" ) );
185+
186+
QStringList imageFormats;
187+
const auto supportedImageFormats { QImageWriter::supportedImageFormats() };
188+
for ( const auto format : QImageWriter::supportedImageFormats() )
189+
{
190+
if ( format == QByteArray( "svg" ) )
191+
continue;
192+
imageFormats << QString( format );
193+
}
194+
int idx = parameterAsEnum( parameters, QStringLiteral( "EXTENSION" ), context );
195+
QString extension = '.' + imageFormats.at( idx );
196+
197+
QgsLayoutExporter exporter( layout );
198+
QgsLayoutExporter::ImageExportSettings settings;
199+
200+
if ( parameters.value( QStringLiteral( "DPI" ) ).isValid() )
201+
{
202+
settings.dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context );
203+
}
204+
205+
settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context );
206+
settings.generateWorldFile = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context );
207+
208+
if ( parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context ) )
209+
settings.flags = settings.flags | QgsLayoutRenderContext::FlagAntialiasing;
210+
else
211+
settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagAntialiasing;
212+
213+
QgsLayoutExporter::ExportResult result = exporter.exportToImage( atlas, fileName, extension, settings, error, feedback );
214+
restoreAtlas();
215+
216+
switch ( result )
217+
{
218+
case QgsLayoutExporter::Success:
219+
{
220+
feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( directory ) ) );
221+
break;
222+
}
223+
224+
case QgsLayoutExporter::FileError:
225+
throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( directory ) ) );
226+
227+
case QgsLayoutExporter::MemoryError:
228+
throw QgsProcessingException( QObject::tr( "Trying to create the image "
229+
"resulted in a memory overflow.\n\n"
230+
"Please try a lower resolution or a smaller paper size." ) );
231+
232+
case QgsLayoutExporter::IteratorError:
233+
throw QgsProcessingException( QObject::tr( "Error encountered while exporting atlas." ) );
234+
235+
case QgsLayoutExporter::SvgLayerError:
236+
case QgsLayoutExporter::PrintError:
237+
case QgsLayoutExporter::Canceled:
238+
// no meaning for imageexports, will not be encountered
239+
break;
240+
}
241+
242+
feedback->setProgress( 100 );
243+
244+
QVariantMap outputs;
245+
outputs.insert( QStringLiteral( "FOLDER" ), directory );
246+
return outputs;
247+
}
248+
249+
///@endcond
250+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/***************************************************************************
2+
qgsalgorithmlayoutatlastoimage.h
3+
---------------------
4+
begin : June 2020
5+
copyright : (C) 2020 by Mathieu Pellerin
6+
email : nirvn dot asia 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+
18+
#ifndef QGSALGORITHMLAYOUTATLASTOIMAGE_H
19+
#define QGSALGORITHMLAYOUTATLASTOIMAGE_H
20+
21+
#define SIP_NO_FILE
22+
23+
#include "qgis_sip.h"
24+
#include "qgsprocessingalgorithm.h"
25+
26+
///@cond PRIVATE
27+
28+
/**
29+
* Native export layout to image algorithm.
30+
*/
31+
class QgsLayoutAtlasToImageAlgorithm : public QgsProcessingAlgorithm
32+
{
33+
34+
public:
35+
36+
QgsLayoutAtlasToImageAlgorithm() = default;
37+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
38+
Flags flags() const override;
39+
QString name() const override;
40+
QString displayName() const override;
41+
QStringList tags() const override;
42+
QString group() const override;
43+
QString groupId() const override;
44+
QString shortDescription() const override;
45+
QString shortHelpString() const override;
46+
QgsLayoutAtlasToImageAlgorithm *createInstance() const override SIP_FACTORY;
47+
48+
protected:
49+
50+
QVariantMap processAlgorithm( const QVariantMap &parameters,
51+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
52+
53+
54+
};
55+
56+
///@endcond PRIVATE
57+
58+
#endif // QGSALGORITHMLAYOUTATLASTOIMAGE_H
59+
60+

‎src/analysis/processing/qgsnativealgorithms.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
#include "qgsalgorithminterpolatepoint.h"
8888
#include "qgsalgorithmintersection.h"
8989
#include "qgsalgorithmkmeansclustering.h"
90+
#include "qgsalgorithmlayoutatlastoimage.h"
9091
#include "qgsalgorithmlayouttoimage.h"
9192
#include "qgsalgorithmlayouttopdf.h"
9293
#include "qgsalgorithmlinedensity.h"
@@ -313,6 +314,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
313314
addAlgorithm( new QgsKMeansClusteringAlgorithm() );
314315
addAlgorithm( new QgsLayerToBookmarksAlgorithm() );
315316
addAlgorithm( new QgsLayoutMapExtentToLayerAlgorithm() );
317+
addAlgorithm( new QgsLayoutAtlasToImageAlgorithm() );
316318
addAlgorithm( new QgsLayoutToImageAlgorithm() );
317319
addAlgorithm( new QgsLayoutToPdfAlgorithm() );
318320
addAlgorithm( new QgsLineDensityAlgorithm() );

‎src/ui/qgstextformatwidgetbase.ui

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5913,6 +5913,16 @@ font-style: italic;</string>
59135913
<string>Obstacles</string>
59145914
</property>
59155915
<layout class="QVBoxLayout" name="verticalLayout_17">
5916+
<item>
5917+
<widget class="QLabel" name="mLblNoObstacle">
5918+
<property name="wordWrap">
5919+
<bool>true</bool>
5920+
</property>
5921+
<property name="text">
5922+
<string>When activated, featuring acting as obstacles discourage labels and diagrams from covering them.</string>
5923+
</property>
5924+
</widget>
5925+
</item>
59165926
<item>
59175927
<layout class="QHBoxLayout" name="horizontalLayout_16" stretch="0,0,0,0">
59185928
<item>
@@ -5928,7 +5938,7 @@ font-style: italic;</string>
59285938
<bool>true</bool>
59295939
</property>
59305940
<property name="text">
5931-
<string>Discourage labels and diagrams from covering features</string>
5941+
<string>Features act as obstacles</string>
59325942
</property>
59335943
</widget>
59345944
</item>

0 commit comments

Comments
 (0)
Please sign in to comment.