QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
QgsGdalUtils::imageToMemoryDataset
static gdal::dataset_unique_ptr imageToMemoryDataset(const QImage &image)
Converts an image to a GDAL memory dataset by borrowing image data.
Definition: qgsgdalutils.cpp:109
QgsMapSettings::setDestinationCrs
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
Definition: qgsmapsettings.cpp:350
QgsLayerTreeGroup::findLayers
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Definition: qgslayertreegroup.cpp:249
QgsFeedback::setProgress
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:76
QgsMapSettings::setFlag
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
Definition: qgsmapsettings.cpp:382
QgsProcessingContext::project
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Definition: qgsprocessingcontext.h:121
QgsProcessingParameterNumber
A numeric parameter for processing algorithms.
Definition: qgsprocessingparameters.h:2179
qgsmapthemecollection.h
QgsProcessingFeedback
Base class for providing feedback from a processing algorithm.
Definition: qgsprocessingfeedback.h:37
QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL
@ WKT_PREFERRED_GDAL
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition: qgscoordinatereferencesystem.h:682
Qgis::MapSettingsFlag::UseAdvancedEffects
@ UseAdvancedEffects
Enable layer opacity and blending effects.
QgsRasterFileWriter::driverForExtension
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
Definition: qgsrasterfilewriter.cpp:1066
qgsgdalutils.h
QgsProcessingParameterRasterDestination
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
Definition: qgsprocessingparameters.h:3390
QgsProcessingParameterMapTheme
A map theme parameter for processing algorithms, allowing users to select an existing map theme from ...
Definition: qgsprocessingparameters.h:3905
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
qgsmaprenderercustompainterjob.h
QgsMapSettings::setOutputDpi
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
Definition: qgsmapsettings.cpp:272
QgsProcessingParameterMultipleLayers
A parameter for processing algorithms which accepts multiple map layers.
Definition: qgsprocessingparameters.h:2097
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Qgis::MapSettingsFlag::Antialiasing
@ Antialiasing
Enable anti-aliasing for map rendering.
QgsMapThemeCollection::mapThemeVisibleLayers
QList< QgsMapLayer * > mapThemeVisibleLayers(const QString &name) const
Returns the list of layers that are visible for the specified map theme.
Definition: qgsmapthemecollection.cpp:331
QgsMapThemeCollection::mapThemeStyleOverrides
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme.
Definition: qgsmapthemecollection.cpp:385
qgsrasterfilewriter.h
QgsProject::layerTreeRoot
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
Definition: qgsproject.cpp:3541
QgsLayerTree
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
QgsMapSettings::setBackgroundColor
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
Definition: qgsmapsettings.h:381
QgsProcessing::TypeMapLayer
@ TypeMapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer)
Definition: qgsprocessing.h:47
gdal::dataset_unique_ptr
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:139
QgsProcessingContext
Contains information about the context in which a processing algorithm is executed.
Definition: qgsprocessingcontext.h:46
QgsLayerTree::layerOrder
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Definition: qgslayertree.cpp:79
QgsLayerTreeLayer
Layer tree node points to a map layer.
Definition: qgslayertreelayer.h:43
QgsProcessingParameterExtent
A rectangular map extent parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1772
QgsCoordinateReferenceSystem::toWkt
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Definition: qgscoordinatereferencesystem.cpp:1810
QgsProcessingContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Definition: qgsprocessingcontext.h:165
QgsProject::mapThemeCollection
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:112
QgsMapSettings::setTransformContext
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
Definition: qgsmapsettings.cpp:473
qgslayertree.h
Qgis::MapSettingsFlag::HighQualityImageTransforms
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
QgsMapSettings::setLayers
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
Definition: qgsmapsettings.cpp:327
QgsMapRendererCustomPainterJob
Job implementation that renders everything sequentially using a custom painter.
Definition: qgsmaprenderercustompainterjob.h:63
qgsprocessingparameters.h
QgsProcessingParameterBoolean
A boolean parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1709
qgsalgorithmrasterize.h
QgsMapSettings::setLayerStyleOverrides
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 ...
Definition: qgsmapsettings.cpp:345
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsMapSettings::setOutputImageFormat
void setOutputImageFormat(QImage::Format format)
sets format of internal QImage
Definition: qgsmapsettings.h:442
QgsMapThemeCollection::hasMapTheme
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
Definition: qgsmapthemecollection.cpp:257
QgsMapSettings::setOutputSize
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
Definition: qgsmapsettings.cpp:244
QgsProcessingAlgorithm::flags
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Definition: qgsprocessingalgorithm.cpp:90
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map. The rendering itself is don...
Definition: qgsmapsettings.h:88
QgsMapSettings::setExtent
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
Definition: qgsmapsettings.cpp:80
QgsProject::crs
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:109
QgsProcessingException
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsProject::readNumEntry
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
Definition: qgsproject.cpp:2972
QgsMapSettings::setExtentBuffer
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
Definition: qgsmapsettings.cpp:97
Qgis::MapSettingsFlag::RenderMapTile
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.