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