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