QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsalgorithmrasterize.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrasterize.cpp - QgsRasterizeAlgorithm
3
4 ---------------------
5 Original implementation in Python:
6
7 begin : 2016-10-05
8 copyright : (C) 2016 by OPENGIS.ch
10
11 C++ port:
12
13 begin : 20.11.2019
14 copyright : (C) 2019 by Alessandro Pasotti
15 email : elpaso at itopen dot it
16 ***************************************************************************
17 * *
18 * This program is free software; you can redistribute it and/or modify *
19 * it under the terms of the GNU General Public License as published by *
20 * the Free Software Foundation; either version 2 of the License, or *
21 * (at your option) any later version. *
22 * *
23 ***************************************************************************/
24
28#include "qgsrasterfilewriter.h"
30#include "gdal.h"
31#include "qgsgdalutils.h"
32#include "qgslayertree.h"
33
34#include <QtConcurrent>
35
37
38QString QgsRasterizeAlgorithm::name() const
39{
40 return QStringLiteral( "rasterize" );
41}
42
43QString QgsRasterizeAlgorithm::displayName() const
44{
45 return QObject::tr( "Convert map to raster" );
46}
47
48QStringList QgsRasterizeAlgorithm::tags() const
49{
50 return QObject::tr( "layer,raster,convert,file,map themes,tiles,render" ).split( ',' );
51}
52
53Qgis::ProcessingAlgorithmFlags QgsRasterizeAlgorithm::flags() const
54{
56}
57
58QString QgsRasterizeAlgorithm::group() const
59{
60 return QObject::tr( "Raster tools" );
61}
62
63QString QgsRasterizeAlgorithm::groupId() const
64{
65 return QStringLiteral( "rastertools" );
66}
67
68void QgsRasterizeAlgorithm::initAlgorithm( const QVariantMap & )
69{
70 addParameter( new QgsProcessingParameterExtent(
71 QStringLiteral( "EXTENT" ),
72 QObject::tr( "Minimum extent to render" ) ) );
73 addParameter( new QgsProcessingParameterNumber(
74 QStringLiteral( "EXTENT_BUFFER" ),
75 QObject::tr( "Buffer around tiles in map units" ),
77 0,
78 true,
79 0 ) );
80 addParameter( new QgsProcessingParameterNumber(
81 QStringLiteral( "TILE_SIZE" ),
82 QObject::tr( "Tile size" ),
84 1024,
85 false,
86 64 ) );
87 addParameter( new QgsProcessingParameterNumber(
88 QStringLiteral( "MAP_UNITS_PER_PIXEL" ),
89 QObject::tr( "Map units per pixel" ),
91 100,
92 true,
93 0 ) );
94 addParameter( new QgsProcessingParameterBoolean(
95 QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ),
96 QObject::tr( "Make background transparent" ),
97 false ) );
98
99 addParameter( new QgsProcessingParameterMapTheme(
100 QStringLiteral( "MAP_THEME" ),
101 QObject::tr( "Map theme to render" ),
102 QVariant(), true ) );
103
104 addParameter( new QgsProcessingParameterMultipleLayers(
105 QStringLiteral( "LAYERS" ),
106 QObject::tr( "Layers to render" ),
108 QVariant(),
109 true
110 ) );
112 QStringLiteral( "OUTPUT" ),
113 QObject::tr( "Output layer" ) ) );
114
115}
116
117QString QgsRasterizeAlgorithm::shortDescription() const
118{
119 return QObject::tr( "Renders the map canvas to a raster file." );
120}
121
122QString QgsRasterizeAlgorithm::shortHelpString() const
123{
124 return QObject::tr( "This algorithm rasterizes map canvas content.\n\n"
125 "A map theme can be selected to render a predetermined set of layers with a defined style for each layer. "
126 "Alternatively, a set of layers can be selected if no map theme is set. "
127 "If neither map theme nor layer is set, all the visible layers in the set extent will be rendered.\n\n"
128 "The minimum extent entered will internally be extended to a multiple of the tile size." );
129}
130
131QgsRasterizeAlgorithm *QgsRasterizeAlgorithm::createInstance() const
132{
133 return new QgsRasterizeAlgorithm();
134}
135
136
137QVariantMap QgsRasterizeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
138{
139 // Note: MAP_THEME and LAYERS are handled and cloned in prepareAlgorithm
140 const QgsRectangle extent { parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, context.project()->crs() ) };
141 const int tileSize { parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context ) };
142 const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
143 const double mapUnitsPerPixel { parameterAsDouble( parameters, QStringLiteral( "MAP_UNITS_PER_PIXEL" ), context ) };
144 const double extentBuffer { parameterAsDouble( parameters, QStringLiteral( "EXTENT_BUFFER" ), context ) };
145 const QString outputLayerFileName { parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context )};
146
147 int xTileCount { static_cast<int>( ceil( extent.width() / mapUnitsPerPixel / tileSize ) )};
148 int yTileCount { static_cast<int>( ceil( extent.height() / mapUnitsPerPixel / tileSize ) )};
149 int width { xTileCount * tileSize };
150 int height { yTileCount * tileSize };
151 int nBands { transparent ? 4 : 3 };
152
153 const QString driverName { QgsRasterFileWriter::driverForExtension( QFileInfo( outputLayerFileName ).suffix() ) };
154 if ( driverName.isEmpty() )
155 {
156 throw QgsProcessingException( QObject::tr( "Invalid output raster format" ) );
157 }
158
159 GDALDriverH hOutputFileDriver = GDALGetDriverByName( driverName.toLocal8Bit().constData() );
160 if ( !hOutputFileDriver )
161 {
162 throw QgsProcessingException( QObject::tr( "Error creating GDAL driver" ) );
163 }
164
165 gdal::dataset_unique_ptr hOutputDataset( GDALCreate( hOutputFileDriver, outputLayerFileName.toUtf8().constData(), width, height, nBands, GDALDataType::GDT_Byte, nullptr ) );
166 if ( !hOutputDataset )
167 {
168 throw QgsProcessingException( QObject::tr( "Error creating GDAL output layer" ) );
169 }
170
171 GDALSetProjection( hOutputDataset.get(), context.project()->crs().toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLatin1().constData() );
172 double geoTransform[6];
173 geoTransform[0] = extent.xMinimum();
174 geoTransform[1] = mapUnitsPerPixel;
175 geoTransform[2] = 0;
176 geoTransform[3] = extent.yMaximum();
177 geoTransform[4] = 0;
178 geoTransform[5] = - mapUnitsPerPixel;
179 GDALSetGeoTransform( hOutputDataset.get(), geoTransform );
180
181 int red = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorRedPart", 255 );
182 int green = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorGreenPart", 255 );
183 int blue = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorBluePart", 255 );
184
185 QColor bgColor;
186 if ( transparent )
187 {
188 bgColor = QColor( red, green, blue, 0 );
189 }
190 else
191 {
192 bgColor = QColor( red, green, blue );
193 }
194
195 QgsMapSettings mapSettings;
196 mapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
197 mapSettings.setDestinationCrs( context.project()->crs() );
198 mapSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, true );
202 mapSettings.setTransformContext( context.transformContext() );
203 mapSettings.setExtentBuffer( extentBuffer );
204 mapSettings.setBackgroundColor( bgColor );
205
206 // Set layers cloned in prepareAlgorithm
207 QList<QgsMapLayer *> layers;
208 for ( const auto &lptr : mMapLayers )
209 {
210 layers.push_back( lptr.get() );
211 }
212 mapSettings.setLayers( layers );
213 mapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
214
215 // Start rendering
216 const double extentRatio { mapUnitsPerPixel * tileSize };
217 const int numTiles { xTileCount * yTileCount };
218
219 // Custom deleter for CPL allocation
220 struct CPLDelete
221 {
222 void operator()( uint8_t *ptr ) const
223 {
224 CPLFree( ptr );
225 }
226 };
227
228 QAtomicInt rendered = 0;
229 QMutex rasterWriteLocker;
230
231 const auto renderJob = [ & ]( const int x, const int y, QgsMapSettings mapSettings )
232 {
233 QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
234 mapSettings.setOutputDpi( image.logicalDpiX() );
235 mapSettings.setOutputSize( image.size() );
236 QPainter painter { &image };
237 if ( feedback->isCanceled() )
238 {
239 return;
240 }
241 image.fill( transparent ? bgColor.rgba() : bgColor.rgb() );
242 mapSettings.setExtent( QgsRectangle(
243 extent.xMinimum() + x * extentRatio,
244 extent.yMaximum() - ( y + 1 ) * extentRatio,
245 extent.xMinimum() + ( x + 1 ) * extentRatio,
246 extent.yMaximum() - y * extentRatio
247 ) );
248 QgsMapRendererCustomPainterJob job( mapSettings, &painter );
249 job.start();
250 job.waitForFinished();
251
252 gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
253 if ( !hIntermediateDataset )
254 {
255 throw QgsProcessingException( QStringLiteral( "Error reading tiles from the temporary image" ) );
256 }
257
258 const int xOffset { x * tileSize };
259 const int yOffset { y * tileSize };
260
261 std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast< uint8_t * >( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
262 CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(),
263 GF_Read, 0, 0, tileSize, tileSize,
264 buffer.get(),
265 tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
266 if ( err != CE_None )
267 {
268 throw QgsProcessingException( QStringLiteral( "Error reading intermediate raster" ) );
269 }
270
271 {
272 QMutexLocker locker( &rasterWriteLocker );
273 err = GDALDatasetRasterIO( hOutputDataset.get(),
274 GF_Write, xOffset, yOffset, tileSize, tileSize,
275 buffer.get(),
276 tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
277 rendered++;
278 feedback->setProgress( static_cast<double>( rendered ) / numTiles * 100.0 );
279 }
280 if ( err != CE_None )
281 {
282 throw QgsProcessingException( QStringLiteral( "Error writing output raster" ) );
283 }
284 };
285
286 feedback->setProgress( 0 );
287
288 std::vector<QFuture<void>> futures;
289
290 for ( int x = 0; x < xTileCount; ++x )
291 {
292 for ( int y = 0; y < yTileCount; ++y )
293 {
294 if ( feedback->isCanceled() )
295 {
296 return {};
297 }
298 futures.push_back( QtConcurrent::run( renderJob, x, y, mapSettings ) );
299 }
300 }
301
302 for ( auto &f : futures )
303 {
304 f.waitForFinished();
305 }
306
307 return { { QStringLiteral( "OUTPUT" ), outputLayerFileName } };
308}
309
310
311bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
312{
313 Q_UNUSED( feedback )
314 // Retrieve and clone layers
315 const QString mapTheme { parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context ) };
316 const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ) };
317 if ( ! mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
318 {
319 const auto constLayers { context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
320 for ( const QgsMapLayer *ml : constLayers )
321 {
322 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
323 }
324 mMapThemeStyleOverrides = context.project()->mapThemeCollection( )->mapThemeStyleOverrides( mapTheme );
325 }
326 else if ( ! mapLayers.isEmpty() )
327 {
328 for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
329 {
330 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
331 }
332 }
333 // Still no layers? Get them all from the project
334 if ( mMapLayers.size() == 0 )
335 {
336 QList<QgsMapLayer *> layers;
337 QgsLayerTree *root = context.project()->layerTreeRoot();
338 for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
339 {
340 QgsMapLayer *layer = nodeLayer->layer();
341 if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
342 layers << layer;
343 }
344
345 for ( const QgsMapLayer *ml : std::as_const( layers ) )
346 {
347 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
348 }
349 }
350 return mMapLayers.size() > 0;
351}
352
353
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer)
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition: qgis.h:2934
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
@ RequiresProject
The algorithm requires that a valid QgsProject is available from the processing context in order to e...
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:61
static gdal::dataset_unique_ptr imageToMemoryDataset(const QImage &image)
Converts an image to a GDAL memory dataset by borrowing image data.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Layer tree node points to a map layer.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Base class for all map layer types.
Definition: qgsmaplayer.h:75
Job implementation that renders everything sequentially using a custom painter.
The QgsMapSettings class contains configuration for rendering of the map.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
void setOutputImageFormat(QImage::Format format)
sets format of internal QImage
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
QList< QgsMapLayer * > mapThemeVisibleLayers(const QString &name) const
Returns the list of layers that are visible for the specified map theme.
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme.
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
A boolean parameter for processing algorithms.
A rectangular map extent parameter for processing algorithms.
A map theme parameter for processing algorithms, allowing users to select an existing map theme from ...
A parameter for processing algorithms which accepts multiple map layers.
A numeric parameter for processing algorithms.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:115
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:112
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:157