QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 : [email protected]
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 
25 #include "qgsalgorithmrasterize.h"
27 #include "qgsmapthemecollection.h"
28 #include "qgsrasterfilewriter.h"
30 #include "gdal.h"
31 #include "qgsgdalutils.h"
32 #include "qgslayertree.h"
33 
34 #include <QtConcurrent>
35 
37 
38 QString QgsRasterizeAlgorithm::name() const
39 {
40  return QStringLiteral( "rasterize" );
41 }
42 
43 QString QgsRasterizeAlgorithm::displayName() const
44 {
45  return QObject::tr( "Convert map to raster" );
46 }
47 
48 QStringList QgsRasterizeAlgorithm::tags() const
49 {
50  return QObject::tr( "layer,raster,convert,file,map themes,tiles,render" ).split( ',' );
51 }
52 
53 QgsProcessingAlgorithm::Flags QgsRasterizeAlgorithm::flags() const
54 {
55  return QgsProcessingAlgorithm::flags() | FlagRequiresProject;
56 }
57 
58 QString QgsRasterizeAlgorithm::group() const
59 {
60  return QObject::tr( "Raster tools" );
61 }
62 
63 QString QgsRasterizeAlgorithm::groupId() const
64 {
65  return QStringLiteral( "rastertools" );
66 }
67 
68 void QgsRasterizeAlgorithm::initAlgorithm( const QVariantMap & )
69 {
70  addParameter( new QgsProcessingParameterExtent(
71  QStringLiteral( "EXTENT" ),
72  QObject::tr( "Minimum extent to render" ) ) );
73  addParameter( new QgsProcessingParameterNumber(
74  QStringLiteral( "EXTENT_BUFFER" ),
75  QObject::tr( "Buffer around tiles in map units" ),
76  QgsProcessingParameterNumber::Type::Double,
77  0,
78  true,
79  0 ) );
80  addParameter( new QgsProcessingParameterNumber(
81  QStringLiteral( "TILE_SIZE" ),
82  QObject::tr( "Tile size" ),
83  QgsProcessingParameterNumber::Type::Integer,
84  1024,
85  false,
86  64 ) );
87  addParameter( new QgsProcessingParameterNumber(
88  QStringLiteral( "MAP_UNITS_PER_PIXEL" ),
89  QObject::tr( "Map units per pixel" ),
90  QgsProcessingParameterNumber::Type::Double,
91  100,
92  true,
93  0 ) );
94  addParameter( new QgsProcessingParameterBoolean(
95  QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ),
96  QObject::tr( "Make background transparent" ),
97  false ) );
98 
99  addParameter( new QgsProcessingParameterMapTheme(
100  QStringLiteral( "MAP_THEME" ),
101  QObject::tr( "Map theme to render" ),
102  QVariant(), true ) );
103 
104  QList<QgsMapLayer *> projectLayers { QgsProject::instance()->mapLayers().values() };
105  addParameter( new QgsProcessingParameterMultipleLayers(
106  QStringLiteral( "LAYERS" ),
107  QObject::tr( "Layers to render" ),
109  QVariant(),
110  true
111  ) );
112  addParameter( new QgsProcessingParameterRasterDestination(
113  QStringLiteral( "OUTPUT" ),
114  QObject::tr( "Output layer" ) ) );
115 
116 }
117 
118 QString QgsRasterizeAlgorithm::shortDescription() const
119 {
120  return QObject::tr( "Renders the map canvas to a raster file." );
121 }
122 
123 QString QgsRasterizeAlgorithm::shortHelpString() const
124 {
125  return QObject::tr( "This algorithm rasterizes map canvas content.\n\n"
126  "A map theme can be selected to render a predetermined set of layers with a defined style for each layer. "
127  "Alternatively, a set of layers can be selected if no map theme is set. "
128  "If neither map theme nor layer is set, all the visible layers in the set extent will be rendered.\n\n"
129  "The minimum extent entered will internally be extended to a multiple of the tile size." );
130 }
131 
132 QgsRasterizeAlgorithm *QgsRasterizeAlgorithm::createInstance() const
133 {
134  return new QgsRasterizeAlgorithm();
135 }
136 
137 
138 QVariantMap QgsRasterizeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
139 {
140  // Note: MAP_THEME and LAYERS are handled and cloned in prepareAlgorithm
141  const QgsRectangle extent { parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, context.project()->crs() ) };
142  const int tileSize { parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context ) };
143  const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
144  const double mapUnitsPerPixel { parameterAsDouble( parameters, QStringLiteral( "MAP_UNITS_PER_PIXEL" ), context ) };
145  const double extentBuffer { parameterAsDouble( parameters, QStringLiteral( "EXTENT_BUFFER" ), context ) };
146  const QString outputLayerFileName { parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context )};
147 
148  int xTileCount { static_cast<int>( ceil( extent.width() / mapUnitsPerPixel / tileSize ) )};
149  int yTileCount { static_cast<int>( ceil( extent.height() / mapUnitsPerPixel / tileSize ) )};
150  int width { xTileCount * tileSize };
151  int height { yTileCount * tileSize };
152  int nBands { transparent ? 4 : 3 };
153 
154  const QString driverName { QgsRasterFileWriter::driverForExtension( QFileInfo( outputLayerFileName ).suffix() ) };
155  if ( driverName.isEmpty() )
156  {
157  throw QgsProcessingException( QObject::tr( "Invalid output raster format" ) );
158  }
159 
160  GDALDriverH hOutputFileDriver = GDALGetDriverByName( driverName.toLocal8Bit().constData() );
161  if ( !hOutputFileDriver )
162  {
163  throw QgsProcessingException( QObject::tr( "Error creating GDAL driver" ) );
164  }
165 
166  gdal::dataset_unique_ptr hOutputDataset( GDALCreate( hOutputFileDriver, outputLayerFileName.toLocal8Bit().constData(), width, height, nBands, GDALDataType::GDT_Byte, nullptr ) );
167  if ( !hOutputDataset )
168  {
169  throw QgsProcessingException( QObject::tr( "Error creating GDAL output layer" ) );
170  }
171 
172  GDALSetProjection( hOutputDataset.get(), context.project()->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLatin1().constData() );
173  double geoTransform[6];
174  geoTransform[0] = extent.xMinimum();
175  geoTransform[1] = mapUnitsPerPixel;
176  geoTransform[2] = 0;
177  geoTransform[3] = extent.yMaximum();
178  geoTransform[4] = 0;
179  geoTransform[5] = - mapUnitsPerPixel;
180  GDALSetGeoTransform( hOutputDataset.get(), geoTransform );
181 
182  int red = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorRedPart", 255 );
183  int green = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorGreenPart", 255 );
184  int blue = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorBluePart", 255 );
185 
186  QColor bgColor;
187  if ( transparent )
188  {
189  bgColor = QColor( red, green, blue, 0 );
190  }
191  else
192  {
193  bgColor = QColor( red, green, blue );
194  }
195 
196  QgsMapSettings mapSettings;
197  mapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
198  mapSettings.setDestinationCrs( context.project()->crs() );
199  mapSettings.setFlag( QgsMapSettings::Antialiasing, true );
200  mapSettings.setFlag( QgsMapSettings::RenderMapTile, true );
201  mapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, true );
202  mapSettings.setTransformContext( context.transformContext() );
203  mapSettings.setExtentBuffer( extentBuffer );
204  mapSettings.setBackgroundColor( bgColor );
205 
206  // Set layers cloned in prepareAlgorithm
207  QList<QgsMapLayer *> layers;
208  for ( const auto &lptr : mMapLayers )
209  {
210  layers.push_back( lptr.get() );
211  }
212  mapSettings.setLayers( layers );
213  mapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
214 
215  // Start rendering
216  const double extentRatio { mapUnitsPerPixel * tileSize };
217  const int numTiles { xTileCount * yTileCount };
218  const QString fileExtension { QFileInfo( outputLayerFileName ).suffix() };
219 
220  // Custom deleter for CPL allocation
221  struct CPLDelete
222  {
223  void operator()( uint8_t *ptr ) const
224  {
225  CPLFree( ptr );
226  }
227  };
228 
229  QAtomicInt rendered = 0;
230  QMutex rasterWriteLocker;
231 
232  const auto renderJob = [ & ]( const int x, const int y, QgsMapSettings mapSettings )
233  {
234  QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
235  mapSettings.setOutputDpi( image.logicalDpiX() );
236  mapSettings.setOutputSize( image.size() );
237  QPainter painter { &image };
238  if ( feedback->isCanceled() )
239  {
240  return;
241  }
242  image.fill( transparent ? bgColor.rgba() : bgColor.rgb() );
243  mapSettings.setExtent( QgsRectangle(
244  extent.xMinimum() + x * extentRatio,
245  extent.yMaximum() - ( y + 1 ) * extentRatio,
246  extent.xMinimum() + ( x + 1 ) * extentRatio,
247  extent.yMaximum() - y * extentRatio
248  ) );
249  QgsMapRendererCustomPainterJob job( mapSettings, &painter );
250  job.start();
251  job.waitForFinished();
252 
253  gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
254  if ( !hIntermediateDataset )
255  {
256  throw QgsProcessingException( QStringLiteral( "Error reading tiles from the temporary image" ) );
257  }
258 
259  const int xOffset { x * tileSize };
260  const int yOffset { y * tileSize };
261 
262  std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast< uint8_t * >( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
263  CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(),
264  GF_Read, 0, 0, tileSize, tileSize,
265  buffer.get(),
266  tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
267  if ( err != CE_None )
268  {
269  throw QgsProcessingException( QStringLiteral( "Error reading intermediate raster" ) );
270  }
271 
272  {
273  QMutexLocker locker( &rasterWriteLocker );
274  err = GDALDatasetRasterIO( hOutputDataset.get(),
275  GF_Write, xOffset, yOffset, tileSize, tileSize,
276  buffer.get(),
277  tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
278  rendered++;
279  feedback->setProgress( static_cast<double>( rendered ) / numTiles * 100.0 );
280  }
281  if ( err != CE_None )
282  {
283  throw QgsProcessingException( QStringLiteral( "Error writing output raster" ) );
284  }
285  };
286 
287  feedback->setProgress( 0 );
288 
289  std::vector<QFuture<void>> futures;
290 
291  for ( int x = 0; x < xTileCount; ++x )
292  {
293  for ( int y = 0; y < yTileCount; ++y )
294  {
295  if ( feedback->isCanceled() )
296  {
297  return {};
298  }
299  futures.push_back( QtConcurrent::run( renderJob, x, y, mapSettings ) );
300  }
301  }
302 
303  for ( auto &f : futures )
304  {
305  f.waitForFinished();
306  }
307 
308  return { { QStringLiteral( "OUTPUT" ), outputLayerFileName } };
309 }
310 
311 
312 bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
313 {
314  Q_UNUSED( feedback )
315  // Retrieve and clone layers
316  const QString mapTheme { parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context ) };
317  const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ) };
318  if ( ! mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
319  {
320  const auto constLayers { context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
321  for ( const QgsMapLayer *ml : constLayers )
322  {
323  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
324  }
325  mMapThemeStyleOverrides = context.project()->mapThemeCollection( )->mapThemeStyleOverrides( mapTheme );
326  }
327  else if ( ! mapLayers.isEmpty() )
328  {
329  for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
330  {
331  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
332  }
333  }
334  // Still no layers? Get them all from the project
335  if ( mMapLayers.size() == 0 )
336  {
337  QList<QgsMapLayer *> layers;
338  QgsLayerTree *root = context.project()->layerTreeRoot();
339  for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
340  {
341  QgsMapLayer *layer = nodeLayer->layer();
342  if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
343  layers << layer;
344  }
345 
346  for ( const QgsMapLayer *ml : std::as_const( layers ) )
347  {
348  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
349  }
350  }
351  return mMapLayers.size() > 0;
352 }
353 
354 
@ WKT_PREFERRED_GDAL
Preferred format for conversion of CRS to WKT for use with the GDAL library.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
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.
Definition: qgslayertree.h:33
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Base class for all map layer types.
Definition: qgsmaplayer.h:70
Job implementation that renders everything sequentially using a custom painter.
The QgsMapSettings class contains configuration for rendering of the map.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
void setOutputImageFormat(QImage::Format format)
sets format of internal QImage
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
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 Flags 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.
Definition: qgsexception.h:83
Base class for providing feedback from a processing 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 ...
@ TypeMapLayer
Any map layer type (raster or vector or mesh)
Definition: qgsprocessing.h:47
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:107
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:104
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:136