QGIS API Documentation  3.12.1-București (121cc00ff0)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 : matthias@opengis.ch
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::WKT2_2018 ).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 
A boolean parameter for processing algorithms.
A parameter for processing algorithms which accepts multiple map layers.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:79
void setExtent(const QgsRectangle &rect, bool magnified=true)
Set coordinates of the rectangle which should be rendered.
Job implementation that renders everything sequentially using a custom painter.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
Base class for providing feedback from a processing algorithm.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
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...
static gdal::dataset_unique_ptr imageToMemoryDataset(const QImage &image)
Converts an image to a GDAL memory dataset by borrowing image data.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:64
void setOutputDpi(double dpi)
Sets DPI used for conversion between real world units (e.g. mm) and pixels.
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...
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
Enable layer opacity and blending effects.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
QgsProject * project() const
Returns the project in which the algorithm is being executed.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
The QgsMapSettings class contains configuration for rendering of the map.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
void setOutputSize(QSize size)
Sets the size of the resulting map image.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:100
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:97
Enable anti-aliasing for map rendering.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
void setOutputImageFormat(QImage::Format format)
sets format of internal QImage
A rectangular map extent parameter for processing algorithms.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
A numeric parameter for processing algorithms.
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
Draw map such that there are no problems between adjacent tiles.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:55
Any map layer type (raster or vector or mesh)
Definition: qgsprocessing.h:46
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:450
void setLayers(const QList< QgsMapLayer *> &layers)
Set list of layers for map rendering.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
A map theme parameter for processing algorithms, allowing users to select an existing map theme from ...
Contains information about the context in which a processing algorithm is executed.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.