37#include <QWaitCondition>
39using namespace Qt::StringLiterals;
46 int tile2tms(
const int y,
const int zoom )
48 double n = std::pow( 2, zoom );
49 return (
int ) std::floor( n - y - 1 );
52 int lon2tileX(
const double lon,
const int z )
54 return (
int ) ( std::floor( ( lon + 180.0 ) / 360.0 * ( 1 << z ) ) );
57 int lat2tileY(
const double lat,
const int z )
59 double latRad = lat * M_PI / 180.0;
60 return (
int ) ( std::floor( ( 1.0 - std::asinh( std::tan( latRad ) ) / M_PI ) / 2.0 * ( 1 << z ) ) );
63 double tileX2lon(
const int x,
const int z )
65 return x / ( double ) ( 1 << z ) * 360.0 - 180;
68 double tileY2lat(
const int y,
const int z )
70 double n = M_PI - 2.0 * M_PI * y / ( double ) ( 1 << z );
71 return 180.0 / M_PI * std::atan( 0.5 * ( std::exp( n ) - std::exp( -n ) ) );
76void MetaTile::addTile(
const int row,
const int col, Tile tileToAdd )
78 tiles.insert( QPair<int, int>( row, col ), tileToAdd );
91 const Tile first = tiles.first();
92 const Tile last = tiles.last();
93 return QgsRectangle( tileX2lon( first.x, first.z ), tileY2lat( last.y + 1, last.z ), tileX2lon( last.x + 1, last.z ), tileY2lat( first.y, first.z ) );
99 QList<MetaTile> getMetatiles(
const QgsRectangle extent,
const int zoom,
long long &tileCount,
const int tileSize )
101 int minX = lon2tileX( extent.
xMinimum(), zoom );
102 int minY = lat2tileY( extent.
yMaximum(), zoom );
103 int maxX = lon2tileX( extent.
xMaximum(), zoom );
104 int maxY = lat2tileY( extent.
yMinimum(), zoom );
105 tileCount =
static_cast<long long>( maxX - minX + 1 ) *
static_cast<long long>( maxY - minY + 1 );
107 QHash<uint64_t, MetaTile> tiles;
109 for (
int x = minX; x <= maxX; x++ )
112 for (
int y = minY; y <= maxY; y++ )
114 const uint64_t key = (
static_cast<uint64_t
>( i / tileSize ) << 32 ) |
static_cast<uint32_t
>( j / tileSize );
115 tiles[key].addTile( i % tileSize, j % tileSize, Tile( x, y, zoom ) );
120 return tiles.values();
133class PendingTilesToWriteQueue
139 void push(
const QList<QgsMbTiles::TileData> &tiles )
141 if ( tiles.isEmpty() )
144 QMutexLocker locker( &mMutex );
145 for (
const QgsMbTiles::TileData &tile : tiles )
147 mQueue.enqueue( tile );
159 bool popBatch( QList<QgsMbTiles::TileData> &batch,
int maxBatchSize,
unsigned long timeoutMs = 500 )
161 QMutexLocker locker( &mMutex );
164 while ( mQueue.isEmpty() && !mFinished )
166 mNotEmpty.wait( &mMutex );
169 if ( mQueue.isEmpty() )
173 while ( mQueue.size() < maxBatchSize && !mFinished )
175 if ( !mNotEmpty.wait( &mMutex, timeoutMs ) )
182 while ( !mQueue.isEmpty() && batch.size() < maxBatchSize )
184 batch.append( mQueue.dequeue() );
194 QMutexLocker locker( &mMutex );
200 QQueue<QgsMbTiles::TileData> mQueue;
201 mutable QMutex mMutex;
202 QWaitCondition mNotEmpty;
203 bool mFinished =
false;
210QString QgsXyzTilesBaseAlgorithm::group()
const
212 return QObject::tr(
"Raster tools" );
215QString QgsXyzTilesBaseAlgorithm::groupId()
const
217 return u
"rastertools"_s;
225void QgsXyzTilesBaseAlgorithm::createCommonParameters()
227 auto extentParam = std::make_unique<QgsProcessingParameterExtent>( u
"EXTENT"_s, QObject::tr(
"Extent" ) );
228 extentParam->setHelp( QObject::tr(
"Spatial extent of the area for tile generation." ) );
229 addParameter( extentParam.release() );
232 minZoomParam->setHelp(
234 "Minimum zoom level for generated tiles (0–25). Lower zoom levels cover broader geographic areas "
235 "at lower spatial resolution. Must be less than or equal to the maximum zoom level."
238 addParameter( minZoomParam.release() );
241 maxZoomParam->setHelp(
243 "Maximum zoom level for generated tiles (0–25). Higher zoom levels capture finer map details "
244 "and higher resolution, but exponentially increase total tile count, storage requirements, and rendering time. "
245 "Must be greater than or equal to the minimum zoom level."
248 addParameter( maxZoomParam.release() );
251 dpiParam->setHelp( QObject::tr(
"Output resolution in DPI for rendered map content." ) );
253 addParameter( dpiParam.release() );
255 auto bgColorParam = std::make_unique<QgsProcessingParameterColor>( u
"BACKGROUND_COLOR"_s, QObject::tr(
"Background color" ), QColor( Qt::transparent ),
true,
true );
256 bgColorParam->setHelp( QObject::tr(
"Background color used when rendering map tiles." ) );
257 addParameter( bgColorParam.release() );
259 auto antialiasParam = std::make_unique<QgsProcessingParameterBoolean>( u
"ANTIALIAS"_s, QObject::tr(
"Enable antialiasing" ),
true );
260 antialiasParam->setHelp( QObject::tr(
"Controls whether antialiasing is applied during tile rendering." ) );
262 addParameter( antialiasParam.release() );
264 auto tileFormatParam = std::make_unique<QgsProcessingParameterEnum>( u
"TILE_FORMAT"_s, QObject::tr(
"Tile format" ), QStringList { u
"PNG"_s, u
"JPG"_s, u
"WEBP"_s },
false, 0 );
265 tileFormatParam->setHelp( QObject::tr(
"Output image format for the rendered tiles." ) );
266 addParameter( tileFormatParam.release() );
269 qualityParam->setHelp( QObject::tr(
"Image quality percentage used when tile format is set to JPG (1–100)." ) );
271 addParameter( qualityParam.release() );
274 metaTileSizeParam->setHelp( QObject::tr(
"Size of metatiles (in tile units) used during rendering." ) );
276 addParameter( metaTileSizeParam.release() );
278 auto skipEmptyTilesParam = std::make_unique<QgsProcessingParameterBoolean>( u
"SKIP_EMPTY_TILES"_s, QObject::tr(
"Skip empty tiles" ),
false );
279 skipEmptyTilesParam->setHelp( QObject::tr(
"If set, completely empty tiles will be skipped." ) );
281 addParameter( skipEmptyTilesParam.release() );
286 Q_UNUSED( feedback );
293 QSet<QString> visibleLayers;
296 if ( layer->isVisible() )
298 visibleLayers << layer->layer()->id();
305 if ( visibleLayers.contains( layer->id() ) )
308 clonedLayer->moveToThread(
nullptr );
309 mLayers << clonedLayer;
313 QgsRectangle extent = parameterAsExtent( parameters, u
"EXTENT"_s, context );
316 ct.setBallparkTransformsAreAppropriate(
true );
319 mExtent = ct.transformBoundingBox( extent );
326 mMinZoom = parameterAsInt( parameters, u
"ZOOM_MIN"_s, context );
327 mMaxZoom = parameterAsInt( parameters, u
"ZOOM_MAX"_s, context );
328 if ( mMaxZoom < mMinZoom )
330 throw QgsProcessingException( QObject::tr(
"Maximum zoom (%1) must be ≥ minimum zoom (%2)" ).arg( mMaxZoom ).arg( mMinZoom ) );
332 mDpi = parameterAsInt( parameters, u
"DPI"_s, context );
333 mBackgroundColor = parameterAsColor( parameters, u
"BACKGROUND_COLOR"_s, context );
334 mAntialias = parameterAsBool( parameters, u
"ANTIALIAS"_s, context );
335 mSkipEmptyTiles = parameterAsBool( parameters, u
"SKIP_EMPTY_TILES"_s, context );
336 switch ( parameterAsEnum( parameters, u
"TILE_FORMAT"_s, context ) )
339 mTileFormat = u
"PNG"_s;
342 mTileFormat = u
"JPG"_s;
345 mTileFormat = u
"WEBP"_s;
348 mTileFormat = u
"PNG"_s;
352 mJpgQuality = mTileFormat !=
"PNG"_L1 ? parameterAsInt( parameters, u
"QUALITY"_s, context ) : -1;
353 mMetaTileSize = parameterAsInt( parameters, u
"METATILESIZE"_s, context );
369 if ( parameters.contains( u
"TILE_WIDTH"_s ) )
371 mTileWidth = parameterAsInt( parameters, u
"TILE_WIDTH"_s, context );
374 if ( parameters.contains( u
"TILE_HEIGHT"_s ) )
376 mTileHeight = parameterAsInt( parameters, u
"TILE_HEIGHT"_s, context );
379 if ( ( mTileFormat !=
"PNG"_L1 && mTileFormat !=
"WEBP"_L1 ) && mBackgroundColor.alpha() != 255 )
382 QObject::tr(
"A semi-transparent background color was set, but the JPG format only supports fully opaque colors. The background color setting will be ignored. Please use a fully opaque background color instead." )
395 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
401 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" )
402 .arg( layer->name(), u
"<a href=\"https://operations.osmfoundation.org/policies/tiles/\">"_s, u
"</a>"_s ),
403 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" )
404 .arg( layer->name(), QString(), QString() )
406 mLayers.removeAll( layer );
413std::optional< QgsMapSettings > QgsXyzTilesBaseAlgorithm::mapSettingsForTile(
const MetaTile &metaTile )
const
441 if ( mTileFormat ==
"PNG"_L1 || mTileFormat ==
"WEBP"_L1 || mBackgroundColor.alpha() == 255 )
445 QSize size( mTileWidth * metaTile.rows, mTileHeight * metaTile.cols );
461 while ( mRendererJobs.size() < mThreadsNumber && !mMetaTiles.empty() )
466 MetaTile metaTile = mMetaTiles.takeFirst();
467 const std::optional<QgsMapSettings> settings = mapSettingsForTile( metaTile );
468 if ( !settings.has_value() )
470 mProcessedMetaTiles++;
471 feedback->
setProgress( 100.0 * mProcessedMetaTiles / mTotalMetaTiles );
476 mRendererJobs.insert( job, metaTile );
479 const MetaTile tile = mRendererJobs.take( job );
483 mProcessedMetaTiles++;
484 feedback->
setProgress( 100.0 * mProcessedMetaTiles / mTotalMetaTiles );
486 processMetaTile( tile, renderedImage, feedback );
490 startJobs( feedback );
492 checkPipelineFinished( feedback );
498 checkPipelineFinished( feedback );
503 if ( feedback->
isCanceled() || ( mMetaTiles.isEmpty() && mRendererJobs.isEmpty() && mActivePostProcessingTasks == 0 ) )
516QString QgsXyzTilesDirectoryAlgorithm::name()
const
518 return u
"tilesxyzdirectory"_s;
521QString QgsXyzTilesDirectoryAlgorithm::displayName()
const
523 return QObject::tr(
"Generate XYZ tiles (Directory)" );
526QStringList QgsXyzTilesDirectoryAlgorithm::tags()
const
528 return QObject::tr(
"tiles,xyz,tms,directory" ).split(
',' );
531QString QgsXyzTilesDirectoryAlgorithm::shortHelpString()
const
534 "This algorithm generates XYZ raster tiles from the current project and saves them as individual image files in a structured directory hierarchy ({z}/{x}/{y}.png or .jpg).\n\n"
535 "All visible map layers from the project will be rendered into tiles across the specified extent and zoom range.\n\n"
536 "Optionally, a standalone Leaflet HTML file can be generated for instant web previewing of the tiles."
540QgsXyzTilesDirectoryAlgorithm *QgsXyzTilesDirectoryAlgorithm::createInstance()
const
542 return new QgsXyzTilesDirectoryAlgorithm();
545void QgsXyzTilesDirectoryAlgorithm::initAlgorithm(
const QVariantMap & )
547 createCommonParameters();
549 tileWidthParam->setHelp( QObject::tr(
"Width of each tile image in pixels." ) );
550 addParameter( tileWidthParam.release() );
553 tileHeightParam->setHelp( QObject::tr(
"Height of each tile image in pixels." ) );
554 addParameter( tileHeightParam.release() );
556 auto tmsParam = std::make_unique<QgsProcessingParameterBoolean>( u
"TMS_CONVENTION"_s, QObject::tr(
"Use inverted tile Y axis (TMS convention)" ),
false );
557 tmsParam->setHelp( QObject::tr(
"Inverts the Y tile coordinate naming convention to follow TMS format." ) );
558 addParameter( tmsParam.release() );
560 auto titleParam = std::make_unique<QgsProcessingParameterString>( u
"HTML_TITLE"_s, QObject::tr(
"Leaflet HTML output title" ), QVariant(),
false,
true );
562 titleParam->setHelp( QObject::tr(
"Title displayed in the generated Leaflet HTML web viewer." ) );
563 addParameter( titleParam.release() );
565 auto attributionParam = std::make_unique<QgsProcessingParameterString>( u
"HTML_ATTRIBUTION"_s, QObject::tr(
"Leaflet HTML output attribution" ), QVariant(),
false,
true );
567 attributionParam->setHelp( QObject::tr(
"Attribution text displayed in the generated Leaflet HTML web viewer." ) );
568 addParameter( attributionParam.release() );
570 auto osmParam = std::make_unique<QgsProcessingParameterBoolean>( u
"HTML_OSM"_s, QObject::tr(
"Include OpenStreetMap basemap in Leaflet HTML output" ),
false );
572 osmParam->setHelp( QObject::tr(
"Includes an OpenStreetMap background layer in the generated Leaflet HTML viewer." ) );
573 addParameter( osmParam.release() );
575 auto outputDirParam = std::make_unique<QgsProcessingParameterFolderDestination>( u
"OUTPUT_DIRECTORY"_s, QObject::tr(
"Output directory" ) );
576 outputDirParam->setHelp( QObject::tr(
"Destination folder where the generated directory structure and tile files will be stored." ) );
577 addParameter( outputDirParam.release() );
579 auto outputHtmlParam = std::make_unique<QgsProcessingParameterFileDestination>( u
"OUTPUT_HTML"_s, QObject::tr(
"Output HTML (Leaflet)" ), QObject::tr(
"HTML files (*.html)" ), QVariant(),
true );
580 outputHtmlParam->setHelp( QObject::tr(
"Destination file path for the optional Leaflet HTML web map preview." ) );
581 addParameter( outputHtmlParam.release() );
588 QGS_MARK_ALGORITHM_SOURCE
590 const bool tms = parameterAsBoolean( parameters, u
"TMS_CONVENTION"_s, context );
591 const QString title = parameterAsString( parameters, u
"HTML_TITLE"_s, context );
592 const QString attribution = parameterAsString( parameters, u
"HTML_ATTRIBUTION"_s, context );
593 const bool useOsm = parameterAsBoolean( parameters, u
"HTML_OSM"_s, context );
594 QString outputDir = parameterAsString( parameters, u
"OUTPUT_DIRECTORY"_s, context );
595 const QString outputHtml = parameterAsString( parameters, u
"OUTPUT_HTML"_s, context );
597 mOutputDir = outputDir;
600 long long totalTiles = 0;
602 for (
int z = mMinZoom; z <= mMaxZoom; z++ )
607 long long tileCount = 0;
608 mMetaTiles += getMetatiles( mWgs84Extent, z, tileCount, mMetaTileSize );
609 feedback->
pushInfo( QObject::tr(
"%1 metatiles (%2 tiles) will be created for zoom level %3" ).arg( mMetaTiles.size() - mTotalMetaTiles ).arg( tileCount ).arg( z ) );
610 mTotalMetaTiles = mMetaTiles.size();
611 totalTiles += tileCount;
613 if ( mTotalMetaTiles == 0 )
615 throw QgsProcessingException( QObject::tr(
"No metatiles will be created -- please check the extent and zoom limits" ) );
618 feedback->
pushInfo( QObject::tr(
"A total of %1 metatiles (%2 tiles) will be created" ).arg( mTotalMetaTiles ).arg( totalTiles ) );
620 checkLayersUsagePolicy( feedback );
622 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
624 layer->moveToThread( QThread::currentThread() );
627 doExport( feedback );
629 qDeleteAll( mLayers );
632 if ( mSkipEmptyTiles )
634 feedback->
pushInfo( QObject::tr(
"Wrote %1 total tiles, skipped %2 empty tiles" ).arg( mTilesWritten.load() ).arg( mEmptyTiles.load() ) );
638 results.insert( u
"OUTPUT_DIRECTORY"_s, outputDir );
640 if ( !outputHtml.isEmpty() )
642 const QString osm = QStringLiteral(
643 "var osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',"
644 "{minZoom: %1, maxZoom: %2, attribution: '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);"
649 const QString addOsm = useOsm ? osm : QString();
650 const QString tmsConvention = tms ? u
"true"_s : u
"false"_s;
651 const QString attr = attribution.isEmpty() ? u
"Created by QGIS"_s : attribution;
652 const QString tileSource = u
"'file:///%1/{z}/{x}/{y}.%2'"_s.arg( outputDir.replace(
"\\",
"/" ).toHtmlEscaped(), mTileFormat.toLower() );
654 const QString html = QStringLiteral(
655 "<!DOCTYPE html><html><head><title>%1</title><meta charset=\"utf-8\"/>"
656 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
657 "<link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\""
658 "integrity=\"sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H\""
660 "<script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\""
661 "integrity=\"sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH\""
662 "crossorigin=\"\"></script>"
663 "<style type=\"text/css\">body {margin: 0;padding: 0;} html, body, #map{width: 100%;height: 100%;}</style></head>"
664 "<body><div id=\"map\"></div><script>"
665 "var map = L.map('map', {attributionControl: false}).setView([%2, %3], %4);"
666 "L.control.attribution({prefix: false}).addTo(map);"
668 "var tilesource_layer = L.tileLayer(%6, {minZoom: %7, maxZoom: %8, tms: %9, attribution: '%10'}).addTo(map);"
669 "</script></body></html>"
671 .arg( title.isEmpty() ? u
"Leaflet preview"_s : title )
672 .arg( mWgs84Extent.center().y() )
673 .arg( mWgs84Extent.center().x() )
674 .arg( ( mMaxZoom + mMinZoom ) / 2 )
675 .arg( addOsm, tileSource )
678 .arg( tmsConvention, attr );
680 QFile htmlFile( outputHtml );
681 if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
685 QTextStream fout( &htmlFile );
688 results.insert( u
"OUTPUT_HTML"_s, outputHtml );
694 const QString layerUri
695 = u
"type=xyz&url=file:///%1/%7Bz%7D/%7Bx%7D/%7By%7D.%2&zmax=%3&zmin=%4"_s.arg( outputDir.replace(
"\\",
"/" ).toHtmlEscaped(), mTileFormat.toLower() ).arg( mMaxZoom ).arg( mMinZoom );
696 auto layer = std::make_unique<QgsRasterLayer>( layerUri,
"OUTPUT_LAYER", u
"wms"_s );
697 if ( !layer->isValid() )
699 feedback->
reportError( QObject::tr(
"Failed to open XYZ directory as a raster layer" ) );
701 const QString layerId = layer->id();
703 details.setOutputLayerName( layer.get() );
706 results.insert( u
"OUTPUT_LAYER"_s, layerId );
712void QgsXyzTilesDirectoryAlgorithm::processMetaTile(
const MetaTile &metaTile,
const QImage &renderedImage,
QgsProcessingFeedback *feedback )
714 mActivePostProcessingTasks++;
716 mPostProcessingPool->start( [
this, metaTile, renderedImage, feedback]() {
717 const bool testEmptyTilesUsingAlpha0 = mTileFormat !=
"JPG"_L1 && mBackgroundColor.alpha() == 0;
718 long long localWritten = 0;
719 long long localEmpty = 0;
721 QSet<QString> createdDirs;
723 for (
auto it = metaTile.tiles.constBegin(); it != metaTile.tiles.constEnd(); ++it )
728 const QPair<int, int> tm = it.key();
729 const QImage tileImage = renderedImage.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
737 const Tile tile = it.value();
738 const QString dirPath = u
"%1/%2/%3"_s.arg( mOutputDir ).arg( tile.z ).arg( tile.x );
739 if ( !createdDirs.contains( dirPath ) )
741 QDir().mkpath( dirPath );
742 createdDirs.insert( dirPath );
745 const int y = mTms ? tile2tms( tile.y, tile.z ) : tile.y;
746 const QString filePath = u
"%1/%2.%3"_s.arg( dirPath ).arg( y ).arg( mTileFormat.toLower() );
747 tileImage.save( filePath, mTileFormat.toStdString().c_str(), mJpgQuality );
752 mTilesWritten += localWritten;
753 mEmptyTiles += localEmpty;
755 mActivePostProcessingTasks--;
756 checkPipelineFinished( feedback );
762 mPostProcessingPool = std::make_unique<QThreadPool>();
763 mPostProcessingPool->setMaxThreadCount( std::max( 1, mThreadsNumber ) );
767 mJobOwner.reset(
new QObject() );
769 startJobs( feedback );
771 if ( !mMetaTiles.isEmpty() || !mRendererJobs.isEmpty() || mActivePostProcessingTasks.load() > 0 )
776 for (
auto *j : mRendererJobs.keys() )
781 mRendererJobs.clear();
783 mPostProcessingPool->waitForDone();
784 mPostProcessingPool.reset();
785 mEventLoop =
nullptr;
793QString QgsXyzTilesMbtilesAlgorithm::name()
const
795 return u
"tilesxyzmbtiles"_s;
798QString QgsXyzTilesMbtilesAlgorithm::displayName()
const
800 return QObject::tr(
"Generate XYZ tiles (MBTiles)" );
803QStringList QgsXyzTilesMbtilesAlgorithm::tags()
const
805 return QObject::tr(
"tiles,xyz,tms,mbtiles" ).split(
',' );
808QString QgsXyzTilesMbtilesAlgorithm::shortHelpString()
const
811 "This algorithm generates XYZ raster tiles from the current project and packages them into a single, portable MBTiles (SQLite) database file.\n\n"
812 "All visible map layers from the project will be rendered into tiles across the specified extent and zoom range."
816QgsXyzTilesMbtilesAlgorithm *QgsXyzTilesMbtilesAlgorithm::createInstance()
const
818 return new QgsXyzTilesMbtilesAlgorithm();
821void QgsXyzTilesMbtilesAlgorithm::initAlgorithm(
const QVariantMap & )
823 createCommonParameters();
831 QGS_MARK_ALGORITHM_SOURCE
833 const QString outputFile = parameterAsString( parameters, u
"OUTPUT_FILE"_s, context );
834 if ( QFile::exists( outputFile ) )
836 feedback->
pushWarning( QObject::tr(
"Removing existing file '%1'" ).arg( QDir::toNativeSeparators( outputFile ) ) );
837 if ( !QFile( outputFile ).remove() )
839 throw QgsProcessingException( QObject::tr(
"Could not remove existing file '%1'" ).arg( QDir::toNativeSeparators( outputFile ) ) );
843 mMbtilesWriter = std::make_unique<QgsMbTiles>( outputFile );
846 if ( !mMbtilesWriter->create(
true ) )
850 mMbtilesWriter->setMetadataValue( u
"format"_s, mTileFormat.toLower() );
851 mMbtilesWriter->setMetadataValue( u
"name"_s, QFileInfo( outputFile ).baseName() );
852 mMbtilesWriter->setMetadataValue( u
"description"_s, QFileInfo( outputFile ).baseName() );
853 mMbtilesWriter->setMetadataValue( u
"version"_s, u
"1.1"_s );
854 mMbtilesWriter->setMetadataValue( u
"type"_s, u
"overlay"_s );
855 mMbtilesWriter->setMetadataValue( u
"minzoom"_s, QString::number( mMinZoom ) );
856 mMbtilesWriter->setMetadataValue( u
"maxzoom"_s, QString::number( mMaxZoom ) );
857 QString boundsStr = QString( u
"%1,%2,%3,%4"_s ).arg( mWgs84Extent.xMinimum() ).arg( mWgs84Extent.yMinimum() ).arg( mWgs84Extent.xMaximum() ).arg( mWgs84Extent.yMaximum() );
858 mMbtilesWriter->setMetadataValue( u
"bounds"_s, boundsStr );
860 long long totalTiles = 0;
862 for (
int z = mMinZoom; z <= mMaxZoom; z++ )
867 long long tileCount = 0;
868 mMetaTiles += getMetatiles( mWgs84Extent, z, tileCount, mMetaTileSize );
869 feedback->
pushInfo( QObject::tr(
"%1 metatiles (%2 tiles) will be created for zoom level %3" ).arg( mMetaTiles.size() - mTotalMetaTiles ).arg( tileCount ).arg( z ) );
870 mTotalMetaTiles = mMetaTiles.size();
871 totalTiles += tileCount;
873 if ( mTotalMetaTiles == 0 )
875 throw QgsProcessingException( QObject::tr(
"No metatiles will be created -- please check the extent and zoom limits" ) );
878 feedback->
pushInfo( QObject::tr(
"A total of %1 metatiles (%2 tiles) will be created" ).arg( mTotalMetaTiles ).arg( totalTiles ) );
880 checkLayersUsagePolicy( feedback );
882 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
884 layer->moveToThread( QThread::currentThread() );
887 doExport( feedback );
889 qDeleteAll( mLayers );
894 mMbtilesWriter->finalize();
897 if ( mSkipEmptyTiles )
899 feedback->
pushInfo( QObject::tr(
"Wrote %1 total tiles, skipped %2 empty tiles" ).arg( mTilesWritten.load() ).arg( mEmptyTiles.load() ) );
902 results.insert( u
"OUTPUT_FILE"_s, outputFile );
907 auto layer = std::make_unique<QgsRasterLayer>( outputFile,
"OUTPUT_LAYER", u
"gdal"_s );
908 if ( !layer->isValid() )
910 feedback->
reportError( QObject::tr(
"Failed to open MBTiles file as a raster layer" ) );
912 const QString layerId = layer->id();
914 details.setOutputLayerName( layer.get() );
917 results.insert( u
"OUTPUT_LAYER"_s, layerId );
923void QgsXyzTilesMbtilesAlgorithm::processMetaTile(
const MetaTile &metaTile,
const QImage &renderedImg,
QgsProcessingFeedback *feedback )
925 mActivePostProcessingTasks++;
927 mPostProcessingPool->start( [
this, feedback, metaTile, renderedImg]() {
928 const bool testEmptyTilesUsingAlpha0 = mTileFormat !=
"JPG"_L1 && mBackgroundColor.alpha() == 0;
929 long long localWritten = 0;
930 long long localEmpty = 0;
932 QList<QgsMbTiles::TileData> metatileTiles;
933 metatileTiles.reserve( metaTile.tiles.size() );
935 for (
auto it = metaTile.tiles.constBegin(); it != metaTile.tiles.constEnd(); ++it )
940 const QPair<int, int> tm = it.key();
942 const QImage tileImage = renderedImg.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
952 QBuffer buffer( &bytes );
953 buffer.open( QIODevice::WriteOnly );
954 tileImage.save( &buffer, mTileFormat.toStdString().c_str(), mJpgQuality );
956 const Tile tile = it.value();
957 const int tileY = tile2tms( tile.y, tile.z );
958 metatileTiles.append( { tile.z, tile.x, tileY, bytes } );
962 mWriteQueue->push( metatileTiles );
964 mTilesWritten += localWritten;
965 mEmptyTiles += localEmpty;
967 mActivePostProcessingTasks--;
968 checkPipelineFinished( feedback );
974 mPostProcessingPool = std::make_unique<QThreadPool>();
975 mPostProcessingPool->setMaxThreadCount( std::max( 1, mThreadsNumber ) );
977 PendingTilesToWriteQueue queue;
978 mWriteQueue = &queue;
980 QThread *dbThread = QThread::create( [
this, &queue]() {
981 QList<QgsMbTiles::TileData> batch;
982 constexpr int BATCH_SIZE = 10000;
983 batch.reserve( BATCH_SIZE );
984 while ( queue.popBatch( batch, BATCH_SIZE ) )
986 mMbtilesWriter->setTileData( batch );
994 mJobOwner.reset(
new QObject() );
996 startJobs( feedback );
998 if ( !mMetaTiles.isEmpty() || !mRendererJobs.isEmpty() || mActivePostProcessingTasks.load() > 0 )
1003 for (
auto *j : mRendererJobs.keys() )
1008 mRendererJobs.clear();
1010 mPostProcessingPool->waitForDone();
1011 mPostProcessingPool.reset();
1013 queue.setFinished();
1017 mWriteQueue =
nullptr;
1018 mEventLoop =
nullptr;
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
@ 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.
@ Export
Renderer used for printing or exporting to a file.
@ 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.
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ Antialiasing
Enable anti-aliasing for map rendering.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Represents a coordinate reference system (CRS).
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.
void setProgress(double progress)
Sets the current progress for the feedback object.
Contains operations and filters which apply to QImages.
static bool isBlankImage(const QImage &image)
Tests whether an image is completely blank, i.e.
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.
QgsMapLayer * addMapLayer(QgsMapLayer *layer, bool takeOwnership=true)
Add a layer to the store.
static bool isOpenStreetMapLayer(QgsMapLayer *layer)
Returns true if the layer is served by OpenStreetMap server.
Base class for all map layer types.
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.
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 setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
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.
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Details for layers to load into projects.
Contains information about the context in which a processing algorithm is executed.
QgsExpressionContext & expressionContext()
Returns the expression context.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
void addLayerToLoadOnCompletion(const QString &layer, const QgsProcessingContext::LayerDetails &details)
Adds a layer to load (by ID or datasource) into the canvas upon completion of the algorithm or model.
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.
QgsMapLayerStore * temporaryLayerStore()
Returns a reference to the layer store used for storing temporary layers during algorithm execution.
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 raster layer output for processing algorithms.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
@ Raster
Raster layer type.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Qgis::ScaleCalculationMethod scaleMethod
A rectangle specified with double values.
#define MAXIMUM_OPENSTREETMAP_TILES_FETCH