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