QGIS API Documentation 3.43.0-Master (b60ef06885e)
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 try
153 {
154 mExtent = ct.transformBoundingBox( extent );
155 }
156 catch ( QgsCsException & )
157 {
158 feedback->reportError( QObject::tr( "Could not transform the extent into the project CRS" ), true );
159 return false;
160 }
161
162 mMinZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MIN" ), context );
163 mMaxZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MAX" ), context );
164 mDpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context );
165 mBackgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context );
166 mAntialias = parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context );
167 mTileFormat = parameterAsEnum( parameters, QStringLiteral( "TILE_FORMAT" ), context ) ? QStringLiteral( "JPG" ) : QStringLiteral( "PNG" );
168 mJpgQuality = mTileFormat == QLatin1String( "JPG" ) ? parameterAsInt( parameters, QStringLiteral( "QUALITY" ), context ) : -1;
169 mMetaTileSize = parameterAsInt( parameters, QStringLiteral( "METATILESIZE" ), context );
170 mThreadsNumber = context.maximumThreads();
171 mTransformContext = context.transformContext();
172 mFeedback = feedback;
173
174 mWgs84Crs = QgsCoordinateReferenceSystem( "EPSG:4326" );
175 mMercatorCrs = QgsCoordinateReferenceSystem( "EPSG:3857" );
176 mSrc2Wgs = QgsCoordinateTransform( project->crs(), mWgs84Crs, context.transformContext() );
177 mWgs2Mercator = QgsCoordinateTransform( mWgs84Crs, mMercatorCrs, context.transformContext() );
178 try
179 {
180 mWgs84Extent = mSrc2Wgs.transformBoundingBox( mExtent );
181 }
182 catch ( QgsCsException & )
183 {
184 feedback->reportError( QObject::tr( "Could not transform the extent into WGS84" ), true );
185 return false;
186 }
187
188 if ( parameters.contains( QStringLiteral( "TILE_WIDTH" ) ) )
189 {
190 mTileWidth = parameterAsInt( parameters, QStringLiteral( "TILE_WIDTH" ), context );
191 }
192
193 if ( parameters.contains( QStringLiteral( "TILE_HEIGHT" ) ) )
194 {
195 mTileHeight = parameterAsInt( parameters, QStringLiteral( "TILE_HEIGHT" ), context );
196 }
197
198 if ( mTileFormat != QLatin1String( "PNG" ) && mBackgroundColor.alpha() != 255 )
199 {
200 feedback->pushWarning( QObject::tr( "Background color setting ignored, the JPG format only supports fully opaque colors" ) );
201 }
202
203 mScaleMethod = project->scaleMethod();
204
205 return true;
206}
207
208void QgsXyzTilesBaseAlgorithm::checkLayersUsagePolicy( QgsProcessingFeedback *feedback )
209{
210 if ( mTotalTiles > MAXIMUM_OPENSTREETMAP_TILES_FETCH )
211 {
212 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
213 {
215 {
216 // Prevent bulk downloading of tiles from openstreetmap.org as per OSMF tile usage policy
217 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() ) );
218 mLayers.removeAll( layer );
219 delete layer;
220 }
221 }
222 }
223}
224
225void QgsXyzTilesBaseAlgorithm::startJobs()
226{
227 while ( mRendererJobs.size() < mThreadsNumber && !mMetaTiles.empty() )
228 {
229 MetaTile metaTile = mMetaTiles.takeFirst();
230
231 QgsMapSettings settings;
232 try
233 {
234 settings.setExtent( mWgs2Mercator.transformBoundingBox( metaTile.extent() ) );
235 }
236 catch ( QgsCsException & )
237 {
238 continue;
239 }
240 settings.setOutputImageFormat( QImage::Format_ARGB32_Premultiplied );
241 settings.setTransformContext( mTransformContext );
242 settings.setDestinationCrs( mMercatorCrs );
243 settings.setLayers( mLayers );
244 settings.setOutputDpi( mDpi );
245 settings.setFlag( Qgis::MapSettingsFlag::Antialiasing, mAntialias );
246 settings.setScaleMethod( mScaleMethod );
247 if ( mTileFormat == QLatin1String( "PNG" ) || mBackgroundColor.alpha() == 255 )
248 {
249 settings.setBackgroundColor( mBackgroundColor );
250 }
251 QSize size( mTileWidth * metaTile.rows, mTileHeight * metaTile.cols );
252 settings.setOutputSize( size );
253
254 QgsLabelingEngineSettings labelingSettings = settings.labelingEngineSettings();
255 labelingSettings.setFlag( Qgis::LabelingFlag::UsePartialCandidates, false );
256 settings.setLabelingEngineSettings( labelingSettings );
257
258 QgsExpressionContext exprContext = settings.expressionContext();
260 settings.setExpressionContext( exprContext );
261
263 mRendererJobs.insert( job, metaTile );
264 QObject::connect( job, &QgsMapRendererJob::finished, mFeedback, [this, job]() { processMetaTile( job ); } );
265 job->start();
266 }
267}
268
269// Native XYZ tiles (directory) algorithm
270
271QString QgsXyzTilesDirectoryAlgorithm::name() const
272{
273 return QStringLiteral( "tilesxyzdirectory" );
274}
275
276QString QgsXyzTilesDirectoryAlgorithm::displayName() const
277{
278 return QObject::tr( "Generate XYZ tiles (Directory)" );
279}
280
281QStringList QgsXyzTilesDirectoryAlgorithm::tags() const
282{
283 return QObject::tr( "tiles,xyz,tms,directory" ).split( ',' );
284}
285
286QString QgsXyzTilesDirectoryAlgorithm::shortHelpString() const
287{
288 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as individual images in a directory." );
289}
290
291QgsXyzTilesDirectoryAlgorithm *QgsXyzTilesDirectoryAlgorithm::createInstance() const
292{
293 return new QgsXyzTilesDirectoryAlgorithm();
294}
295
296void QgsXyzTilesDirectoryAlgorithm::initAlgorithm( const QVariantMap & )
297{
298 createCommonParameters();
299 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_WIDTH" ), QObject::tr( "Tile width" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
300 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_HEIGHT" ), QObject::tr( "Tile height" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
301 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "TMS_CONVENTION" ), QObject::tr( "Use inverted tile Y axis (TMS convention)" ), false ) );
302
303 auto titleParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "HTML_TITLE" ), QObject::tr( "Leaflet HTML output title" ), QVariant(), false, true );
304 titleParam->setFlags( titleParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
305 addParameter( titleParam.release() );
306 auto attributionParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "HTML_ATTRIBUTION" ), QObject::tr( "Leaflet HTML output attribution" ), QVariant(), false, true );
307 attributionParam->setFlags( attributionParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
308 addParameter( attributionParam.release() );
309 auto osmParam = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral( "HTML_OSM" ), QObject::tr( "Include OpenStreetMap basemap in Leaflet HTML output" ), false );
310 osmParam->setFlags( osmParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
311 addParameter( osmParam.release() );
312
313 addParameter( new QgsProcessingParameterFolderDestination( QStringLiteral( "OUTPUT_DIRECTORY" ), QObject::tr( "Output directory" ) ) );
314 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML" ), QObject::tr( "Output html (Leaflet)" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
315}
316
317QVariantMap QgsXyzTilesDirectoryAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
318{
319 const bool tms = parameterAsBoolean( parameters, QStringLiteral( "TMS_CONVENTION" ), context );
320 const QString title = parameterAsString( parameters, QStringLiteral( "HTML_TITLE" ), context );
321 const QString attribution = parameterAsString( parameters, QStringLiteral( "HTML_ATTRIBUTION" ), context );
322 const bool useOsm = parameterAsBoolean( parameters, QStringLiteral( "HTML_OSM" ), context );
323 QString outputDir = parameterAsString( parameters, QStringLiteral( "OUTPUT_DIRECTORY" ), context );
324 const QString outputHtml = parameterAsString( parameters, QStringLiteral( "OUTPUT_HTML" ), context );
325
326 mOutputDir = outputDir;
327 mTms = tms;
328
329 mTotalTiles = 0;
330 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
331 {
332 if ( feedback->isCanceled() )
333 break;
334
335 mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
336 feedback->pushWarning( QObject::tr( "%1 tiles will be created for zoom level %2" ).arg( mMetaTiles.size() - mTotalTiles ).arg( z ) );
337 mTotalTiles = mMetaTiles.size();
338 }
339 feedback->pushWarning( QObject::tr( "A total of %1 tiles will be created" ).arg( mTotalTiles ) );
340
341 checkLayersUsagePolicy( feedback );
342
343 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
344 {
345 layer->moveToThread( QThread::currentThread() );
346 }
347
348 QEventLoop loop;
349 // cppcheck-suppress danglingLifetime
350 mEventLoop = &loop;
351 startJobs();
352 loop.exec();
353
354 qDeleteAll( mLayers );
355 mLayers.clear();
356
357 QVariantMap results;
358 results.insert( QStringLiteral( "OUTPUT_DIRECTORY" ), outputDir );
359
360 if ( !outputHtml.isEmpty() )
361 {
362 QString osm = QStringLiteral(
363 "var osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',"
364 "{minZoom: %1, maxZoom: %2, attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);"
365 )
366 .arg( mMinZoom )
367 .arg( mMaxZoom );
368
369 QString addOsm = useOsm ? osm : QString();
370 QString tmsConvention = tms ? QStringLiteral( "true" ) : QStringLiteral( "false" );
371 QString attr = attribution.isEmpty() ? QStringLiteral( "Created by QGIS" ) : attribution;
372 QString tileSource = QStringLiteral( "'file:///%1/{z}/{x}/{y}.%2'" )
373 .arg( outputDir.replace( "\\", "/" ).toHtmlEscaped() )
374 .arg( mTileFormat.toLower() );
375
376 QString html = QStringLiteral(
377 "<!DOCTYPE html><html><head><title>%1</title><meta charset=\"utf-8\"/>"
378 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
379 "<link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\""
380 "integrity=\"sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H\""
381 "crossorigin=\"\"/>"
382 "<script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\""
383 "integrity=\"sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH\""
384 "crossorigin=\"\"></script>"
385 "<style type=\"text/css\">body {margin: 0;padding: 0;} html, body, #map{width: 100%;height: 100%;}</style></head>"
386 "<body><div id=\"map\"></div><script>"
387 "var map = L.map('map', {attributionControl: false}).setView([%2, %3], %4);"
388 "L.control.attribution({prefix: false}).addTo(map);"
389 "%5"
390 "var tilesource_layer = L.tileLayer(%6, {minZoom: %7, maxZoom: %8, tms: %9, attribution: '%10'}).addTo(map);"
391 "</script></body></html>"
392 )
393 .arg( title.isEmpty() ? QStringLiteral( "Leaflet preview" ) : title )
394 .arg( mWgs84Extent.center().y() )
395 .arg( mWgs84Extent.center().x() )
396 .arg( ( mMaxZoom + mMinZoom ) / 2 )
397 .arg( addOsm )
398 .arg( tileSource )
399 .arg( mMinZoom )
400 .arg( mMaxZoom )
401 .arg( tmsConvention )
402 .arg( attr );
403
404 QFile htmlFile( outputHtml );
405 if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
406 {
407 throw QgsProcessingException( QObject::tr( "Could not open html file %1" ).arg( outputHtml ) );
408 }
409 QTextStream fout( &htmlFile );
410#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
411 fout.setCodec( "UTF-8" );
412#endif
413 fout << html;
414
415 results.insert( QStringLiteral( "OUTPUT_HTML" ), outputHtml );
416 }
417
418 return results;
419}
420
421void QgsXyzTilesDirectoryAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
422{
423 MetaTile metaTile = mRendererJobs.value( job );
424 QImage img = job->renderedImage();
425
426 QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
427 while ( it != metaTile.tiles.constEnd() )
428 {
429 QPair<int, int> tm = it.key();
430 Tile tile = it.value();
431 QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
432 QDir tileDir( QStringLiteral( "%1/%2/%3" ).arg( mOutputDir ).arg( tile.z ).arg( tile.x ) );
433 tileDir.mkpath( tileDir.absolutePath() );
434 int y = tile.y;
435 if ( mTms )
436 {
437 y = tile2tms( y, tile.z );
438 }
439 tileImage.save( QStringLiteral( "%1/%2.%3" ).arg( tileDir.absolutePath() ).arg( y ).arg( mTileFormat.toLower() ), mTileFormat.toStdString().c_str(), mJpgQuality );
440 ++it;
441 }
442
443 mRendererJobs.remove( job );
444 job->deleteLater();
445
446 mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
447
448 if ( mFeedback->isCanceled() )
449 {
450 while ( mRendererJobs.size() > 0 )
451 {
452 QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
453 j->cancel();
454 mRendererJobs.remove( j );
455 j->deleteLater();
456 }
457 mRendererJobs.clear();
458 if ( mEventLoop )
459 {
460 mEventLoop->exit();
461 }
462 return;
463 }
464
465 if ( mMetaTiles.size() > 0 )
466 {
467 startJobs();
468 }
469 else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
470 {
471 if ( mEventLoop )
472 {
473 mEventLoop->exit();
474 }
475 }
476}
477
478// Native XYZ tiles (MBTiles) algorithm
479
480QString QgsXyzTilesMbtilesAlgorithm::name() const
481{
482 return QStringLiteral( "tilesxyzmbtiles" );
483}
484
485QString QgsXyzTilesMbtilesAlgorithm::displayName() const
486{
487 return QObject::tr( "Generate XYZ tiles (MBTiles)" );
488}
489
490QStringList QgsXyzTilesMbtilesAlgorithm::tags() const
491{
492 return QObject::tr( "tiles,xyz,tms,mbtiles" ).split( ',' );
493}
494
495QString QgsXyzTilesMbtilesAlgorithm::shortHelpString() const
496{
497 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as an MBTiles file." );
498}
499
500QgsXyzTilesMbtilesAlgorithm *QgsXyzTilesMbtilesAlgorithm::createInstance() const
501{
502 return new QgsXyzTilesMbtilesAlgorithm();
503}
504
505void QgsXyzTilesMbtilesAlgorithm::initAlgorithm( const QVariantMap & )
506{
507 createCommonParameters();
508 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_FILE" ), QObject::tr( "Output" ), QObject::tr( "MBTiles files (*.mbtiles *.MBTILES)" ) ) );
509}
510
511QVariantMap QgsXyzTilesMbtilesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
512{
513 const QString outputFile = parameterAsString( parameters, QStringLiteral( "OUTPUT_FILE" ), context );
514
515 mMbtilesWriter = std::make_unique<QgsMbTiles>( outputFile );
516 if ( !mMbtilesWriter->create() )
517 {
518 throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
519 }
520 mMbtilesWriter->setMetadataValue( "format", mTileFormat.toLower() );
521 mMbtilesWriter->setMetadataValue( "name", QFileInfo( outputFile ).baseName() );
522 mMbtilesWriter->setMetadataValue( "description", QFileInfo( outputFile ).baseName() );
523 mMbtilesWriter->setMetadataValue( "version", QStringLiteral( "1.1" ) );
524 mMbtilesWriter->setMetadataValue( "type", QStringLiteral( "overlay" ) );
525 mMbtilesWriter->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
526 mMbtilesWriter->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
527 QString boundsStr = QString( "%1,%2,%3,%4" )
528 .arg( mWgs84Extent.xMinimum() )
529 .arg( mWgs84Extent.yMinimum() )
530 .arg( mWgs84Extent.xMaximum() )
531 .arg( mWgs84Extent.yMaximum() );
532 mMbtilesWriter->setMetadataValue( "bounds", boundsStr );
533
534 mTotalTiles = 0;
535 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
536 {
537 if ( feedback->isCanceled() )
538 break;
539
540 mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
541 feedback->pushInfo( QObject::tr( "%1 tiles will be created for zoom level %2" ).arg( mMetaTiles.size() - mTotalTiles ).arg( z ) );
542 mTotalTiles = mMetaTiles.size();
543 }
544 feedback->pushInfo( QObject::tr( "A total of %1 tiles will be created" ).arg( mTotalTiles ) );
545
546 checkLayersUsagePolicy( feedback );
547
548 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
549 {
550 layer->moveToThread( QThread::currentThread() );
551 }
552
553 QEventLoop loop;
554 // cppcheck-suppress danglingLifetime
555 mEventLoop = &loop;
556 startJobs();
557 loop.exec();
558
559 qDeleteAll( mLayers );
560 mLayers.clear();
561
562 QVariantMap results;
563 results.insert( QStringLiteral( "OUTPUT_FILE" ), outputFile );
564 return results;
565}
566
567void QgsXyzTilesMbtilesAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
568{
569 MetaTile metaTile = mRendererJobs.value( job );
570 QImage img = job->renderedImage();
571
572 QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
573 while ( it != metaTile.tiles.constEnd() )
574 {
575 QPair<int, int> tm = it.key();
576 Tile tile = it.value();
577 QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
578 QByteArray ba;
579 QBuffer buffer( &ba );
580 buffer.open( QIODevice::WriteOnly );
581 tileImage.save( &buffer, mTileFormat.toStdString().c_str(), mJpgQuality );
582 mMbtilesWriter->setTileData( tile.z, tile.x, tile2tms( tile.y, tile.z ), ba );
583 ++it;
584 }
585
586 mRendererJobs.remove( job );
587 job->deleteLater();
588
589 mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
590
591 if ( mFeedback->isCanceled() )
592 {
593 while ( mRendererJobs.size() > 0 )
594 {
595 QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
596 j->cancel();
597 mRendererJobs.remove( j );
598 j->deleteLater();
599 }
600 mRendererJobs.clear();
601 if ( mEventLoop )
602 {
603 mEventLoop->exit();
604 }
605 return;
606 }
607
608 if ( mMetaTiles.size() > 0 )
609 {
610 startJobs();
611 }
612 else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
613 {
614 if ( mEventLoop )
615 {
616 mEventLoop->exit();
617 }
618 }
619}
620
@ 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:3476
@ 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.
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
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:77
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.
Contains configuration for rendering maps.
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 setScaleMethod(Qgis::ScaleCalculationMethod method)
Sets the method to use for scale calculations for 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.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
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
Qgis::ScaleCalculationMethod scaleMethod
Definition qgsproject.h:128
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH