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