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