Branch data Line data Source code
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 "qgslayoutitemmap.h"
22 : : #include "qgslayoututils.h"
23 : : #include "qgsprintlayout.h"
24 : : #include "qgsprocessingoutputs.h"
25 : : #include "qgslayoutexporter.h"
26 : :
27 : : #include <QImageWriter>
28 : :
29 : : ///@cond PRIVATE
30 : :
31 : 0 : QString QgsLayoutAtlasToImageAlgorithm::name() const
32 : : {
33 : 0 : return QStringLiteral( "atlaslayouttoimage" );
34 : : }
35 : :
36 : 0 : QString QgsLayoutAtlasToImageAlgorithm::displayName() const
37 : : {
38 : 0 : return QObject::tr( "Export atlas layout as image" );
39 : : }
40 : :
41 : 0 : QStringList QgsLayoutAtlasToImageAlgorithm::tags() const
42 : : {
43 : 0 : return QObject::tr( "layout,atlas,composer,composition,save,png,jpeg,jpg" ).split( ',' );
44 : 0 : }
45 : :
46 : 0 : QString QgsLayoutAtlasToImageAlgorithm::group() const
47 : : {
48 : 0 : return QObject::tr( "Cartography" );
49 : : }
50 : :
51 : 0 : QString QgsLayoutAtlasToImageAlgorithm::groupId() const
52 : : {
53 : 0 : return QStringLiteral( "cartography" );
54 : : }
55 : :
56 : 0 : QString QgsLayoutAtlasToImageAlgorithm::shortDescription() const
57 : : {
58 : 0 : return QObject::tr( "Exports an atlas layout as a set of images." );
59 : : }
60 : :
61 : 0 : QString QgsLayoutAtlasToImageAlgorithm::shortHelpString() const
62 : : {
63 : 0 : return QObject::tr( "This algorithm outputs an atlas layout to a set of image files (e.g. PNG or JPEG images).\n\n"
64 : : "If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
65 : : "will be overwritten. In this case, an empty filter or sort by expression will turn those "
66 : : "settings off." );
67 : : }
68 : :
69 : 0 : void QgsLayoutAtlasToImageAlgorithm::initAlgorithm( const QVariantMap & )
70 : : {
71 : 0 : addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Atlas layout" ) ) );
72 : :
73 : 0 : addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "COVERAGE_LAYER" ), QObject::tr( "Coverage layer" ), QList< int >(), QVariant(), true ) );
74 : 0 : addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILTER_EXPRESSION" ), QObject::tr( "Filter expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
75 : 0 : addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Sort expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
76 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SORTBY_REVERSE" ), QObject::tr( "Reverse sort order (used when a sort expression is provided)" ), false, true ) );
77 : :
78 : 0 : addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILENAME_EXPRESSION" ), QObject::tr( "Output filename expression" ), QStringLiteral( "'output_'||@atlas_featurenumber" ), QStringLiteral( "COVERAGE_LAYER" ) ) );
79 : 0 : addParameter( new QgsProcessingParameterFile( QStringLiteral( "FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) );
80 : :
81 : :
82 : 0 : 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 );
83 : 0 : layersParam->setFlags( layersParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
84 : 0 : addParameter( layersParam.release() );
85 : :
86 : 0 : QStringList imageFormats;
87 : 0 : const QList<QByteArray> supportedImageFormats { QImageWriter::supportedImageFormats() };
88 : 0 : for ( const QByteArray &format : supportedImageFormats )
89 : : {
90 : 0 : if ( format == QByteArray( "svg" ) )
91 : 0 : continue;
92 : 0 : imageFormats << QString( format );
93 : : }
94 : 0 : std::unique_ptr< QgsProcessingParameterEnum > extensionParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "EXTENSION" ), QObject::tr( "Image format" ), imageFormats, false, imageFormats.indexOf( QLatin1String( "png" ) ) );
95 : 0 : extensionParam->setFlags( extensionParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
96 : 0 : addParameter( extensionParam.release() );
97 : :
98 : 0 : std::unique_ptr< QgsProcessingParameterNumber > dpiParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DPI" ), QObject::tr( "DPI (leave blank for default layout DPI)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0 );
99 : 0 : dpiParam->setFlags( dpiParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
100 : 0 : addParameter( dpiParam.release() );
101 : :
102 : 0 : std::unique_ptr< QgsProcessingParameterBoolean > appendGeorefParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "GEOREFERENCE" ), QObject::tr( "Generate world file" ), true );
103 : 0 : appendGeorefParam->setFlags( appendGeorefParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
104 : 0 : addParameter( appendGeorefParam.release() );
105 : :
106 : 0 : std::unique_ptr< QgsProcessingParameterBoolean > exportRDFParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "INCLUDE_METADATA" ), QObject::tr( "Export RDF metadata (title, author, etc.)" ), true );
107 : 0 : exportRDFParam->setFlags( exportRDFParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
108 : 0 : addParameter( exportRDFParam.release() );
109 : :
110 : 0 : std::unique_ptr< QgsProcessingParameterBoolean > antialias = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true );
111 : 0 : antialias->setFlags( antialias->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
112 : 0 : addParameter( antialias.release() );
113 : 0 : }
114 : :
115 : 0 : QgsProcessingAlgorithm::Flags QgsLayoutAtlasToImageAlgorithm::flags() const
116 : : {
117 : 0 : return QgsProcessingAlgorithm::flags() | FlagNoThreading;
118 : : }
119 : :
120 : 0 : QgsLayoutAtlasToImageAlgorithm *QgsLayoutAtlasToImageAlgorithm::createInstance() const
121 : : {
122 : 0 : return new QgsLayoutAtlasToImageAlgorithm();
123 : : }
124 : :
125 : 0 : QVariantMap QgsLayoutAtlasToImageAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
126 : : {
127 : : // this needs to be done in main thread, layouts are not thread safe
128 : 0 : QgsPrintLayout *l = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context );
129 : 0 : if ( !l )
130 : 0 : throw QgsProcessingException( QObject::tr( "Cannot find layout with name \"%1\"" ).arg( parameters.value( QStringLiteral( "LAYOUT" ) ).toString() ) );
131 : :
132 : 0 : std::unique_ptr< QgsPrintLayout > layout( l->clone() );
133 : 0 : QgsLayoutAtlas *atlas = layout->atlas();
134 : :
135 : 0 : QString expression, error;
136 : 0 : QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "COVERAGE_LAYER" ), context );
137 : 0 : if ( layer )
138 : : {
139 : 0 : atlas->setEnabled( true );
140 : 0 : atlas->setCoverageLayer( layer );
141 : :
142 : 0 : expression = parameterAsString( parameters, QStringLiteral( "FILTER_EXPRESSION" ), context );
143 : 0 : atlas->setFilterExpression( expression, error );
144 : 0 : if ( !expression.isEmpty() && !error.isEmpty() )
145 : : {
146 : 0 : throw QgsProcessingException( QObject::tr( "Error setting atlas filter expression" ) );
147 : : }
148 : 0 : error.clear();
149 : :
150 : 0 : expression = parameterAsString( parameters, QStringLiteral( "SORTBY_EXPRESSION" ), context );
151 : 0 : if ( !expression.isEmpty() )
152 : : {
153 : 0 : const bool sortByReverse = parameterAsBool( parameters, QStringLiteral( "SORTBY_REVERSE" ), context );
154 : 0 : atlas->setSortFeatures( true );
155 : 0 : atlas->setSortExpression( expression );
156 : 0 : atlas->setSortAscending( !sortByReverse );
157 : 0 : }
158 : : else
159 : : {
160 : 0 : atlas->setSortFeatures( false );
161 : : }
162 : 0 : }
163 : 0 : else if ( !atlas->enabled() )
164 : : {
165 : 0 : throw QgsProcessingException( QObject::tr( "Layout being export doesn't have an enabled atlas" ) );
166 : : }
167 : :
168 : 0 : expression = parameterAsString( parameters, QStringLiteral( "FILENAME_EXPRESSION" ), context );
169 : 0 : atlas->setFilenameExpression( expression, error );
170 : 0 : if ( !error.isEmpty() )
171 : : {
172 : 0 : throw QgsProcessingException( QObject::tr( "Error setting atlas filename expression" ) );
173 : : }
174 : :
175 : 0 : const QString directory = parameterAsFileOutput( parameters, QStringLiteral( "FOLDER" ), context );
176 : 0 : QString fileName = QDir( directory ).filePath( QStringLiteral( "atlas" ) );
177 : :
178 : 0 : QStringList imageFormats;
179 : 0 : const QList<QByteArray> supportedImageFormats { QImageWriter::supportedImageFormats() };
180 : 0 : for ( const QByteArray &format : supportedImageFormats )
181 : : {
182 : 0 : if ( format == QByteArray( "svg" ) )
183 : 0 : continue;
184 : 0 : imageFormats << QString( format );
185 : : }
186 : 0 : int idx = parameterAsEnum( parameters, QStringLiteral( "EXTENSION" ), context );
187 : 0 : QString extension = '.' + imageFormats.at( idx );
188 : :
189 : 0 : QgsLayoutExporter exporter( layout.get() );
190 : 0 : QgsLayoutExporter::ImageExportSettings settings;
191 : :
192 : 0 : if ( parameters.value( QStringLiteral( "DPI" ) ).isValid() )
193 : : {
194 : 0 : settings.dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context );
195 : 0 : }
196 : :
197 : 0 : settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context );
198 : 0 : settings.generateWorldFile = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context );
199 : :
200 : 0 : if ( parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context ) )
201 : 0 : settings.flags = settings.flags | QgsLayoutRenderContext::FlagAntialiasing;
202 : : else
203 : 0 : settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagAntialiasing;
204 : :
205 : 0 : settings.predefinedMapScales = QgsLayoutUtils::predefinedScales( layout.get() );
206 : :
207 : 0 : const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
208 : 0 : if ( layers.size() > 0 )
209 : : {
210 : 0 : const QList<QGraphicsItem *> items = layout->items();
211 : 0 : for ( QGraphicsItem *graphicsItem : items )
212 : : {
213 : 0 : QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
214 : 0 : QgsLayoutItemMap *map = dynamic_cast<QgsLayoutItemMap *>( item );
215 : 0 : if ( map && !map->followVisibilityPreset() && !map->keepLayerSet() )
216 : : {
217 : 0 : map->setKeepLayerSet( true );
218 : 0 : map->setLayers( layers );
219 : 0 : }
220 : : }
221 : 0 : }
222 : :
223 : 0 : if ( atlas->updateFeatures() )
224 : : {
225 : 0 : feedback->pushInfo( QObject::tr( "Exporting %n atlas feature(s)", "", atlas->count() ) );
226 : 0 : switch ( exporter.exportToImage( atlas, fileName, extension, settings, error, feedback ) )
227 : : {
228 : : case QgsLayoutExporter::Success:
229 : : {
230 : 0 : feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( directory ) ) );
231 : 0 : break;
232 : : }
233 : :
234 : : case QgsLayoutExporter::FileError:
235 : 0 : throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( directory ) ) );
236 : :
237 : : case QgsLayoutExporter::MemoryError:
238 : 0 : throw QgsProcessingException( QObject::tr( "Trying to create the image "
239 : : "resulted in a memory overflow.\n\n"
240 : : "Please try a lower resolution or a smaller paper size." ) );
241 : :
242 : : case QgsLayoutExporter::IteratorError:
243 : 0 : throw QgsProcessingException( QObject::tr( "Error encountered while exporting atlas." ) );
244 : :
245 : : case QgsLayoutExporter::SvgLayerError:
246 : : case QgsLayoutExporter::PrintError:
247 : : case QgsLayoutExporter::Canceled:
248 : : // no meaning for imageexports, will not be encountered
249 : 0 : break;
250 : : }
251 : 0 : }
252 : : else
253 : : {
254 : 0 : feedback->reportError( QObject::tr( "No atlas features found" ) );
255 : : }
256 : :
257 : 0 : feedback->setProgress( 100 );
258 : :
259 : 0 : QVariantMap outputs;
260 : 0 : outputs.insert( QStringLiteral( "FOLDER" ), directory );
261 : 0 : return outputs;
262 : 0 : }
263 : :
264 : : ///@endcond
265 : :
|