QGIS API Documentation 4.1.0-Master (376402f9aeb)
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.setRendererUsage( Qgis::RendererUsage::Export );
236 mMapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
237 mMapSettings.setDestinationCrs( mCrs );
238 mMapSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, true );
239 mMapSettings.setFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms, true );
240 mMapSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, true );
241 mMapSettings.setRasterizedRenderingPolicy( Qgis::RasterizedRenderingPolicy::Default );
242 mMapSettings.setTransformContext( context.transformContext() );
243 mMapSettings.setExtentBuffer( extentBuffer );
244
245 // Set layers cloned in prepareAlgorithm
246 QList<QgsMapLayer *> layers;
247 for ( const auto &lptr : mMapLayers )
248 {
249 layers.push_back( lptr.get() );
250 }
251 mMapSettings.setLayers( layers );
252 mMapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
253
254 // Start rendering
255 const double extentRatio { mapUnitsPerPixel * tileSize };
256 const int numTiles { xTileCount * yTileCount };
257
258 // Custom deleter for CPL allocation
259 struct CPLDelete
260 {
261 void operator()( uint8_t *ptr ) const { CPLFree( ptr ); }
262 };
263
264 QAtomicInt rendered = 0;
265 QMutex rasterWriteLocker;
266
267 const auto renderJob = [&]( const int x, const int y, QgsMapSettings mapSettings ) {
268 QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
269 mapSettings.setOutputDpi( image.logicalDpiX() );
270 mapSettings.setOutputSize( image.size() );
271 QPainter painter { &image };
272 if ( feedback->isCanceled() )
273 {
274 return;
275 }
276 image.fill( transparent ? mapSettings.backgroundColor().rgba() : mapSettings.backgroundColor().rgb() );
277 mapSettings.setExtent(
278 QgsRectangle( extent.xMinimum() + x * extentRatio, extent.yMaximum() - ( y + 1 ) * extentRatio, extent.xMinimum() + ( x + 1 ) * extentRatio, extent.yMaximum() - y * extentRatio )
279 );
280 QgsMapRendererCustomPainterJob job( mapSettings, &painter );
281 job.start();
282 job.waitForFinished();
283
284 gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
285 if ( !hIntermediateDataset )
286 {
287 throw QgsProcessingException( QObject::tr( "Error reading tiles from the temporary image" ) );
288 }
289
290 const int xOffset { x * tileSize };
291 const int yOffset { y * tileSize };
292
293 std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast<uint8_t *>( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
294 CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(), GF_Read, 0, 0, tileSize, tileSize, buffer.get(), tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
295 if ( err != CE_None )
296 {
297 throw QgsProcessingException( QObject::tr( "Error reading intermediate raster" ) );
298 }
299
300 {
301 QMutexLocker locker( &rasterWriteLocker );
302 err = GDALDatasetRasterIO( hOutputDataset.get(), GF_Write, xOffset, yOffset, tileSize, tileSize, buffer.get(), tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
303 rendered++;
304 feedback->setProgress( static_cast<double>( rendered ) / numTiles * maxProgressDuringBlockWriting );
305 }
306 if ( err != CE_None )
307 {
308 throw QgsProcessingException( QObject::tr( "Error writing output raster" ) );
309 }
310 };
311
312 feedback->setProgress( 0 );
313
314 std::vector<QFuture<void>> futures;
315
316 for ( int x = 0; x < xTileCount; ++x )
317 {
318 for ( int y = 0; y < yTileCount; ++y )
319 {
320 if ( feedback->isCanceled() )
321 {
322 return {};
323 }
324 futures.push_back( QtConcurrent::run( renderJob, x, y, mMapSettings ) );
325 }
326 }
327
328 for ( auto &f : futures )
329 {
330 f.waitForFinished();
331 }
332
333#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
334 if ( hasReportsDuringClose )
335 {
336 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
337 if ( GDALDatasetRunCloseWithoutDestroyingEx( hOutputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress ) != CE_None )
338 {
339 if ( feedback->isCanceled() )
340 return {};
341 throw QgsProcessingException( QObject::tr( "Error writing output raster" ) );
342 }
343 }
344#endif
345
346 return { { u"OUTPUT"_s, outputLayerFileName } };
347}
348
349
350bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
351{
352 Q_UNUSED( feedback )
353 // Retrieve and clone layers
354 QgsProject *project = context.project();
355 const QString mapTheme { parameterAsString( parameters, u"MAP_THEME"_s, context ) };
356 const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, u"LAYERS"_s, context ) };
357 if ( !mapTheme.isEmpty() && project && project->mapThemeCollection()->hasMapTheme( mapTheme ) )
358 {
359 const auto constLayers { project->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
360 for ( const QgsMapLayer *ml : constLayers )
361 {
362 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
363 }
364 mMapThemeStyleOverrides = project->mapThemeCollection()->mapThemeStyleOverrides( mapTheme );
365 }
366 else if ( !mapLayers.isEmpty() )
367 {
368 for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
369 {
370 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
371 }
372 }
373 // Still no layers? Get them all from the project
374 if ( mMapLayers.empty() && project )
375 {
376 QList<QgsMapLayer *> layers;
377 QgsLayerTree *root = project->layerTreeRoot();
378 for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
379 {
380 QgsMapLayer *layer = nodeLayer->layer();
381 if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
382 layers << layer;
383 }
384
385 for ( const QgsMapLayer *ml : std::as_const( layers ) )
386 {
387 mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone() ) );
388 }
389 }
390
391 mCrs = project ? project->crs() : QgsCoordinateReferenceSystem();
392
393 int red = project ? project->readNumEntry( u"Gui"_s, "/CanvasColorRedPart", 255 ) : 255;
394 int green = project ? project->readNumEntry( u"Gui"_s, "/CanvasColorGreenPart", 255 ) : 255;
395 int blue = project ? project->readNumEntry( u"Gui"_s, "/CanvasColorBluePart", 255 ) : 255;
396
397 const bool transparent { parameterAsBool( parameters, u"MAKE_BACKGROUND_TRANSPARENT"_s, context ) };
398 QColor bgColor;
399 if ( transparent )
400 {
401 bgColor = QColor( red, green, blue, 0 );
402 }
403 else
404 {
405 bgColor = QColor( red, green, blue );
406 }
407 mMapSettings.setBackgroundColor( bgColor );
408
409 if ( project )
410 {
411 mMapSettings.setScaleMethod( project->scaleMethod() );
412 }
413
414 return mMapLayers.size() > 0;
415}
416
417
@ MapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer).
Definition qgis.h:3713
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2866
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3791
@ Export
Renderer used for printing or exporting to a file.
Definition qgis.h:3628
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition qgis.h:2583
@ RequiresProject
The algorithm requires that a valid QgsProject is available from the processing context in order to e...
Definition qgis.h:3778
@ Double
Double/float values.
Definition qgis.h:3988
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
Definition qgis.h:2887
@ Antialiasing
Enable anti-aliasing for map rendering.
Definition qgis.h:2879
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2894
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.
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.
double xMinimum
double yMaximum
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH