QGIS API Documentation 4.3.0-Master (7c7bd4d7018)
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
21#include "qgslayertree.h"
22#include "qgslayertreelayer.h"
23#include "qgsmaplayerutils.h"
24#include "qgsprovidermetadata.h"
25
26#include <QBuffer>
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
32
33int tile2tms( const int y, const int zoom )
34{
35 double n = std::pow( 2, zoom );
36 return ( int ) std::floor( n - y - 1 );
37}
38
39int lon2tileX( const double lon, const int z )
40{
41 return ( int ) ( std::floor( ( lon + 180.0 ) / 360.0 * ( 1 << z ) ) );
42}
43
44int lat2tileY( const double lat, const int z )
45{
46 double latRad = lat * M_PI / 180.0;
47 return ( int ) ( std::floor( ( 1.0 - std::asinh( std::tan( latRad ) ) / M_PI ) / 2.0 * ( 1 << z ) ) );
48}
49
50double tileX2lon( const int x, const int z )
51{
52 return x / ( double ) ( 1 << z ) * 360.0 - 180;
53}
54
55double tileY2lat( const int y, const int z )
56{
57 double n = M_PI - 2.0 * M_PI * y / ( double ) ( 1 << z );
58 return 180.0 / M_PI * std::atan( 0.5 * ( std::exp( n ) - std::exp( -n ) ) );
59}
60
61QList<MetaTile> getMetatiles( const QgsRectangle extent, const int zoom, long long &tileCount, const int tileSize )
62{
63 int minX = lon2tileX( extent.xMinimum(), zoom );
64 int minY = lat2tileY( extent.yMaximum(), zoom );
65 int maxX = lon2tileX( extent.xMaximum(), zoom );
66 int maxY = lat2tileY( extent.yMinimum(), zoom );
67 tileCount = static_cast<long long>( maxX - minX + 1 ) * static_cast<long long>( maxY - minY + 1 );
68
69 int i = 0;
70 QMap<QString, MetaTile> tiles;
71 for ( int x = minX; x <= maxX; x++ )
72 {
73 int j = 0;
74 for ( int y = minY; y <= maxY; y++ )
75 {
76 QString key = u"%1:%2"_s.arg( ( int ) ( i / tileSize ) ).arg( ( int ) ( j / tileSize ) );
77 MetaTile tile = tiles.value( key, MetaTile() );
78 tile.addTile( i % tileSize, j % tileSize, Tile( x, y, zoom ) );
79 tiles.insert( key, tile );
80 j++;
81 }
82 i++;
83 }
84 return tiles.values();
85}
86
88
89QString QgsXyzTilesBaseAlgorithm::group() const
90{
91 return QObject::tr( "Raster tools" );
92}
93
94QString QgsXyzTilesBaseAlgorithm::groupId() const
95{
96 return u"rastertools"_s;
97}
98
99Qgis::ProcessingAlgorithmFlags QgsXyzTilesBaseAlgorithm::flags() const
100{
102}
103
104void QgsXyzTilesBaseAlgorithm::createCommonParameters()
105{
106 addParameter( new QgsProcessingParameterExtent( u"EXTENT"_s, QObject::tr( "Extent" ) ) );
107 addParameter( new QgsProcessingParameterNumber( u"ZOOM_MIN"_s, QObject::tr( "Minimum zoom" ), Qgis::ProcessingNumberParameterType::Integer, 12, false, 0, 25 ) );
108 addParameter( new QgsProcessingParameterNumber( u"ZOOM_MAX"_s, QObject::tr( "Maximum zoom" ), Qgis::ProcessingNumberParameterType::Integer, 12, false, 0, 25 ) );
109 addParameter( new QgsProcessingParameterNumber( u"DPI"_s, QObject::tr( "DPI" ), Qgis::ProcessingNumberParameterType::Integer, 96, false, 48, 600 ) );
110 addParameter( new QgsProcessingParameterColor( u"BACKGROUND_COLOR"_s, QObject::tr( "Background color" ), QColor( Qt::transparent ), true, true ) );
111 addParameter( new QgsProcessingParameterBoolean( u"ANTIALIAS"_s, QObject::tr( "Enable antialiasing" ), true ) );
112 addParameter( new QgsProcessingParameterEnum( u"TILE_FORMAT"_s, QObject::tr( "Tile format" ), QStringList() << u"PNG"_s << u"JPG"_s, false, 0 ) );
113 addParameter( new QgsProcessingParameterNumber( u"QUALITY"_s, QObject::tr( "Quality (JPG only)" ), Qgis::ProcessingNumberParameterType::Integer, 75, false, 1, 100 ) );
114 addParameter( new QgsProcessingParameterNumber( u"METATILESIZE"_s, QObject::tr( "Metatile size" ), Qgis::ProcessingNumberParameterType::Integer, 4, false, 1, 20 ) );
115}
116
117bool QgsXyzTilesBaseAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
118{
119 Q_UNUSED( feedback );
120
121 QgsProject *project = context.project();
122
123 const QList<QgsLayerTreeLayer *> projectLayers = project->layerTreeRoot()->findLayers();
124 QSet<QString> visibleLayers;
125 for ( const QgsLayerTreeLayer *layer : projectLayers )
126 {
127 if ( layer->isVisible() )
128 {
129 visibleLayers << layer->layer()->id();
130 }
131 }
132
133 QList<QgsMapLayer *> renderLayers = project->layerTreeRoot()->layerOrder();
134 for ( QgsMapLayer *layer : renderLayers )
135 {
136 if ( visibleLayers.contains( layer->id() ) )
137 {
138 QgsMapLayer *clonedLayer = layer->clone();
139 clonedLayer->moveToThread( nullptr );
140 mLayers << clonedLayer;
141 }
142 }
143
144 QgsRectangle extent = parameterAsExtent( parameters, u"EXTENT"_s, context );
145 QgsCoordinateReferenceSystem extentCrs = parameterAsExtentCrs( parameters, u"EXTENT"_s, context );
146 QgsCoordinateTransform ct( extentCrs, project->crs(), context.transformContext() );
147 try
148 {
149 mExtent = ct.transformBoundingBox( extent );
150 }
151 catch ( QgsCsException & )
152 {
153 feedback->reportError( QObject::tr( "Could not transform the extent into the project CRS" ), true );
154 return false;
155 }
156
157 mMinZoom = parameterAsInt( parameters, u"ZOOM_MIN"_s, context );
158 mMaxZoom = parameterAsInt( parameters, u"ZOOM_MAX"_s, context );
159 mDpi = parameterAsInt( parameters, u"DPI"_s, context );
160 mBackgroundColor = parameterAsColor( parameters, u"BACKGROUND_COLOR"_s, context );
161 mAntialias = parameterAsBool( parameters, u"ANTIALIAS"_s, context );
162 mTileFormat = parameterAsEnum( parameters, u"TILE_FORMAT"_s, context ) ? u"JPG"_s : u"PNG"_s;
163 mJpgQuality = mTileFormat == "JPG"_L1 ? parameterAsInt( parameters, u"QUALITY"_s, context ) : -1;
164 mMetaTileSize = parameterAsInt( parameters, u"METATILESIZE"_s, context );
165 mThreadsNumber = context.maximumThreads();
166 mTransformContext = context.transformContext();
167 mEllipsoid = context.ellipsoid();
168 mFeedback = feedback;
169
170 mWgs84Crs = QgsCoordinateReferenceSystem( "EPSG:4326" );
171 mMercatorCrs = QgsCoordinateReferenceSystem( "EPSG:3857" );
172 mSrc2Wgs = QgsCoordinateTransform( project->crs(), mWgs84Crs, context.transformContext() );
173 mWgs2Mercator = QgsCoordinateTransform( mWgs84Crs, mMercatorCrs, context.transformContext() );
174 try
175 {
176 mWgs84Extent = mSrc2Wgs.transformBoundingBox( mExtent );
177 }
178 catch ( QgsCsException & )
179 {
180 feedback->reportError( QObject::tr( "Could not transform the extent into WGS84" ), true );
181 return false;
182 }
183
184 if ( parameters.contains( u"TILE_WIDTH"_s ) )
185 {
186 mTileWidth = parameterAsInt( parameters, u"TILE_WIDTH"_s, context );
187 }
188
189 if ( parameters.contains( u"TILE_HEIGHT"_s ) )
190 {
191 mTileHeight = parameterAsInt( parameters, u"TILE_HEIGHT"_s, context );
192 }
193
194 if ( mTileFormat != "PNG"_L1 && mBackgroundColor.alpha() != 255 )
195 {
196 feedback->pushWarning( QObject::tr( "Background color setting ignored, the JPG format only supports fully opaque colors" ) );
197 }
198
199 mScaleMethod = project->scaleMethod();
200
201 return true;
202}
203
204void QgsXyzTilesBaseAlgorithm::checkLayersUsagePolicy( QgsProcessingFeedback *feedback )
205{
206 if ( mTotalMetaTiles > MAXIMUM_OPENSTREETMAP_TILES_FETCH )
207 {
208 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
209 {
211 {
212 // Prevent bulk downloading of tiles from openstreetmap.org as per OSMF tile usage policy
213 feedback->pushFormattedMessage(
214 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" )
215 .arg( layer->name(), u"<a href=\"https://operations.osmfoundation.org/policies/tiles/\">"_s, u"</a>"_s ),
216 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" )
217 .arg( layer->name(), QString(), QString() )
218 );
219 mLayers.removeAll( layer );
220 delete layer;
221 }
222 }
223 }
224}
225
226void QgsXyzTilesBaseAlgorithm::startJobs()
227{
228 while ( mRendererJobs.size() < mThreadsNumber && !mMetaTiles.empty() )
229 {
230 MetaTile metaTile = mMetaTiles.takeFirst();
231
232 QgsMapSettings settings;
233 try
234 {
235 settings.setExtent( mWgs2Mercator.transformBoundingBox( metaTile.extent() ) );
236 }
237 catch ( QgsCsException & )
238 {
239 continue;
240 }
242 settings.setOutputImageFormat( QImage::Format_ARGB32_Premultiplied );
243 settings.setTransformContext( mTransformContext );
244 settings.setEllipsoid( mEllipsoid );
245 settings.setDestinationCrs( mMercatorCrs );
246 settings.setLayers( mLayers );
247 settings.setOutputDpi( mDpi );
248 settings.setFlag( Qgis::MapSettingsFlag::Antialiasing, mAntialias );
249 settings.setScaleMethod( mScaleMethod );
250 if ( mTileFormat == "PNG"_L1 || mBackgroundColor.alpha() == 255 )
251 {
252 settings.setBackgroundColor( mBackgroundColor );
253 }
254 QSize size( mTileWidth * metaTile.rows, mTileHeight * metaTile.cols );
255 settings.setOutputSize( size );
256
257 QgsLabelingEngineSettings labelingSettings = settings.labelingEngineSettings();
258 labelingSettings.setFlag( Qgis::LabelingFlag::UsePartialCandidates, false );
259 settings.setLabelingEngineSettings( labelingSettings );
260
261 QgsExpressionContext exprContext = settings.expressionContext();
263 settings.setExpressionContext( exprContext );
264
266 mRendererJobs.insert( job, metaTile );
267 QObject::connect( job, &QgsMapRendererJob::finished, mFeedback, [this, job]() { processMetaTile( job ); } );
268 job->start();
269 }
270}
271
272// Native XYZ tiles (directory) algorithm
273
274QString QgsXyzTilesDirectoryAlgorithm::name() const
275{
276 return u"tilesxyzdirectory"_s;
277}
278
279QString QgsXyzTilesDirectoryAlgorithm::displayName() const
280{
281 return QObject::tr( "Generate XYZ tiles (Directory)" );
282}
283
284QStringList QgsXyzTilesDirectoryAlgorithm::tags() const
285{
286 return QObject::tr( "tiles,xyz,tms,directory" ).split( ',' );
287}
288
289QString QgsXyzTilesDirectoryAlgorithm::shortHelpString() const
290{
291 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as individual images in a directory." );
292}
293
294QgsXyzTilesDirectoryAlgorithm *QgsXyzTilesDirectoryAlgorithm::createInstance() const
295{
296 return new QgsXyzTilesDirectoryAlgorithm();
297}
298
299void QgsXyzTilesDirectoryAlgorithm::initAlgorithm( const QVariantMap & )
300{
301 createCommonParameters();
302 addParameter( new QgsProcessingParameterNumber( u"TILE_WIDTH"_s, QObject::tr( "Tile width" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
303 addParameter( new QgsProcessingParameterNumber( u"TILE_HEIGHT"_s, QObject::tr( "Tile height" ), Qgis::ProcessingNumberParameterType::Integer, 256, false, 1, 4096 ) );
304 addParameter( new QgsProcessingParameterBoolean( u"TMS_CONVENTION"_s, QObject::tr( "Use inverted tile Y axis (TMS convention)" ), false ) );
305
306 auto titleParam = std::make_unique<QgsProcessingParameterString>( u"HTML_TITLE"_s, QObject::tr( "Leaflet HTML output title" ), QVariant(), false, true );
307 titleParam->setFlags( titleParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
308 addParameter( titleParam.release() );
309 auto attributionParam = std::make_unique<QgsProcessingParameterString>( u"HTML_ATTRIBUTION"_s, QObject::tr( "Leaflet HTML output attribution" ), QVariant(), false, true );
310 attributionParam->setFlags( attributionParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
311 addParameter( attributionParam.release() );
312 auto osmParam = std::make_unique<QgsProcessingParameterBoolean>( u"HTML_OSM"_s, QObject::tr( "Include OpenStreetMap basemap in Leaflet HTML output" ), false );
313 osmParam->setFlags( osmParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
314 addParameter( osmParam.release() );
315
316 addParameter( new QgsProcessingParameterFolderDestination( u"OUTPUT_DIRECTORY"_s, QObject::tr( "Output directory" ) ) );
317 addParameter( new QgsProcessingParameterFileDestination( u"OUTPUT_HTML"_s, QObject::tr( "Output html (Leaflet)" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
318}
319
320QVariantMap QgsXyzTilesDirectoryAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
321{
322 const bool tms = parameterAsBoolean( parameters, u"TMS_CONVENTION"_s, context );
323 const QString title = parameterAsString( parameters, u"HTML_TITLE"_s, context );
324 const QString attribution = parameterAsString( parameters, u"HTML_ATTRIBUTION"_s, context );
325 const bool useOsm = parameterAsBoolean( parameters, u"HTML_OSM"_s, context );
326 QString outputDir = parameterAsString( parameters, u"OUTPUT_DIRECTORY"_s, context );
327 const QString outputHtml = parameterAsString( parameters, u"OUTPUT_HTML"_s, context );
328
329 mOutputDir = outputDir;
330 mTms = tms;
331
332 long long totalTiles = 0;
333 mTotalMetaTiles = 0;
334 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
335 {
336 if ( feedback->isCanceled() )
337 break;
338
339 long long tileCount = 0;
340 mMetaTiles += getMetatiles( mWgs84Extent, z, tileCount, mMetaTileSize );
341 feedback->pushInfo( QObject::tr( "%1 metatiles (%2 tiles) will be created for zoom level %3" ).arg( mMetaTiles.size() - mTotalMetaTiles ).arg( tileCount ).arg( z ) );
342 mTotalMetaTiles = mMetaTiles.size();
343 totalTiles += tileCount;
344 }
345 feedback->pushInfo( QObject::tr( "A total of %1 metatiles (%2 tiles) will be created" ).arg( mTotalMetaTiles ).arg( totalTiles ) );
346
347 checkLayersUsagePolicy( feedback );
348
349 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
350 {
351 layer->moveToThread( QThread::currentThread() );
352 }
353
354 QEventLoop loop;
355 // cppcheck-suppress danglingLifetime
356 mEventLoop = &loop;
357 startJobs();
358 loop.exec();
359
360 qDeleteAll( mLayers );
361 mLayers.clear();
362
363 QVariantMap results;
364 results.insert( u"OUTPUT_DIRECTORY"_s, outputDir );
365
366 if ( !outputHtml.isEmpty() )
367 {
368 QString osm = QStringLiteral(
369 "var osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',"
370 "{minZoom: %1, maxZoom: %2, attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);"
371 )
372 .arg( mMinZoom )
373 .arg( mMaxZoom );
374
375 QString addOsm = useOsm ? osm : QString();
376 QString tmsConvention = tms ? u"true"_s : u"false"_s;
377 QString attr = attribution.isEmpty() ? u"Created by QGIS"_s : attribution;
378 QString tileSource = u"'file:///%1/{z}/{x}/{y}.%2'"_s.arg( outputDir.replace( "\\", "/" ).toHtmlEscaped() ).arg( mTileFormat.toLower() );
379
380 QString html = QStringLiteral(
381 "<!DOCTYPE html><html><head><title>%1</title><meta charset=\"utf-8\"/>"
382 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
383 "<link rel=\"stylesheet\" href=\"https://unpkg.com/[email protected]/dist/leaflet.css\""
384 "integrity=\"sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H\""
385 "crossorigin=\"\"/>"
386 "<script src=\"https://unpkg.com/[email protected]/dist/leaflet.js\""
387 "integrity=\"sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH\""
388 "crossorigin=\"\"></script>"
389 "<style type=\"text/css\">body {margin: 0;padding: 0;} html, body, #map{width: 100%;height: 100%;}</style></head>"
390 "<body><div id=\"map\"></div><script>"
391 "var map = L.map('map', {attributionControl: false}).setView([%2, %3], %4);"
392 "L.control.attribution({prefix: false}).addTo(map);"
393 "%5"
394 "var tilesource_layer = L.tileLayer(%6, {minZoom: %7, maxZoom: %8, tms: %9, attribution: '%10'}).addTo(map);"
395 "</script></body></html>"
396 )
397 .arg( title.isEmpty() ? u"Leaflet preview"_s : title )
398 .arg( mWgs84Extent.center().y() )
399 .arg( mWgs84Extent.center().x() )
400 .arg( ( mMaxZoom + mMinZoom ) / 2 )
401 .arg( addOsm )
402 .arg( tileSource )
403 .arg( mMinZoom )
404 .arg( mMaxZoom )
405 .arg( tmsConvention )
406 .arg( attr );
407
408 QFile htmlFile( outputHtml );
409 if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
410 {
411 throw QgsProcessingException( QObject::tr( "Could not open html file %1" ).arg( outputHtml ) );
412 }
413 QTextStream fout( &htmlFile );
414 fout << html;
415
416 results.insert( u"OUTPUT_HTML"_s, outputHtml );
417 }
418
419 return results;
420}
421
422void QgsXyzTilesDirectoryAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
423{
424 MetaTile metaTile = mRendererJobs.value( job );
425 QImage img = job->renderedImage();
426
427 QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
428 while ( it != metaTile.tiles.constEnd() )
429 {
430 QPair<int, int> tm = it.key();
431 Tile tile = it.value();
432 QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
433 QDir tileDir( u"%1/%2/%3"_s.arg( mOutputDir ).arg( tile.z ).arg( tile.x ) );
434 tileDir.mkpath( tileDir.absolutePath() );
435 int y = tile.y;
436 if ( mTms )
437 {
438 y = tile2tms( y, tile.z );
439 }
440 tileImage.save( u"%1/%2.%3"_s.arg( tileDir.absolutePath() ).arg( y ).arg( mTileFormat.toLower() ), mTileFormat.toStdString().c_str(), mJpgQuality );
441 ++it;
442 }
443
444 mRendererJobs.remove( job );
445 job->deleteLater();
446
447 mFeedback->setProgress( 100.0 * ( mProcessedMetaTiles++ ) / mTotalMetaTiles );
448
449 if ( mFeedback->isCanceled() )
450 {
451 while ( mRendererJobs.size() > 0 )
452 {
453 QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
454 j->cancel();
455 mRendererJobs.remove( j );
456 j->deleteLater();
457 }
458 mRendererJobs.clear();
459 if ( mEventLoop )
460 {
461 mEventLoop->exit();
462 }
463 return;
464 }
465
466 if ( mMetaTiles.size() > 0 )
467 {
468 startJobs();
469 }
470 else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
471 {
472 if ( mEventLoop )
473 {
474 mEventLoop->exit();
475 }
476 }
477}
478
479// Native XYZ tiles (MBTiles) algorithm
480
481QString QgsXyzTilesMbtilesAlgorithm::name() const
482{
483 return u"tilesxyzmbtiles"_s;
484}
485
486QString QgsXyzTilesMbtilesAlgorithm::displayName() const
487{
488 return QObject::tr( "Generate XYZ tiles (MBTiles)" );
489}
490
491QStringList QgsXyzTilesMbtilesAlgorithm::tags() const
492{
493 return QObject::tr( "tiles,xyz,tms,mbtiles" ).split( ',' );
494}
495
496QString QgsXyzTilesMbtilesAlgorithm::shortHelpString() const
497{
498 return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as an MBTiles file." );
499}
500
501QgsXyzTilesMbtilesAlgorithm *QgsXyzTilesMbtilesAlgorithm::createInstance() const
502{
503 return new QgsXyzTilesMbtilesAlgorithm();
504}
505
506void QgsXyzTilesMbtilesAlgorithm::initAlgorithm( const QVariantMap & )
507{
508 createCommonParameters();
509 addParameter( new QgsProcessingParameterFileDestination( u"OUTPUT_FILE"_s, QObject::tr( "Output" ), QObject::tr( "MBTiles files (*.mbtiles *.MBTILES)" ) ) );
510}
511
512QVariantMap QgsXyzTilesMbtilesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
513{
514 const QString outputFile = parameterAsString( parameters, u"OUTPUT_FILE"_s, context );
515
516 mMbtilesWriter = std::make_unique<QgsMbTiles>( outputFile );
517 if ( !mMbtilesWriter->create() )
518 {
519 throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
520 }
521 mMbtilesWriter->setMetadataValue( "format", mTileFormat.toLower() );
522 mMbtilesWriter->setMetadataValue( "name", QFileInfo( outputFile ).baseName() );
523 mMbtilesWriter->setMetadataValue( "description", QFileInfo( outputFile ).baseName() );
524 mMbtilesWriter->setMetadataValue( "version", u"1.1"_s );
525 mMbtilesWriter->setMetadataValue( "type", u"overlay"_s );
526 mMbtilesWriter->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
527 mMbtilesWriter->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
528 QString boundsStr = QString( "%1,%2,%3,%4" ).arg( mWgs84Extent.xMinimum() ).arg( mWgs84Extent.yMinimum() ).arg( mWgs84Extent.xMaximum() ).arg( mWgs84Extent.yMaximum() );
529 mMbtilesWriter->setMetadataValue( "bounds", boundsStr );
530
531 long long totalTiles = 0;
532 mTotalMetaTiles = 0;
533 for ( int z = mMinZoom; z <= mMaxZoom; z++ )
534 {
535 if ( feedback->isCanceled() )
536 break;
537
538 long long tileCount = 0;
539 mMetaTiles += getMetatiles( mWgs84Extent, z, tileCount, mMetaTileSize );
540 feedback->pushInfo( QObject::tr( "%1 metatiles (%2 tiles) will be created for zoom level %3" ).arg( mMetaTiles.size() - mTotalMetaTiles ).arg( tileCount ).arg( z ) );
541 mTotalMetaTiles = mMetaTiles.size();
542 totalTiles += tileCount;
543 }
544 feedback->pushInfo( QObject::tr( "A total of %1 metatiles (%2 tiles) will be created" ).arg( mTotalMetaTiles ).arg( totalTiles ) );
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( u"OUTPUT_FILE"_s, 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 * ( mProcessedMetaTiles++ ) / mTotalMetaTiles );
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.
Definition qgis.h:3012
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3792
@ Export
Renderer used for printing or exporting to a file.
Definition qgis.h:3629
@ RequiresProject
The algorithm requires that a valid QgsProject is available from the processing context in order to e...
Definition qgis.h:3779
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3948
@ Antialiasing
Enable anti-aliasing for map rendering.
Definition qgis.h:2880
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:56
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:83
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.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
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 setRendererUsage(Qgis::RendererUsage rendererUsage)
Sets the rendering usage.
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.
QString ellipsoid() const
Returns the ellipsoid to use for distance and area calculations.
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:114
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition qgsproject.h:120
Qgis::ScaleCalculationMethod scaleMethod
Definition qgsproject.h:136
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH