QGIS API Documentation 3.43.0-Master (b60ef06885e)
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
9 email : matthias@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
27#include "qgsprovidermetadata.h"
28#include "qgsmaplayerutils.h"
30#include "qgsrasterfilewriter.h"
32#include "gdal.h"
33#include "qgsgdalutils.h"
34#include "qgslayertree.h"
35
36#include <QtConcurrent>
37
39
40QString QgsRasterizeAlgorithm::name() const
41{
42 return QStringLiteral( "rasterize" );
43}
44
45QString QgsRasterizeAlgorithm::displayName() const
46{
47 return QObject::tr( "Convert map to raster" );
48}
49
50QStringList QgsRasterizeAlgorithm::tags() const
51{
52 return QObject::tr( "layer,raster,convert,file,map themes,tiles,render" ).split( ',' );
53}
54
55Qgis::ProcessingAlgorithmFlags QgsRasterizeAlgorithm::flags() const
56{
58}
59
60QString QgsRasterizeAlgorithm::group() const
61{
62 return QObject::tr( "Raster tools" );
63}
64
65QString QgsRasterizeAlgorithm::groupId() const
66{
67 return QStringLiteral( "rastertools" );
68}
69
70void QgsRasterizeAlgorithm::initAlgorithm( const QVariantMap & )
71{
72 addParameter( new QgsProcessingParameterExtent(
73 QStringLiteral( "EXTENT" ),
74 QObject::tr( "Minimum extent to render" )
75 ) );
76 addParameter( new QgsProcessingParameterNumber(
77 QStringLiteral( "EXTENT_BUFFER" ),
78 QObject::tr( "Buffer around tiles in map units" ),
80 0,
81 true,
82 0
83 ) );
84 addParameter( new QgsProcessingParameterNumber(
85 QStringLiteral( "TILE_SIZE" ),
86 QObject::tr( "Tile size" ),
88 1024,
89 false,
90 64
91 ) );
92 addParameter( new QgsProcessingParameterNumber(
93 QStringLiteral( "MAP_UNITS_PER_PIXEL" ),
94 QObject::tr( "Map units per pixel" ),
96 100,
97 true,
98 0
99 ) );
100 addParameter( new QgsProcessingParameterBoolean(
101 QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ),
102 QObject::tr( "Make background transparent" ),
103 false
104 ) );
105
106 addParameter( new QgsProcessingParameterMapTheme(
107 QStringLiteral( "MAP_THEME" ),
108 QObject::tr( "Map theme to render" ),
109 QVariant(), true
110 ) );
111
112 addParameter( new QgsProcessingParameterMultipleLayers(
113 QStringLiteral( "LAYERS" ),
114 QObject::tr( "Layers to render" ),
116 QVariant(),
117 true
118 ) );
120 QStringLiteral( "OUTPUT" ),
121 QObject::tr( "Output layer" )
122 ) );
123}
124
125QString QgsRasterizeAlgorithm::shortDescription() const
126{
127 return QObject::tr( "Renders the map canvas to a raster file." );
128}
129
130QString QgsRasterizeAlgorithm::shortHelpString() const
131{
132 return QObject::tr( "This algorithm rasterizes map canvas content.\n\n"
133 "A map theme can be selected to render a predetermined set of layers with a defined style for each layer. "
134 "Alternatively, a set of layers can be selected if no map theme is set. "
135 "If neither map theme nor layer is set, all the visible layers in the set extent will be rendered.\n\n"
136 "The minimum extent entered will internally be extended to a multiple of the tile size." );
137}
138
139QgsRasterizeAlgorithm *QgsRasterizeAlgorithm::createInstance() const
140{
141 return new QgsRasterizeAlgorithm();
142}
143
144
145QVariantMap QgsRasterizeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
146{
147 // Note: MAP_THEME and LAYERS are handled and cloned in prepareAlgorithm
148 const QgsRectangle extent { parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, mCrs ) };
149 const int tileSize { parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context ) };
150 const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
151 const double mapUnitsPerPixel { parameterAsDouble( parameters, QStringLiteral( "MAP_UNITS_PER_PIXEL" ), context ) };
152 const double extentBuffer { parameterAsDouble( parameters, QStringLiteral( "EXTENT_BUFFER" ), context ) };
153 const QString outputLayerFileName { parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ) };
154
155 int xTileCount { static_cast<int>( ceil( extent.width() / mapUnitsPerPixel / tileSize ) ) };
156 int yTileCount { static_cast<int>( ceil( extent.height() / mapUnitsPerPixel / tileSize ) ) };
157 int width { xTileCount * tileSize };
158 int height { yTileCount * tileSize };
159 int nBands { transparent ? 4 : 3 };
160
161 int64_t totalTiles = 0;
162 for ( auto &layer : std::as_const( mMapLayers ) )
163 {
164 if ( QgsMapLayerUtils::isOpenStreetMapLayer( layer.get() ) )
165 {
166 if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( ( layer.get() ) ) )
167 {
168 const QList<double> resolutions = rasterLayer->dataProvider()->nativeResolutions();
169 if ( resolutions.isEmpty() )
170 {
171 continue;
172 }
173
174 if ( totalTiles == 0 )
175 {
176 const QgsCoordinateTransform ct( mCrs, rasterLayer->crs(), context.transformContext() );
177 QgsRectangle extentLayer;
178 try
179 {
180 extentLayer = ct.transform( extent );
181 }
182 catch ( QgsCsException & )
183 {
184 totalTiles = -1;
185 continue;
186 }
187
188 const double mapUnitsPerPixelLayer = extentLayer.width() / width;
189 int i;
190 for ( i = 0; i < resolutions.size() && resolutions.at( i ) < mapUnitsPerPixelLayer; i++ )
191 {
192 }
193
194 if ( i == resolutions.size() || ( i > 0 && resolutions.at( i ) - mapUnitsPerPixelLayer > mapUnitsPerPixelLayer - resolutions.at( i - 1 ) ) )
195 {
196 i--;
197 }
198
199 const int nbTilesWidth = std::ceil( extentLayer.width() / resolutions.at( i ) / 256 );
200 const int nbTilesHeight = std::ceil( extentLayer.height() / resolutions.at( i ) / 256 );
201 totalTiles = static_cast<int64_t>( nbTilesWidth ) * nbTilesHeight;
202 }
203 feedback->pushInfo( QStringLiteral( "%1" ).arg( totalTiles ) );
204
205 if ( totalTiles > MAXIMUM_OPENSTREETMAP_TILES_FETCH )
206 {
207 // Prevent bulk downloading of tiles from openstreetmap.org as per OSMF tile usage policy
208 feedback->pushFormattedMessage( QObject::tr( "Layer %1 will be skipped as the algorithm leads to bulk downloading behavior which is prohibited by the %2OpenStreetMap Foundation tile usage policy%3" ).arg( rasterLayer->name(), QStringLiteral( "<a href=\"https://operations.osmfoundation.org/policies/tiles/\">" ), QStringLiteral( "</a>" ) ), QObject::tr( "Layer %1 will be skipped as the algorithm leads to bulk downloading behavior which is prohibited by the %2OpenStreetMap Foundation tile usage policy%3" ).arg( rasterLayer->name(), QString(), QString() ) );
209
210 layer->deleteLater();
211 std::vector<std::unique_ptr<QgsMapLayer>>::iterator position = std::find( mMapLayers.begin(), mMapLayers.end(), layer );
212 if ( position != mMapLayers.end() )
213 {
214 mMapLayers.erase( position );
215 }
216 }
217 }
218 }
219 }
220
221 const QString driverName { QgsRasterFileWriter::driverForExtension( QFileInfo( outputLayerFileName ).suffix() ) };
222 if ( driverName.isEmpty() )
223 {
224 throw QgsProcessingException( QObject::tr( "Invalid output raster format" ) );
225 }
226
227 GDALDriverH hOutputFileDriver = GDALGetDriverByName( driverName.toLocal8Bit().constData() );
228 if ( !hOutputFileDriver )
229 {
230 throw QgsProcessingException( QObject::tr( "Error creating GDAL driver" ) );
231 }
232
233 gdal::dataset_unique_ptr hOutputDataset( GDALCreate( hOutputFileDriver, outputLayerFileName.toUtf8().constData(), width, height, nBands, GDALDataType::GDT_Byte, nullptr ) );
234 if ( !hOutputDataset )
235 {
236 throw QgsProcessingException( QObject::tr( "Error creating GDAL output layer" ) );
237 }
238
239 GDALSetProjection( hOutputDataset.get(), mCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLatin1().constData() );
240 double geoTransform[6];
241 geoTransform[0] = extent.xMinimum();
242 geoTransform[1] = mapUnitsPerPixel;
243 geoTransform[2] = 0;
244 geoTransform[3] = extent.yMaximum();
245 geoTransform[4] = 0;
246 geoTransform[5] = -mapUnitsPerPixel;
247 GDALSetGeoTransform( hOutputDataset.get(), geoTransform );
248
249 mMapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
250 mMapSettings.setDestinationCrs( mCrs );
251 mMapSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, true );
252 mMapSettings.setFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms, true );
253 mMapSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, true );
254 mMapSettings.setFlag( Qgis::MapSettingsFlag::UseAdvancedEffects, true );
255 mMapSettings.setTransformContext( context.transformContext() );
256 mMapSettings.setExtentBuffer( extentBuffer );
257
258 // Set layers cloned in prepareAlgorithm
259 QList<QgsMapLayer *> layers;
260 for ( const auto &lptr : mMapLayers )
261 {
262 layers.push_back( lptr.get() );
263 }
264 mMapSettings.setLayers( layers );
265 mMapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
266
267 // Start rendering
268 const double extentRatio { mapUnitsPerPixel * tileSize };
269 const int numTiles { xTileCount * yTileCount };
270
271 // Custom deleter for CPL allocation
272 struct CPLDelete
273 {
274 void operator()( uint8_t *ptr ) const
275 {
276 CPLFree( ptr );
277 }
278 };
279
280 QAtomicInt rendered = 0;
281 QMutex rasterWriteLocker;
282
283 const auto renderJob = [&]( const int x, const int y, QgsMapSettings mapSettings ) {
284 QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
285 mapSettings.setOutputDpi( image.logicalDpiX() );
286 mapSettings.setOutputSize( image.size() );
287 QPainter painter { &image };
288 if ( feedback->isCanceled() )
289 {
290 return;
291 }
292 image.fill( transparent ? mapSettings.backgroundColor().rgba() : mapSettings.backgroundColor().rgb() );
293 mapSettings.setExtent( QgsRectangle(
294 extent.xMinimum() + x * extentRatio,
295 extent.yMaximum() - ( y + 1 ) * extentRatio,
296 extent.xMinimum() + ( x + 1 ) * extentRatio,
297 extent.yMaximum() - y * extentRatio
298 ) );
299 QgsMapRendererCustomPainterJob job( mapSettings, &painter );
300 job.start();
301 job.waitForFinished();
302
303 gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
304 if ( !hIntermediateDataset )
305 {
306 throw QgsProcessingException( QObject::tr( "Error reading tiles from the temporary image" ) );
307 }
308
309 const int xOffset { x * tileSize };
310 const int yOffset { y * tileSize };
311
312 std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast<uint8_t *>( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
313 CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(), GF_Read, 0, 0, tileSize, tileSize, buffer.get(), tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
314 if ( err != CE_None )
315 {
316 throw QgsProcessingException( QObject::tr( "Error reading intermediate raster" ) );
317 }
318
319 {
320 QMutexLocker locker( &rasterWriteLocker );
321 err = GDALDatasetRasterIO( hOutputDataset.get(), GF_Write, xOffset, yOffset, tileSize, tileSize, buffer.get(), tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
322 rendered++;
323 feedback->setProgress( static_cast<double>( rendered ) / numTiles * 100.0 );
324 }
325 if ( err != CE_None )
326 {
327 throw QgsProcessingException( QObject::tr( "Error writing output raster" ) );
328 }
329 };
330
331 feedback->setProgress( 0 );
332
333 std::vector<QFuture<void>> futures;
334
335 for ( int x = 0; x < xTileCount; ++x )
336 {
337 for ( int y = 0; y < yTileCount; ++y )
338 {
339 if ( feedback->isCanceled() )
340 {
341 return {};
342 }
343 futures.push_back( QtConcurrent::run( renderJob, x, y, mMapSettings ) );
344 }
345 }
346
347 for ( auto &f : futures )
348 {
349 f.waitForFinished();
350 }
351
352 return { { QStringLiteral( "OUTPUT" ), outputLayerFileName } };
353}
354
355
356bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
357{
358 Q_UNUSED( feedback )
359 // Retrieve and clone layers
360 const QString mapTheme { parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context ) };
361 const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ) };
362 if ( !mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
363 {
364 const auto constLayers { context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
365 for ( const QgsMapLayer *ml : constLayers )
366 {
367 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
368 }
369 mMapThemeStyleOverrides = context.project()->mapThemeCollection()->mapThemeStyleOverrides( mapTheme );
370 }
371 else if ( !mapLayers.isEmpty() )
372 {
373 for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
374 {
375 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
376 }
377 }
378 // Still no layers? Get them all from the project
379 if ( mMapLayers.size() == 0 )
380 {
381 QList<QgsMapLayer *> layers;
382 QgsLayerTree *root = context.project()->layerTreeRoot();
383 for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
384 {
385 QgsMapLayer *layer = nodeLayer->layer();
386 if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
387 layers << layer;
388 }
389
390 for ( const QgsMapLayer *ml : std::as_const( layers ) )
391 {
392 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
393 }
394 }
395
396 mCrs = context.project()->crs();
397
398 int red = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorRedPart", 255 );
399 int green = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorGreenPart", 255 );
400 int blue = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorBluePart", 255 );
401
402 const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
403 QColor bgColor;
404 if ( transparent )
405 {
406 bgColor = QColor( red, green, blue, 0 );
407 }
408 else
409 {
410 bgColor = QColor( red, green, blue );
411 }
412 mMapSettings.setBackgroundColor( bgColor );
413
414 mMapSettings.setScaleMethod( context.project()->scaleMethod() );
415
416 return mMapLayers.size() > 0;
417}
418
419
@ 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:3476
@ 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...
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
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.
Layer tree node points to a map layer.
Namespace with helper functions for layer tree operations.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
static bool isOpenStreetMapLayer(QgsMapLayer *layer)
Returns true if the layer is served by OpenStreetMap server.
Base class for all map layer types.
Definition qgsmaplayer.h:77
Job implementation that renders everything sequentially using a custom painter.
Contains configuration for rendering maps.
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.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushFormattedMessage(const QString &html, const QString &text)
Pushes a pre-formatted message from the 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
Qgis::ScaleCalculationMethod scaleMethod
Definition qgsproject.h:128
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
Represents a raster layer.
A rectangle specified with double values.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH