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