QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  addParameter( new QgsProcessingParameterMultipleLayers(
105  QStringLiteral( "LAYERS" ),
106  QObject::tr( "Layers to render" ),
108  QVariant(),
109  true
110  ) );
111  addParameter( new QgsProcessingParameterRasterDestination(
112  QStringLiteral( "OUTPUT" ),
113  QObject::tr( "Output layer" ) ) );
114 
115 }
116 
117 QString QgsRasterizeAlgorithm::shortDescription() const
118 {
119  return QObject::tr( "Renders the map canvas to a raster file." );
120 }
121 
122 QString QgsRasterizeAlgorithm::shortHelpString() const
123 {
124  return QObject::tr( "This algorithm rasterizes map canvas content.\n\n"
125  "A map theme can be selected to render a predetermined set of layers with a defined style for each layer. "
126  "Alternatively, a set of layers can be selected if no map theme is set. "
127  "If neither map theme nor layer is set, all the visible layers in the set extent will be rendered.\n\n"
128  "The minimum extent entered will internally be extended to a multiple of the tile size." );
129 }
130 
131 QgsRasterizeAlgorithm *QgsRasterizeAlgorithm::createInstance() const
132 {
133  return new QgsRasterizeAlgorithm();
134 }
135 
136 
137 QVariantMap QgsRasterizeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
138 {
139  // Note: MAP_THEME and LAYERS are handled and cloned in prepareAlgorithm
140  const QgsRectangle extent { parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, context.project()->crs() ) };
141  const int tileSize { parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context ) };
142  const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
143  const double mapUnitsPerPixel { parameterAsDouble( parameters, QStringLiteral( "MAP_UNITS_PER_PIXEL" ), context ) };
144  const double extentBuffer { parameterAsDouble( parameters, QStringLiteral( "EXTENT_BUFFER" ), context ) };
145  const QString outputLayerFileName { parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context )};
146 
147  int xTileCount { static_cast<int>( ceil( extent.width() / mapUnitsPerPixel / tileSize ) )};
148  int yTileCount { static_cast<int>( ceil( extent.height() / mapUnitsPerPixel / tileSize ) )};
149  int width { xTileCount * tileSize };
150  int height { yTileCount * tileSize };
151  int nBands { transparent ? 4 : 3 };
152 
153  const QString driverName { QgsRasterFileWriter::driverForExtension( QFileInfo( outputLayerFileName ).suffix() ) };
154  if ( driverName.isEmpty() )
155  {
156  throw QgsProcessingException( QObject::tr( "Invalid output raster format" ) );
157  }
158 
159  GDALDriverH hOutputFileDriver = GDALGetDriverByName( driverName.toLocal8Bit().constData() );
160  if ( !hOutputFileDriver )
161  {
162  throw QgsProcessingException( QObject::tr( "Error creating GDAL driver" ) );
163  }
164 
165  gdal::dataset_unique_ptr hOutputDataset( GDALCreate( hOutputFileDriver, outputLayerFileName.toLocal8Bit().constData(), width, height, nBands, GDALDataType::GDT_Byte, nullptr ) );
166  if ( !hOutputDataset )
167  {
168  throw QgsProcessingException( QObject::tr( "Error creating GDAL output layer" ) );
169  }
170 
171  GDALSetProjection( hOutputDataset.get(), context.project()->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLatin1().constData() );
172  double geoTransform[6];
173  geoTransform[0] = extent.xMinimum();
174  geoTransform[1] = mapUnitsPerPixel;
175  geoTransform[2] = 0;
176  geoTransform[3] = extent.yMaximum();
177  geoTransform[4] = 0;
178  geoTransform[5] = - mapUnitsPerPixel;
179  GDALSetGeoTransform( hOutputDataset.get(), geoTransform );
180 
181  int red = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorRedPart", 255 );
182  int green = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorGreenPart", 255 );
183  int blue = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorBluePart", 255 );
184 
185  QColor bgColor;
186  if ( transparent )
187  {
188  bgColor = QColor( red, green, blue, 0 );
189  }
190  else
191  {
192  bgColor = QColor( red, green, blue );
193  }
194 
195  QgsMapSettings mapSettings;
196  mapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
197  mapSettings.setDestinationCrs( context.project()->crs() );
198  mapSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, true );
200  mapSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, 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 
219  // Custom deleter for CPL allocation
220  struct CPLDelete
221  {
222  void operator()( uint8_t *ptr ) const
223  {
224  CPLFree( ptr );
225  }
226  };
227 
228  QAtomicInt rendered = 0;
229  QMutex rasterWriteLocker;
230 
231  const auto renderJob = [ & ]( const int x, const int y, QgsMapSettings mapSettings )
232  {
233  QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
234  mapSettings.setOutputDpi( image.logicalDpiX() );
235  mapSettings.setOutputSize( image.size() );
236  QPainter painter { &image };
237  if ( feedback->isCanceled() )
238  {
239  return;
240  }
241  image.fill( transparent ? bgColor.rgba() : bgColor.rgb() );
242  mapSettings.setExtent( QgsRectangle(
243  extent.xMinimum() + x * extentRatio,
244  extent.yMaximum() - ( y + 1 ) * extentRatio,
245  extent.xMinimum() + ( x + 1 ) * extentRatio,
246  extent.yMaximum() - y * extentRatio
247  ) );
248  QgsMapRendererCustomPainterJob job( mapSettings, &painter );
249  job.start();
250  job.waitForFinished();
251 
252  gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
253  if ( !hIntermediateDataset )
254  {
255  throw QgsProcessingException( QStringLiteral( "Error reading tiles from the temporary image" ) );
256  }
257 
258  const int xOffset { x * tileSize };
259  const int yOffset { y * tileSize };
260 
261  std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast< uint8_t * >( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
262  CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(),
263  GF_Read, 0, 0, tileSize, tileSize,
264  buffer.get(),
265  tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
266  if ( err != CE_None )
267  {
268  throw QgsProcessingException( QStringLiteral( "Error reading intermediate raster" ) );
269  }
270 
271  {
272  QMutexLocker locker( &rasterWriteLocker );
273  err = GDALDatasetRasterIO( hOutputDataset.get(),
274  GF_Write, xOffset, yOffset, tileSize, tileSize,
275  buffer.get(),
276  tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
277  rendered++;
278  feedback->setProgress( static_cast<double>( rendered ) / numTiles * 100.0 );
279  }
280  if ( err != CE_None )
281  {
282  throw QgsProcessingException( QStringLiteral( "Error writing output raster" ) );
283  }
284  };
285 
286  feedback->setProgress( 0 );
287 
288  std::vector<QFuture<void>> futures;
289 
290  for ( int x = 0; x < xTileCount; ++x )
291  {
292  for ( int y = 0; y < yTileCount; ++y )
293  {
294  if ( feedback->isCanceled() )
295  {
296  return {};
297  }
298  futures.push_back( QtConcurrent::run( renderJob, x, y, mapSettings ) );
299  }
300  }
301 
302  for ( auto &f : futures )
303  {
304  f.waitForFinished();
305  }
306 
307  return { { QStringLiteral( "OUTPUT" ), outputLayerFileName } };
308 }
309 
310 
311 bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
312 {
313  Q_UNUSED( feedback )
314  // Retrieve and clone layers
315  const QString mapTheme { parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context ) };
316  const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ) };
317  if ( ! mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
318  {
319  const auto constLayers { context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
320  for ( const QgsMapLayer *ml : constLayers )
321  {
322  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
323  }
324  mMapThemeStyleOverrides = context.project()->mapThemeCollection( )->mapThemeStyleOverrides( mapTheme );
325  }
326  else if ( ! mapLayers.isEmpty() )
327  {
328  for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
329  {
330  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
331  }
332  }
333  // Still no layers? Get them all from the project
334  if ( mMapLayers.size() == 0 )
335  {
336  QList<QgsMapLayer *> layers;
337  QgsLayerTree *root = context.project()->layerTreeRoot();
338  for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
339  {
340  QgsMapLayer *layer = nodeLayer->layer();
341  if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
342  layers << layer;
343  }
344 
345  for ( const QgsMapLayer *ml : std::as_const( layers ) )
346  {
347  mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
348  }
349  }
350  return mMapLayers.size() > 0;
351 }
352 
353 
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ 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:73
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.
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 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 setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
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, vector, mesh, point cloud, annotation or plugin layer)
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.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:109
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:106
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:138