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