Branch data Line data Source code
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"
26 : : #include "qgsprocessingparameters.h"
27 : : #include "qgsmapthemecollection.h"
28 : : #include "qgsrasterfilewriter.h"
29 : : #include "qgsmaprenderercustompainterjob.h"
30 : : #include "gdal.h"
31 : : #include "qgsgdalutils.h"
32 : : #include "qgslayertree.h"
33 : :
34 : : #include <QtConcurrent>
35 : :
36 : : ///@cond PRIVATE
37 : :
38 : 0 : QString QgsRasterizeAlgorithm::name() const
39 : : {
40 : 0 : return QStringLiteral( "rasterize" );
41 : : }
42 : :
43 : 0 : QString QgsRasterizeAlgorithm::displayName() const
44 : : {
45 : 0 : return QObject::tr( "Convert map to raster" );
46 : : }
47 : :
48 : 0 : QStringList QgsRasterizeAlgorithm::tags() const
49 : : {
50 : 0 : return QObject::tr( "layer,raster,convert,file,map themes,tiles,render" ).split( ',' );
51 : 0 : }
52 : :
53 : 0 : QgsProcessingAlgorithm::Flags QgsRasterizeAlgorithm::flags() const
54 : : {
55 : 0 : return QgsProcessingAlgorithm::flags() | FlagRequiresProject;
56 : : }
57 : :
58 : 0 : QString QgsRasterizeAlgorithm::group() const
59 : : {
60 : 0 : return QObject::tr( "Raster tools" );
61 : : }
62 : :
63 : 0 : QString QgsRasterizeAlgorithm::groupId() const
64 : : {
65 : 0 : return QStringLiteral( "rastertools" );
66 : : }
67 : :
68 : 0 : void QgsRasterizeAlgorithm::initAlgorithm( const QVariantMap & )
69 : : {
70 : 0 : addParameter( new QgsProcessingParameterExtent(
71 : 0 : QStringLiteral( "EXTENT" ),
72 : 0 : QObject::tr( "Minimum extent to render" ) ) );
73 : 0 : addParameter( new QgsProcessingParameterNumber(
74 : 0 : QStringLiteral( "EXTENT_BUFFER" ),
75 : 0 : QObject::tr( "Buffer around tiles in map units" ),
76 : : QgsProcessingParameterNumber::Type::Double,
77 : 0 : 0,
78 : : true,
79 : : 0 ) );
80 : 0 : addParameter( new QgsProcessingParameterNumber(
81 : 0 : QStringLiteral( "TILE_SIZE" ),
82 : 0 : QObject::tr( "Tile size" ),
83 : : QgsProcessingParameterNumber::Type::Integer,
84 : 0 : 1024,
85 : : false,
86 : : 64 ) );
87 : 0 : addParameter( new QgsProcessingParameterNumber(
88 : 0 : QStringLiteral( "MAP_UNITS_PER_PIXEL" ),
89 : 0 : QObject::tr( "Map units per pixel" ),
90 : : QgsProcessingParameterNumber::Type::Double,
91 : 0 : 100,
92 : : true,
93 : : 0 ) );
94 : 0 : addParameter( new QgsProcessingParameterBoolean(
95 : 0 : QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ),
96 : 0 : QObject::tr( "Make background transparent" ),
97 : 0 : false ) );
98 : :
99 : 0 : addParameter( new QgsProcessingParameterMapTheme(
100 : 0 : QStringLiteral( "MAP_THEME" ),
101 : 0 : QObject::tr( "Map theme to render" ),
102 : 0 : QVariant(), true ) );
103 : :
104 : 0 : QList<QgsMapLayer *> projectLayers { QgsProject::instance()->mapLayers().values() };
105 : 0 : addParameter( new QgsProcessingParameterMultipleLayers(
106 : 0 : QStringLiteral( "LAYERS" ),
107 : 0 : QObject::tr( "Layers to render" ),
108 : : QgsProcessing::TypeMapLayer,
109 : 0 : QVariant(),
110 : : true
111 : : ) );
112 : 0 : addParameter( new QgsProcessingParameterRasterDestination(
113 : 0 : QStringLiteral( "OUTPUT" ),
114 : 0 : QObject::tr( "Output layer" ) ) );
115 : :
116 : 0 : }
117 : :
118 : 0 : QString QgsRasterizeAlgorithm::shortDescription() const
119 : : {
120 : 0 : return QObject::tr( "Renders the map canvas to a raster file." );
121 : : }
122 : :
123 : 0 : QString QgsRasterizeAlgorithm::shortHelpString() const
124 : : {
125 : 0 : return QObject::tr( "This algorithm rasterizes map canvas content.\n\n"
126 : : "A map theme can be selected to render a predetermined set of layers with a defined style for each layer. "
127 : : "Alternatively, a set of layers can be selected if no map theme is set. "
128 : : "If neither map theme nor layer is set, all the visible layers in the set extent will be rendered.\n\n"
129 : : "The minimum extent entered will internally be extended to a multiple of the tile size." );
130 : : }
131 : :
132 : 0 : QgsRasterizeAlgorithm *QgsRasterizeAlgorithm::createInstance() const
133 : : {
134 : 0 : return new QgsRasterizeAlgorithm();
135 : : }
136 : :
137 : :
138 : 0 : QVariantMap QgsRasterizeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
139 : : {
140 : : // Note: MAP_THEME and LAYERS are handled and cloned in prepareAlgorithm
141 : 0 : const QgsRectangle extent { parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, context.project()->crs() ) };
142 : 0 : const int tileSize { parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context ) };
143 : 0 : const bool transparent { parameterAsBool( parameters, QStringLiteral( "MAKE_BACKGROUND_TRANSPARENT" ), context ) };
144 : 0 : const double mapUnitsPerPixel { parameterAsDouble( parameters, QStringLiteral( "MAP_UNITS_PER_PIXEL" ), context ) };
145 : 0 : const double extentBuffer { parameterAsDouble( parameters, QStringLiteral( "EXTENT_BUFFER" ), context ) };
146 : 0 : const QString outputLayerFileName { parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context )};
147 : :
148 : 0 : int xTileCount { static_cast<int>( ceil( extent.width() / mapUnitsPerPixel / tileSize ) )};
149 : 0 : int yTileCount { static_cast<int>( ceil( extent.height() / mapUnitsPerPixel / tileSize ) )};
150 : 0 : int width { xTileCount * tileSize };
151 : 0 : int height { yTileCount * tileSize };
152 : 0 : int nBands { transparent ? 4 : 3 };
153 : :
154 : 0 : const QString driverName { QgsRasterFileWriter::driverForExtension( QFileInfo( outputLayerFileName ).suffix() ) };
155 : 0 : if ( driverName.isEmpty() )
156 : : {
157 : 0 : throw QgsProcessingException( QObject::tr( "Invalid output raster format" ) );
158 : : }
159 : :
160 : 0 : GDALDriverH hOutputFileDriver = GDALGetDriverByName( driverName.toLocal8Bit().constData() );
161 : 0 : if ( !hOutputFileDriver )
162 : : {
163 : 0 : throw QgsProcessingException( QObject::tr( "Error creating GDAL driver" ) );
164 : : }
165 : :
166 : 0 : gdal::dataset_unique_ptr hOutputDataset( GDALCreate( hOutputFileDriver, outputLayerFileName.toLocal8Bit().constData(), width, height, nBands, GDALDataType::GDT_Byte, nullptr ) );
167 : 0 : if ( !hOutputDataset )
168 : : {
169 : 0 : throw QgsProcessingException( QObject::tr( "Error creating GDAL output layer" ) );
170 : : }
171 : :
172 : 0 : GDALSetProjection( hOutputDataset.get(), context.project()->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLatin1().constData() );
173 : : double geoTransform[6];
174 : 0 : geoTransform[0] = extent.xMinimum();
175 : 0 : geoTransform[1] = mapUnitsPerPixel;
176 : 0 : geoTransform[2] = 0;
177 : 0 : geoTransform[3] = extent.yMaximum();
178 : 0 : geoTransform[4] = 0;
179 : 0 : geoTransform[5] = - mapUnitsPerPixel;
180 : 0 : GDALSetGeoTransform( hOutputDataset.get(), geoTransform );
181 : :
182 : 0 : int red = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorRedPart", 255 );
183 : 0 : int green = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorGreenPart", 255 );
184 : 0 : int blue = context.project()->readNumEntry( QStringLiteral( "Gui" ), "/CanvasColorBluePart", 255 );
185 : :
186 : 0 : QColor bgColor;
187 : 0 : if ( transparent )
188 : : {
189 : 0 : bgColor = QColor( red, green, blue, 0 );
190 : 0 : }
191 : : else
192 : : {
193 : 0 : bgColor = QColor( red, green, blue );
194 : : }
195 : :
196 : 0 : QgsMapSettings mapSettings;
197 : 0 : mapSettings.setOutputImageFormat( QImage::Format_ARGB32 );
198 : 0 : mapSettings.setDestinationCrs( context.project()->crs() );
199 : 0 : mapSettings.setFlag( QgsMapSettings::Antialiasing, true );
200 : 0 : mapSettings.setFlag( QgsMapSettings::RenderMapTile, true );
201 : 0 : mapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, true );
202 : 0 : mapSettings.setTransformContext( context.transformContext() );
203 : 0 : mapSettings.setExtentBuffer( extentBuffer );
204 : 0 : mapSettings.setBackgroundColor( bgColor );
205 : :
206 : : // Set layers cloned in prepareAlgorithm
207 : 0 : QList<QgsMapLayer *> layers;
208 : 0 : for ( const auto &lptr : mMapLayers )
209 : : {
210 : 0 : layers.push_back( lptr.get() );
211 : : }
212 : 0 : mapSettings.setLayers( layers );
213 : 0 : mapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
214 : :
215 : : // Start rendering
216 : 0 : const double extentRatio { mapUnitsPerPixel * tileSize };
217 : 0 : const int numTiles { xTileCount * yTileCount };
218 : 0 : const QString fileExtension { QFileInfo( outputLayerFileName ).suffix() };
219 : :
220 : : // Custom deleter for CPL allocation
221 : : struct CPLDelete
222 : : {
223 : 0 : void operator()( uint8_t *ptr ) const
224 : : {
225 : 0 : CPLFree( ptr );
226 : 0 : }
227 : : };
228 : :
229 : 0 : QAtomicInt rendered = 0;
230 : 0 : QMutex rasterWriteLocker;
231 : :
232 : 0 : const auto renderJob = [ & ]( const int x, const int y, QgsMapSettings mapSettings )
233 : : {
234 : 0 : QImage image { tileSize, tileSize, QImage::Format::Format_ARGB32 };
235 : 0 : mapSettings.setOutputDpi( image.logicalDpiX() );
236 : 0 : mapSettings.setOutputSize( image.size() );
237 : 0 : QPainter painter { &image };
238 : 0 : if ( feedback->isCanceled() )
239 : : {
240 : 0 : return;
241 : : }
242 : 0 : image.fill( transparent ? bgColor.rgba() : bgColor.rgb() );
243 : 0 : mapSettings.setExtent( QgsRectangle(
244 : 0 : extent.xMinimum() + x * extentRatio,
245 : 0 : extent.yMaximum() - ( y + 1 ) * extentRatio,
246 : 0 : extent.xMinimum() + ( x + 1 ) * extentRatio,
247 : 0 : extent.yMaximum() - y * extentRatio
248 : : ) );
249 : 0 : QgsMapRendererCustomPainterJob job( mapSettings, &painter );
250 : 0 : job.start();
251 : 0 : job.waitForFinished();
252 : :
253 : 0 : gdal::dataset_unique_ptr hIntermediateDataset( QgsGdalUtils::imageToMemoryDataset( image ) );
254 : 0 : if ( !hIntermediateDataset )
255 : : {
256 : 0 : throw QgsProcessingException( QStringLiteral( "Error reading tiles from the temporary image" ) );
257 : : }
258 : :
259 : 0 : const int xOffset { x * tileSize };
260 : 0 : const int yOffset { y * tileSize };
261 : :
262 : 0 : std::unique_ptr<uint8_t, CPLDelete> buffer( static_cast< uint8_t * >( CPLMalloc( sizeof( uint8_t ) * static_cast<size_t>( tileSize * tileSize * nBands ) ) ) );
263 : 0 : CPLErr err = GDALDatasetRasterIO( hIntermediateDataset.get(),
264 : 0 : GF_Read, 0, 0, tileSize, tileSize,
265 : 0 : buffer.get(),
266 : 0 : tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
267 : 0 : if ( err != CE_None )
268 : : {
269 : 0 : throw QgsProcessingException( QStringLiteral( "Error reading intermediate raster" ) );
270 : : }
271 : :
272 : : {
273 : 0 : QMutexLocker locker( &rasterWriteLocker );
274 : 0 : err = GDALDatasetRasterIO( hOutputDataset.get(),
275 : 0 : GF_Write, xOffset, yOffset, tileSize, tileSize,
276 : 0 : buffer.get(),
277 : 0 : tileSize, tileSize, GDT_Byte, nBands, nullptr, 0, 0, 0 );
278 : 0 : rendered++;
279 : 0 : feedback->setProgress( static_cast<double>( rendered ) / numTiles * 100.0 );
280 : 0 : }
281 : 0 : if ( err != CE_None )
282 : : {
283 : 0 : throw QgsProcessingException( QStringLiteral( "Error writing output raster" ) );
284 : : }
285 : 0 : };
286 : :
287 : 0 : feedback->setProgress( 0 );
288 : :
289 : 0 : std::vector<QFuture<void>> futures;
290 : :
291 : 0 : for ( int x = 0; x < xTileCount; ++x )
292 : : {
293 : 0 : for ( int y = 0; y < yTileCount; ++y )
294 : : {
295 : 0 : if ( feedback->isCanceled() )
296 : : {
297 : 0 : return {};
298 : : }
299 : 0 : futures.push_back( QtConcurrent::run( renderJob, x, y, mapSettings ) );
300 : 0 : }
301 : 0 : }
302 : :
303 : 0 : for ( auto &f : futures )
304 : : {
305 : 0 : f.waitForFinished();
306 : : }
307 : :
308 : 0 : return { { QStringLiteral( "OUTPUT" ), outputLayerFileName } };
309 : 0 : }
310 : :
311 : :
312 : 0 : bool QgsRasterizeAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
313 : : {
314 : : Q_UNUSED( feedback )
315 : : // Retrieve and clone layers
316 : 0 : const QString mapTheme { parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context ) };
317 : 0 : const QList<QgsMapLayer *> mapLayers { parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ) };
318 : 0 : if ( ! mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
319 : : {
320 : 0 : const auto constLayers { context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme ) };
321 : 0 : for ( const QgsMapLayer *ml : constLayers )
322 : : {
323 : 0 : mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
324 : : }
325 : 0 : mMapThemeStyleOverrides = context.project()->mapThemeCollection( )->mapThemeStyleOverrides( mapTheme );
326 : 0 : }
327 : 0 : else if ( ! mapLayers.isEmpty() )
328 : : {
329 : 0 : for ( const QgsMapLayer *ml : std::as_const( mapLayers ) )
330 : : {
331 : 0 : mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
332 : : }
333 : 0 : }
334 : : // Still no layers? Get them all from the project
335 : 0 : if ( mMapLayers.size() == 0 )
336 : : {
337 : 0 : QList<QgsMapLayer *> layers;
338 : 0 : QgsLayerTree *root = context.project()->layerTreeRoot();
339 : 0 : for ( QgsLayerTreeLayer *nodeLayer : root->findLayers() )
340 : : {
341 : 0 : QgsMapLayer *layer = nodeLayer->layer();
342 : 0 : if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
343 : 0 : layers << layer;
344 : : }
345 : :
346 : 0 : for ( const QgsMapLayer *ml : std::as_const( layers ) )
347 : : {
348 : 0 : mMapLayers.push_back( std::unique_ptr<QgsMapLayer>( ml->clone( ) ) );
349 : : }
350 : 0 : }
351 : 0 : return mMapLayers.size() > 0;
352 : 0 : }
353 : :
354 : :
355 : : ///@endcond
|