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