QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgsalgorithmxyztiles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmxyztiles.cpp
3 ---------------------
4 begin : August 2023
5 copyright : (C) 2023 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include <QBuffer>
21
22#include "qgslayertree.h"
23#include "qgslayertreelayer.h"
25#include "qgsmaplayerutils.h"
26#include "qgsprovidermetadata.h"
27
29
30int tile2tms( const int y, const int zoom )
31{
32 double n = std::pow( 2, zoom );
33 return ( int ) std::floor( n - y - 1 );
34}
35
36int lon2tileX( const double lon, const int z )
37{
38 return ( int ) ( std::floor( ( lon + 180.0 ) / 360.0 * ( 1 << z ) ) );
39}
40
41int lat2tileY( const double lat, const int z )
42{
43 double latRad = lat * M_PI / 180.0;
44 return ( int ) ( std::floor( ( 1.0 - std::asinh( std::tan( latRad ) ) / M_PI ) / 2.0 * ( 1 << z ) ) );
45}
46
47double tileX2lon( const int x, const int z )
48{
49 return x / ( double ) ( 1 << z ) * 360.0 - 180;
50}
51
52double tileY2lat( const int y, const int z )
53{
54 double n = M_PI - 2.0 * M_PI * y / ( double ) ( 1 << z );
55 return 180.0 / M_PI * std::atan( 0.5 * ( std::exp( n ) - std::exp( -n ) ) );
56}
57
58void extent2TileXY( QgsRectangle extent, const int zoom, int &xMin, int &yMin, int &xMax, int &yMax )
59{
60 xMin = lon2tileX( extent.xMinimum(), zoom );
61 yMin = lat2tileY( extent.yMinimum(), zoom );
62 xMax = lon2tileX( extent.xMaximum(), zoom );
63 yMax = lat2tileY( extent.xMaximum(), zoom );
64}
65
66QList<MetaTile> getMetatiles( const QgsRectangle extent, const int zoom, const int tileSize )
67{
68 int minX = lon2tileX( extent.xMinimum(), zoom );
69 int minY = lat2tileY( extent.yMaximum(), zoom );
70 int maxX = lon2tileX( extent.xMaximum(), zoom );
71 int maxY = lat2tileY( extent.yMinimum(), zoom );
72 ;
73
74 int i = 0;
75 QMap<QString, MetaTile> tiles;
76 for ( int x = minX; x <= maxX; x++ )
77 {
78 int j = 0;
79 for ( int y = minY; y <= maxY; y++ )
80 {
81 QString key = QStringLiteral( "%1:%2" ).arg( ( int ) ( i / tileSize ) ).arg( ( int ) ( j / tileSize ) );
82 MetaTile tile = tiles.value( key, MetaTile() );
83 tile.addTile( i % tileSize, j % tileSize, Tile( x, y, zoom ) );
84 tiles.insert( key, tile );
85 j++;
86 }
87 i++;
88 }
89 return tiles.values();
90}
91
93
94QString QgsXyzTilesBaseAlgorithm::group() const
95{
96 return QObject::tr( "Raster tools" );
97}
98
99QString QgsXyzTilesBaseAlgorithm::groupId() const
100{
101 return QStringLiteral( "rastertools" );
102}
103
104Qgis::ProcessingAlgorithmFlags QgsXyzTilesBaseAlgorithm::flags() const
105{
107}
108
109void QgsXyzTilesBaseAlgorithm::createCommonParameters()
110{
111 addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
112 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ZOOM_MIN" ), QObject::tr( "Minimum zoom" ), Qgis::ProcessingNumberParameterType::Integer, 12, false, 0, 25 ) );
113 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ZOOM_MAX" ), QObject::tr( "Maximum zoom" ), Qgis::ProcessingNumberParameterType::Integer, 12, false, 0, 25 ) );
114 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DPI" ), QObject::tr( "DPI" ), Qgis::ProcessingNumberParameterType::Integer, 96, false, 48, 600 ) );
115 addParameter( new QgsProcessingParameterColor( QStringLiteral( "BACKGROUND_COLOR" ), QObject::tr( "Background color" ), QColor( Qt::transparent ), true, true ) );
116 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true ) );
117 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "TILE_FORMAT" ), QObject::tr( "Tile format" ), QStringList() << QStringLiteral( "PNG" ) << QStringLiteral( "JPG" ), false, 0 ) );
118 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "QUALITY" ), QObject::tr( "Quality (JPG only)" ), Qgis::ProcessingNumberParameterType::Integer, 75, false, 1, 100 ) );
119 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "METATILESIZE" ), QObject::tr( "Metatile size" ), Qgis::ProcessingNumberParameterType::Integer, 4, false, 1, 20 ) );
120}
121
122bool QgsXyzTilesBaseAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
123{
124 Q_UNUSED( feedback );
125
126 QgsProject *project = context.project();
127
128 const QList<QgsLayerTreeLayer *> projectLayers = project->layerTreeRoot()->findLayers();
129 QSet<QString> visibleLayers;
130 for ( const QgsLayerTreeLayer *layer : projectLayers )
131 {
132 if ( layer->isVisible() )
133 {
134 visibleLayers << layer->layer()->id();
135 }
136 }
137
138 QList<QgsMapLayer *> renderLayers = project->layerTreeRoot()->layerOrder();
139 for ( QgsMapLayer *layer : renderLayers )
140 {
141 if ( visibleLayers.contains( layer->id() ) )
142 {
143 QgsMapLayer *clonedLayer = layer->clone();
144 clonedLayer->moveToThread( nullptr );
145 mLayers << clonedLayer;
146 }
147 }
148
149 QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context );
150 QgsCoordinateReferenceSystem extentCrs = parameterAsExtentCrs( parameters, QStringLiteral( "EXTENT" ), context );
151 QgsCoordinateTransform ct( extentCrs, project->crs(), context.transformContext() );
152 mExtent = ct.transformBoundingBox( extent );
153
154 mMinZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MIN" ), context );
155 mMaxZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MAX" ), context );
156 mDpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context );
157 mBackgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context );
158 mAntialias = parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context );
159 mTileFormat = parameterAsEnum( parameters, QStringLiteral( "TILE_FORMAT" ), context ) ? QStringLiteral( "JPG" ) : QStringLiteral( "PNG" );
160 mJpgQuality = mTileFormat == QLatin1String( "JPG" ) ? parameterAsInt( parameters, QStringLiteral( "QUALITY" ), context ) : -1;
161 mMetaTileSize = parameterAsInt( parameters, QStringLiteral( "METATILESIZE" ), context );
162 mThreadsNumber = context.maximumThreads();
163 mTransformContext = context.transformContext();
164 mFeedback = feedback;
165
166 mWgs84Crs = QgsCoordinateReferenceSystem( "EPSG:4326" );
167 mMercatorCrs = QgsCoordinateReferenceSystem( "EPSG:3857" );
168 mSrc2Wgs = QgsCoordinateTransform( project->crs(), mWgs84Crs, context.transformContext() );
169 mWgs2Mercator = QgsCoordinateTransform( mWgs84Crs, mMercatorCrs, context.transformContext() );
170
171 mWgs84Extent = mSrc2Wgs.transformBoundingBox( mExtent );
172
173 if ( parameters.contains( QStringLiteral( "TILE_WIDTH" ) ) )
174 {
175 mTileWidth = parameterAsInt( parameters, QStringLiteral( "TILE_WIDTH" ), context );
176 }
177
178 if ( parameters.contains( QStringLiteral( "TILE_HEIGHT" ) ) )
179 {
180 mTileHeight = parameterAsInt( parameters, QStringLiteral( "TILE_HEIGHT" ), context );
181 }
182
183 if ( mTileFormat != QLatin1String( "PNG" ) && mBackgroundColor.alpha() != 255 )
184 {
185 feedback->pushWarning( QObject::tr( "Background color setting ignored, the JPG format only supports fully opaque colors" ) );
186 }
187
188 return true;
189}
190
191void QgsXyzTilesBaseAlgorithm::checkLayersUsagePolicy( QgsProcessingFeedback *feedback )
192{
193 if ( mTotalTiles > MAXIMUM_OPENSTREETMAP_TILES_FETCH )
194 {
195 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
196 {
198 {
199 // Prevent bulk downloading of tiles from openstreetmap.org as per OSMF tile usage policy
200 feedback->pushFormattedMessage( QObject::tr( "Layer %1 will be skipped as the algorithm leads to bulk downloading behavior which is prohibited by the %2OpenStreetMap Foundation tile usage policy%3" ).arg( layer->name(), QStringLiteral( "<a href=\"https://operations.osmfoundation.org/policies/tiles/\">" ), QStringLiteral( "</a>" ) ), QObject::tr( "Layer %1 will be skipped as the algorithm leads to bulk downloading behavior which is prohibited by the %2OpenStreetMap Foundation tile usage policy%3" ).arg( layer->name(), QString(), QString() ) );
201 mLayers.removeAll( layer );
202 delete layer;
203 }
204 }
205 }
206}
207
208void QgsXyzTilesBaseAlgorithm::startJobs()
209{
210 while ( mRendererJobs.size() < mThreadsNumber && !mMetaTiles.empty() )
211 {
212 MetaTile metaTile = mMetaTiles.takeFirst();
213
214 QgsMapSettings settings;
215 settings.setExtent( mWgs2Mercator.transformBoundingBox( metaTile.extent() ) );
216 settings.setOutputImageFormat( QImage::Format_ARGB32_Premultiplied );
217 settings.setTransformContext( mTransformContext );
218 settings.setDestinationCrs( mMercatorCrs );
219 settings.setLayers( mLayers );
220 settings.setOutputDpi( mDpi );
221 settings.setFlag( Qgis::MapSettingsFlag::Antialiasing, mAntialias );
222 if ( mTileFormat == QLatin1String( "PNG" ) || mBackgroundColor.alpha() == 255 )
223 {
224 settings.setBackgroundColor( mBackgroundColor );
225 }
226 QSize size( mTileWidth * metaTile.rows, mTileHeight * metaTile.cols );
227 settings.setOutputSize( size );
228
229 QgsLabelingEngineSettings labelingSettings = settings.labelingEngineSettings();
230 labelingSettings.setFlag( Qgis::LabelingFlag::UsePartialCandidates, false );
231 settings.setLabelingEngineSettings( labelingSettings );
232
233 QgsExpressionContext exprContext = settings.expressionContext();
235 settings.setExpressionContext( exprContext );
236
238 mRendererJobs.insert( job, metaTile );
239 QObject::connect( job, &QgsMapRendererJob::finished, mFeedback, [this, job]() { processMetaTile( job ); } );
240 job->start();
241 }
242}
243
244// Native XYZ tiles (directory) algorithm
245
246QString QgsXyzTilesDirectoryAlgorithm::name() const
247{
248 return QStringLiteral( "tilesxyzdirectory" );
249}
250
251QString QgsXyzTilesDirectoryAlgorithm::displayName() const
252{
253 return QObject::tr( "Generate XYZ tiles (Directory)" );
254}
255
256QStringList QgsXyzTilesDirectoryAlgorithm::tags() const
257{
258 return QObject::tr( "tiles,xyz,tms,directory" ).split( ',' );
259}
260
261QString QgsXyzTilesDirectoryAlgorithm::shortHelpString() const
262{
263 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as individual images in a directory." );
264}
265
266QgsXyzTilesDirectoryAlgorithm *QgsXyzTilesDirectoryAlgorithm::createInstance() const
267{
268 return new QgsXyzTilesDirectoryAlgorithm();
269}
270
271void QgsXyzTilesDirectoryAlgorithm::initAlgorithm( const QVariantMap & )
272{
273 createCommonParameters();
274 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_WIDTH" ), QObject::tr( "Tile width" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
275 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_HEIGHT" ), QObject::tr( "Tile height" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
276 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "TMS_CONVENTION" ), QObject::tr( "Use inverted tile Y axis (TMS convention)" ), false ) );
277
278 std::unique_ptr<QgsProcessingParameterString> titleParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "HTML_TITLE" ), QObject::tr( "Leaflet HTML output title" ), QVariant(), false, true );
279 titleParam->setFlags( titleParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
280 addParameter( titleParam.release() );
281 std::unique_ptr<QgsProcessingParameterString> attributionParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "HTML_ATTRIBUTION" ), QObject::tr( "Leaflet HTML output attribution" ), QVariant(), false, true );
282 attributionParam->setFlags( attributionParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
283 addParameter( attributionParam.release() );
284 std::unique_ptr<QgsProcessingParameterBoolean> osmParam = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral( "HTML_OSM" ), QObject::tr( "Include OpenStreetMap basemap in Leaflet HTML output" ), false );
285 osmParam->setFlags( osmParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
286 addParameter( osmParam.release() );
287
288 addParameter( new QgsProcessingParameterFolderDestination( QStringLiteral( "OUTPUT_DIRECTORY" ), QObject::tr( "Output directory" ) ) );
289 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML" ), QObject::tr( "Output html (Leaflet)" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
290}
291
292QVariantMap QgsXyzTilesDirectoryAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
293{
294 const bool tms = parameterAsBoolean( parameters, QStringLiteral( "TMS_CONVENTION" ), context );
295 const QString title = parameterAsString( parameters, QStringLiteral( "HTML_TITLE" ), context );
296 const QString attribution = parameterAsString( parameters, QStringLiteral( "HTML_ATTRIBUTION" ), context );
297 const bool useOsm = parameterAsBoolean( parameters, QStringLiteral( "HTML_OSM" ), context );
298 QString outputDir = parameterAsString( parameters, QStringLiteral( "OUTPUT_DIRECTORY" ), context );
299 const QString outputHtml = parameterAsString( parameters, QStringLiteral( "OUTPUT_HTML" ), context );
300
301 mOutputDir = outputDir;
302 mTms = tms;
303
304 mTotalTiles = 0;
305 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
306 {
307 if ( feedback->isCanceled() )
308 break;
309
310 mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
311 feedback->pushWarning( QObject::tr( "%1 tiles will be created for zoom level %2" ).arg( mMetaTiles.size() - mTotalTiles ).arg( z ) );
312 mTotalTiles = mMetaTiles.size();
313 }
314 feedback->pushWarning( QObject::tr( "A total of %1 tiles will be created" ).arg( mTotalTiles ) );
315
316 checkLayersUsagePolicy( feedback );
317
318 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
319 {
320 layer->moveToThread( QThread::currentThread() );
321 }
322
323 QEventLoop loop;
324 // cppcheck-suppress danglingLifetime
325 mEventLoop = &loop;
326 startJobs();
327 loop.exec();
328
329 qDeleteAll( mLayers );
330 mLayers.clear();
331
332 QVariantMap results;
333 results.insert( QStringLiteral( "OUTPUT_DIRECTORY" ), outputDir );
334
335 if ( !outputHtml.isEmpty() )
336 {
337 QString osm = QStringLiteral(
338 "var osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',"
339 "{minZoom: %1, maxZoom: %2, attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);"
340 )
341 .arg( mMinZoom )
342 .arg( mMaxZoom );
343
344 QString addOsm = useOsm ? osm : QString();
345 QString tmsConvention = tms ? QStringLiteral( "true" ) : QStringLiteral( "false" );
346 QString attr = attribution.isEmpty() ? QStringLiteral( "Created by QGIS" ) : attribution;
347 QString tileSource = QStringLiteral( "'file:///%1/{z}/{x}/{y}.%2'" )
348 .arg( outputDir.replace( "\\", "/" ).toHtmlEscaped() )
349 .arg( mTileFormat.toLower() );
350
351 QString html = QStringLiteral(
352 "<!DOCTYPE html><html><head><title>%1</title><meta charset=\"utf-8\"/>"
353 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
354 "<link rel=\"stylesheet\" href=\"https://unpkg.com/[email protected]/dist/leaflet.css\""
355 "integrity=\"sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H\""
356 "crossorigin=\"\"/>"
357 "<script src=\"https://unpkg.com/[email protected]/dist/leaflet.js\""
358 "integrity=\"sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH\""
359 "crossorigin=\"\"></script>"
360 "<style type=\"text/css\">body {margin: 0;padding: 0;} html, body, #map{width: 100%;height: 100%;}</style></head>"
361 "<body><div id=\"map\"></div><script>"
362 "var map = L.map('map', {attributionControl: false}).setView([%2, %3], %4);"
363 "L.control.attribution({prefix: false}).addTo(map);"
364 "%5"
365 "var tilesource_layer = L.tileLayer(%6, {minZoom: %7, maxZoom: %8, tms: %9, attribution: '%10'}).addTo(map);"
366 "</script></body></html>"
367 )
368 .arg( title.isEmpty() ? QStringLiteral( "Leaflet preview" ) : title )
369 .arg( mWgs84Extent.center().y() )
370 .arg( mWgs84Extent.center().x() )
371 .arg( ( mMaxZoom + mMinZoom ) / 2 )
372 .arg( addOsm )
373 .arg( tileSource )
374 .arg( mMinZoom )
375 .arg( mMaxZoom )
376 .arg( tmsConvention )
377 .arg( attr );
378
379 QFile htmlFile( outputHtml );
380 if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
381 {
382 throw QgsProcessingException( QObject::tr( "Could not open html file %1" ).arg( outputHtml ) );
383 }
384 QTextStream fout( &htmlFile );
385#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
386 fout.setCodec( "UTF-8" );
387#endif
388 fout << html;
389
390 results.insert( QStringLiteral( "OUTPUT_HTML" ), outputHtml );
391 }
392
393 return results;
394}
395
396void QgsXyzTilesDirectoryAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
397{
398 MetaTile metaTile = mRendererJobs.value( job );
399 QImage img = job->renderedImage();
400
401 QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
402 while ( it != metaTile.tiles.constEnd() )
403 {
404 QPair<int, int> tm = it.key();
405 Tile tile = it.value();
406 QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
407 QDir tileDir( QStringLiteral( "%1/%2/%3" ).arg( mOutputDir ).arg( tile.z ).arg( tile.x ) );
408 tileDir.mkpath( tileDir.absolutePath() );
409 int y = tile.y;
410 if ( mTms )
411 {
412 y = tile2tms( y, tile.z );
413 }
414 tileImage.save( QStringLiteral( "%1/%2.%3" ).arg( tileDir.absolutePath() ).arg( y ).arg( mTileFormat.toLower() ), mTileFormat.toStdString().c_str(), mJpgQuality );
415 ++it;
416 }
417
418 mRendererJobs.remove( job );
419 job->deleteLater();
420
421 mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
422
423 if ( mFeedback->isCanceled() )
424 {
425 while ( mRendererJobs.size() > 0 )
426 {
427 QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
428 j->cancel();
429 mRendererJobs.remove( j );
430 j->deleteLater();
431 }
432 mRendererJobs.clear();
433 if ( mEventLoop )
434 {
435 mEventLoop->exit();
436 }
437 return;
438 }
439
440 if ( mMetaTiles.size() > 0 )
441 {
442 startJobs();
443 }
444 else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
445 {
446 if ( mEventLoop )
447 {
448 mEventLoop->exit();
449 }
450 }
451}
452
453// Native XYZ tiles (MBTiles) algorithm
454
455QString QgsXyzTilesMbtilesAlgorithm::name() const
456{
457 return QStringLiteral( "tilesxyzmbtiles" );
458}
459
460QString QgsXyzTilesMbtilesAlgorithm::displayName() const
461{
462 return QObject::tr( "Generate XYZ tiles (MBTiles)" );
463}
464
465QStringList QgsXyzTilesMbtilesAlgorithm::tags() const
466{
467 return QObject::tr( "tiles,xyz,tms,mbtiles" ).split( ',' );
468}
469
470QString QgsXyzTilesMbtilesAlgorithm::shortHelpString() const
471{
472 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as an MBTiles file." );
473}
474
475QgsXyzTilesMbtilesAlgorithm *QgsXyzTilesMbtilesAlgorithm::createInstance() const
476{
477 return new QgsXyzTilesMbtilesAlgorithm();
478}
479
480void QgsXyzTilesMbtilesAlgorithm::initAlgorithm( const QVariantMap & )
481{
482 createCommonParameters();
483 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_FILE" ), QObject::tr( "Output" ), QObject::tr( "MBTiles files (*.mbtiles *.MBTILES)" ) ) );
484}
485
486QVariantMap QgsXyzTilesMbtilesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
487{
488 const QString outputFile = parameterAsString( parameters, QStringLiteral( "OUTPUT_FILE" ), context );
489
490 mMbtilesWriter = std::make_unique<QgsMbTiles>( outputFile );
491 if ( !mMbtilesWriter->create() )
492 {
493 throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
494 }
495 mMbtilesWriter->setMetadataValue( "format", mTileFormat.toLower() );
496 mMbtilesWriter->setMetadataValue( "name", QFileInfo( outputFile ).baseName() );
497 mMbtilesWriter->setMetadataValue( "description", QFileInfo( outputFile ).baseName() );
498 mMbtilesWriter->setMetadataValue( "version", QStringLiteral( "1.1" ) );
499 mMbtilesWriter->setMetadataValue( "type", QStringLiteral( "overlay" ) );
500 mMbtilesWriter->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
501 mMbtilesWriter->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
502 QString boundsStr = QString( "%1,%2,%3,%4" )
503 .arg( mWgs84Extent.xMinimum() )
504 .arg( mWgs84Extent.yMinimum() )
505 .arg( mWgs84Extent.xMaximum() )
506 .arg( mWgs84Extent.yMaximum() );
507 mMbtilesWriter->setMetadataValue( "bounds", boundsStr );
508
509 mTotalTiles = 0;
510 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
511 {
512 if ( feedback->isCanceled() )
513 break;
514
515 mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
516 feedback->pushInfo( QObject::tr( "%1 tiles will be created for zoom level %2" ).arg( mMetaTiles.size() - mTotalTiles ).arg( z ) );
517 mTotalTiles = mMetaTiles.size();
518 }
519 feedback->pushInfo( QObject::tr( "A total of %1 tiles will be created" ).arg( mTotalTiles ) );
520
521 checkLayersUsagePolicy( feedback );
522
523 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
524 {
525 layer->moveToThread( QThread::currentThread() );
526 }
527
528 QEventLoop loop;
529 // cppcheck-suppress danglingLifetime
530 mEventLoop = &loop;
531 startJobs();
532 loop.exec();
533
534 qDeleteAll( mLayers );
535 mLayers.clear();
536
537 QVariantMap results;
538 results.insert( QStringLiteral( "OUTPUT_FILE" ), outputFile );
539 return results;
540}
541
542void QgsXyzTilesMbtilesAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
543{
544 MetaTile metaTile = mRendererJobs.value( job );
545 QImage img = job->renderedImage();
546
547 QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
548 while ( it != metaTile.tiles.constEnd() )
549 {
550 QPair<int, int> tm = it.key();
551 Tile tile = it.value();
552 QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
553 QByteArray ba;
554 QBuffer buffer( &ba );
555 buffer.open( QIODevice::WriteOnly );
556 tileImage.save( &buffer, mTileFormat.toStdString().c_str(), mJpgQuality );
557 mMbtilesWriter->setTileData( tile.z, tile.x, tile2tms( tile.y, tile.z ), ba );
558 ++it;
559 }
560
561 mRendererJobs.remove( job );
562 job->deleteLater();
563
564 mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
565
566 if ( mFeedback->isCanceled() )
567 {
568 while ( mRendererJobs.size() > 0 )
569 {
570 QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
571 j->cancel();
572 mRendererJobs.remove( j );
573 j->deleteLater();
574 }
575 mRendererJobs.clear();
576 if ( mEventLoop )
577 {
578 mEventLoop->exit();
579 }
580 return;
581 }
582
583 if ( mMetaTiles.size() > 0 )
584 {
585 startJobs();
586 }
587 else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
588 {
589 if ( mEventLoop )
590 {
591 mEventLoop->exit();
592 }
593 }
594}
595
@ UsePartialCandidates
Whether to use also label candidates that are partially outside of the map view.
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3410
@ RequiresProject
The algorithm requires that a valid QgsProject is available from the processing context in order to e...
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
@ Antialiasing
Enable anti-aliasing for map rendering.
This class represents a coordinate reference system (CRS).
Class for doing transforms between two map coordinate systems.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
Stores global configuration for labeling engine.
void setFlag(Qgis::LabelingFlag f, bool enabled=true)
Sets whether a particual flag is enabled.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Layer tree node points to a map layer.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
static bool isOpenStreetMapLayer(QgsMapLayer *layer)
Returns true if the layer is served by OpenStreetMap server.
Base class for all map layer types.
Definition qgsmaplayer.h:76
virtual QgsMapLayer * clone() const =0
Returns a new instance equivalent to this one except for the id which is still unique.
void finished()
emitted when asynchronous rendering is finished (or canceled).
void start()
Start the rendering job and immediately return.
Job implementation that renders everything sequentially in one thread.
QImage renderedImage() override
Gets a preview/resulting image.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
The QgsMapSettings class contains configuration for rendering of the map.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
void setOutputImageFormat(QImage::Format format)
sets format of internal QImage
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
const QgsExpressionContext & expressionContext() const
Gets the expression context.
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QgsProject * project() const
Returns the project in which the algorithm is being executed.
int maximumThreads() const
Returns the (optional) number of threads to use when running algorithms.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
virtual void pushFormattedMessage(const QString &html, const QString &text)
Pushes a pre-formatted message from the algorithm.
A boolean parameter for processing algorithms.
A color parameter for processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A rectangular map extent parameter for processing algorithms.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A folder destination parameter, for specifying the destination path for a folder created by the algor...
A numeric parameter for processing algorithms.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition qgsproject.h:112
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH