mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-16 00:06:09 -05:00
537 lines
21 KiB
C++
537 lines
21 KiB
C++
/***************************************************************************
|
|
qgsalgorithmxyztiles.cpp
|
|
---------------------
|
|
begin : August 2023
|
|
copyright : (C) 2023 by Alexander Bruy
|
|
email : alexander dot bruy at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgsalgorithmxyztiles.h"
|
|
|
|
#include <QBuffer>
|
|
|
|
#include "qgslayertree.h"
|
|
#include "qgslayertreelayer.h"
|
|
#include "qgsexpressioncontextutils.h"
|
|
|
|
///@cond PRIVATE
|
|
|
|
int tile2tms( const int y, const int zoom )
|
|
{
|
|
double n = std::pow( 2, zoom );
|
|
return ( int )std::floor( n - y - 1 );
|
|
}
|
|
|
|
int lon2tileX( const double lon, const int z )
|
|
{
|
|
return ( int )( std::floor( ( lon + 180.0 ) / 360.0 * ( 1 << z ) ) );
|
|
}
|
|
|
|
int lat2tileY( const double lat, const int z )
|
|
{
|
|
double latRad = lat * M_PI / 180.0;
|
|
return ( int )( std::floor( ( 1.0 - std::asinh( std::tan( latRad ) ) / M_PI ) / 2.0 * ( 1 << z ) ) );
|
|
}
|
|
|
|
double tileX2lon( const int x, const int z )
|
|
{
|
|
return x / ( double )( 1 << z ) * 360.0 - 180 ;
|
|
}
|
|
|
|
double tileY2lat( const int y, const int z )
|
|
{
|
|
double n = M_PI - 2.0 * M_PI * y / ( double )( 1 << z );
|
|
return 180.0 / M_PI * std::atan( 0.5 * ( std::exp( n ) - std::exp( -n ) ) );
|
|
}
|
|
|
|
void extent2TileXY( QgsRectangle extent, const int zoom, int &xMin, int &yMin, int &xMax, int &yMax )
|
|
{
|
|
xMin = lon2tileX( extent.xMinimum(), zoom );
|
|
yMin = lat2tileY( extent.yMinimum(), zoom );
|
|
xMax = lon2tileX( extent.xMaximum(), zoom );
|
|
yMax = lat2tileY( extent.xMaximum(), zoom );
|
|
}
|
|
|
|
QList< MetaTile > getMetatiles( const QgsRectangle extent, const int zoom, const int tileSize )
|
|
{
|
|
int minX = lon2tileX( extent.xMinimum(), zoom );
|
|
int minY = lat2tileY( extent.yMaximum(), zoom );
|
|
int maxX = lon2tileX( extent.xMaximum(), zoom );
|
|
int maxY = lat2tileY( extent.yMinimum(), zoom );;
|
|
|
|
int i = 0;
|
|
QMap< QString, MetaTile > tiles;
|
|
for ( int x = minX; x <= maxX; x++ )
|
|
{
|
|
int j = 0;
|
|
for ( int y = minY; y <= maxY; y++ )
|
|
{
|
|
QString key = QStringLiteral( "%1:%2" ).arg( ( int )( i / tileSize ) ).arg( ( int )( j / tileSize ) );
|
|
MetaTile tile = tiles.value( key, MetaTile() );
|
|
tile.addTile( i % tileSize, j % tileSize, Tile( x, y, zoom ) );
|
|
tiles.insert( key, tile );
|
|
j++;
|
|
}
|
|
i++;
|
|
}
|
|
return tiles.values();
|
|
}
|
|
|
|
////
|
|
|
|
QString QgsXyzTilesBaseAlgorithm::group() const
|
|
{
|
|
return QObject::tr( "Raster tools" );
|
|
}
|
|
|
|
QString QgsXyzTilesBaseAlgorithm::groupId() const
|
|
{
|
|
return QStringLiteral( "rastertools" );
|
|
}
|
|
|
|
QgsProcessingAlgorithm::Flags QgsXyzTilesBaseAlgorithm::flags() const
|
|
{
|
|
return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagRequiresProject;
|
|
}
|
|
|
|
void QgsXyzTilesBaseAlgorithm::createCommonParameters()
|
|
{
|
|
addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ZOOM_MIN" ), QObject::tr( "Minimum zoom" ), QgsProcessingParameterNumber::Integer, 12, false, 0, 25 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ZOOM_MAX" ), QObject::tr( "Maximum zoom" ), QgsProcessingParameterNumber::Integer, 12, false, 0, 25 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DPI" ), QObject::tr( "DPI" ), QgsProcessingParameterNumber::Integer, 96, false, 48, 600 ) );
|
|
addParameter( new QgsProcessingParameterColor( QStringLiteral( "BACKGROUND_COLOR" ), QObject::tr( "Background color" ), QColor( Qt::transparent ), true, true ) );
|
|
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true ) );
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "TILE_FORMAT" ), QObject::tr( "Tile format" ), QStringList() << QStringLiteral( "PNG" ) << QStringLiteral( "JPG" ), false, 0 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "QUALITY" ), QObject::tr( "Quality (JPG only)" ), QgsProcessingParameterNumber::Integer, 75, false, 1, 100 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "METATILESIZE" ), QObject::tr( "Metatile size" ), QgsProcessingParameterNumber::Integer, 4, false, 1, 20 ) );
|
|
}
|
|
|
|
bool QgsXyzTilesBaseAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
Q_UNUSED( feedback );
|
|
|
|
QgsProject *project = context.project();
|
|
|
|
const QList< QgsLayerTreeLayer * > projectLayers = project->layerTreeRoot()->findLayers();
|
|
QSet< QString > visibleLayers;
|
|
for ( const QgsLayerTreeLayer *layer : projectLayers )
|
|
{
|
|
if ( layer->isVisible() )
|
|
{
|
|
visibleLayers << layer->layer()->id();
|
|
}
|
|
}
|
|
|
|
QList< QgsMapLayer * > renderLayers = project->layerTreeRoot()->layerOrder();
|
|
for ( QgsMapLayer *layer : renderLayers )
|
|
{
|
|
if ( visibleLayers.contains( layer->id() ) )
|
|
{
|
|
QgsMapLayer *clonedLayer = layer->clone();
|
|
clonedLayer->moveToThread( nullptr );
|
|
mLayers << clonedLayer;
|
|
}
|
|
}
|
|
|
|
QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context );
|
|
QgsCoordinateReferenceSystem extentCrs = parameterAsExtentCrs( parameters, QStringLiteral( "EXTENT" ), context );
|
|
QgsCoordinateTransform ct( extentCrs, project->crs(), context.transformContext() );
|
|
mExtent = ct.transformBoundingBox( extent );
|
|
|
|
mMinZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MIN" ), context );
|
|
mMaxZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MAX" ), context );
|
|
mDpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context );
|
|
mBackgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context );
|
|
mAntialias = parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context );
|
|
mTileFormat = parameterAsEnum( parameters, QStringLiteral( "TILE_FORMAT" ), context ) ? QStringLiteral( "JPG" ) : QStringLiteral( "PNG" );
|
|
mJpgQuality = parameterAsInt( parameters, QStringLiteral( "QUALITY" ), context );
|
|
mMetaTileSize = parameterAsInt( parameters, QStringLiteral( "METATILESIZE" ), context );
|
|
mThreadsNumber = context.maximumThreads();
|
|
mTransformContext = context.transformContext();
|
|
mFeedback = feedback;
|
|
|
|
mWgs84Crs = QgsCoordinateReferenceSystem( "EPSG:4326" );
|
|
mMercatorCrs = QgsCoordinateReferenceSystem( "EPSG:3857" );
|
|
mSrc2Wgs = QgsCoordinateTransform( project->crs(), mWgs84Crs, context.transformContext() );
|
|
mWgs2Mercator = QgsCoordinateTransform( mWgs84Crs, mMercatorCrs, context.transformContext() );
|
|
|
|
mWgs84Extent = mSrc2Wgs.transformBoundingBox( mExtent );
|
|
|
|
if ( parameters.contains( QStringLiteral( "TILE_WIDTH" ) ) )
|
|
{
|
|
mTileWidth = parameterAsInt( parameters, QStringLiteral( "TILE_WIDTH" ), context );
|
|
}
|
|
|
|
if ( parameters.contains( QStringLiteral( "TILE_HEIGHT" ) ) )
|
|
{
|
|
mTileHeight = parameterAsInt( parameters, QStringLiteral( "TILE_HEIGHT" ), context );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void QgsXyzTilesBaseAlgorithm::startJobs()
|
|
{
|
|
while ( mRendererJobs.size() < mThreadsNumber )
|
|
{
|
|
MetaTile metaTile = mMetaTiles.takeFirst();
|
|
|
|
QgsMapSettings settings;
|
|
settings.setExtent( mWgs2Mercator.transformBoundingBox( metaTile.extent() ) );
|
|
settings.setOutputImageFormat( QImage::Format_ARGB32_Premultiplied );
|
|
settings.setTransformContext( mTransformContext );
|
|
settings.setDestinationCrs( mMercatorCrs );
|
|
settings.setLayers( mLayers );
|
|
settings.setOutputDpi( mDpi );
|
|
if ( mTileFormat == QStringLiteral( "PNG" ) )
|
|
{
|
|
settings.setBackgroundColor( mBackgroundColor );
|
|
}
|
|
QSize size( mTileWidth * metaTile.rows, mTileHeight * metaTile.cols );
|
|
settings.setOutputSize( size );
|
|
|
|
QgsLabelingEngineSettings labelingSettings = settings.labelingEngineSettings();
|
|
labelingSettings.setFlag( Qgis::LabelingFlag::UsePartialCandidates, false );
|
|
settings.setLabelingEngineSettings( labelingSettings );
|
|
|
|
QgsExpressionContext exprContext = settings.expressionContext();
|
|
exprContext.appendScope( QgsExpressionContextUtils::mapSettingsScope( settings ) );
|
|
settings.setExpressionContext( exprContext );
|
|
|
|
QgsMapRendererSequentialJob *job = new QgsMapRendererSequentialJob( settings );
|
|
mRendererJobs.insert( job, metaTile );
|
|
QObject::connect( job, &QgsMapRendererJob::finished, mFeedback, [ this, job ]() { processMetaTile( job ); } );
|
|
job->start();
|
|
}
|
|
}
|
|
|
|
// Native XYZ tiles (directory) algorithm
|
|
|
|
QString QgsXyzTilesDirectoryAlgorithm::name() const
|
|
{
|
|
return QStringLiteral( "tilesxyzdirectory" );
|
|
}
|
|
|
|
QString QgsXyzTilesDirectoryAlgorithm::displayName() const
|
|
{
|
|
return QObject::tr( "Generate XYZ tiles (Directory)" );
|
|
}
|
|
|
|
QStringList QgsXyzTilesDirectoryAlgorithm::tags() const
|
|
{
|
|
return QObject::tr( "tiles,xyz,tms,directory" ).split( ',' );
|
|
}
|
|
|
|
QString QgsXyzTilesDirectoryAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as individual images in a directory." );
|
|
}
|
|
|
|
QgsXyzTilesDirectoryAlgorithm *QgsXyzTilesDirectoryAlgorithm::createInstance() const
|
|
{
|
|
return new QgsXyzTilesDirectoryAlgorithm();
|
|
}
|
|
|
|
void QgsXyzTilesDirectoryAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
createCommonParameters();
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_WIDTH" ), QObject::tr( "Tile width" ), QgsProcessingParameterNumber::Integer, 256, false, 1, 4096 ) );
|
|
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_HEIGHT" ), QObject::tr( "Tile height" ), QgsProcessingParameterNumber::Integer, 256, false, 1, 4096 ) );
|
|
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "TMS_CONVENTION" ), QObject::tr( "Use inverted tile Y axis (TMS convention)" ), false, true ) );
|
|
|
|
std::unique_ptr< QgsProcessingParameterString > titleParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "HTML_TITLE" ), QObject::tr( "Leaflet HTML output title" ), QVariant(), false, true );
|
|
titleParam->setFlags( titleParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
|
|
addParameter( titleParam.release() );
|
|
std::unique_ptr< QgsProcessingParameterString > attributionParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "HTML_ATTRIBUTION" ), QObject::tr( "Leaflet HTML output attribution" ), QVariant(), false, true );
|
|
attributionParam->setFlags( attributionParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
|
|
addParameter( attributionParam.release() );
|
|
std::unique_ptr< QgsProcessingParameterBoolean > osmParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "HTML_OSM" ), QObject::tr( "Include OpenStreetMap basemap in Leaflet HTML output" ), false, true );
|
|
osmParam->setFlags( osmParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
|
|
addParameter( osmParam.release() );
|
|
|
|
addParameter( new QgsProcessingParameterFolderDestination( QStringLiteral( "OUTPUT_DIRECTORY" ), QObject::tr( "Output directory" ) ) );
|
|
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML" ), QObject::tr( "Output html (Leaflet)" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
|
|
}
|
|
|
|
QVariantMap QgsXyzTilesDirectoryAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
const bool tms = parameterAsBoolean( parameters, QStringLiteral( "TMS_CONVENTION" ), context );
|
|
const QString title = parameterAsString( parameters, QStringLiteral( "HTML_TITLE" ), context );
|
|
const QString attribution = parameterAsString( parameters, QStringLiteral( "HTML_ATTRIBUTION" ), context );
|
|
const bool useOsm = parameterAsBoolean( parameters, QStringLiteral( "HTML_OSM" ), context );
|
|
QString outputDir = parameterAsString( parameters, QStringLiteral( "OUTPUT_DIRECTORY" ), context );
|
|
const QString outputHtml = parameterAsString( parameters, QStringLiteral( "OUTPUT_HTML" ), context );
|
|
|
|
mOutputDir = outputDir;
|
|
mTms = tms;
|
|
|
|
for ( int z = mMinZoom; z <= mMaxZoom; z++ )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
break;
|
|
|
|
mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
|
|
}
|
|
|
|
for ( QgsMapLayer *layer : std::as_const( mLayers ) )
|
|
{
|
|
layer->moveToThread( QThread::currentThread() );
|
|
}
|
|
|
|
mTotalTiles = mMetaTiles.size();
|
|
|
|
QEventLoop loop;
|
|
// cppcheck-suppress danglingLifetime
|
|
mEventLoop = &loop;
|
|
startJobs();
|
|
loop.exec();
|
|
|
|
qDeleteAll( mLayers );
|
|
mLayers.clear();
|
|
|
|
QVariantMap results;
|
|
results.insert( QStringLiteral( "OUTPUT_DIRECTORY" ), outputDir );
|
|
|
|
if ( !outputHtml.isEmpty() )
|
|
{
|
|
QString osm = QStringLiteral(
|
|
"var osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',"
|
|
"{minZoom: %1, maxZoom: %2, attribution: '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);" )
|
|
.arg( mMinZoom ).arg( mMaxZoom );
|
|
|
|
QString addOsm = useOsm ? osm : QString();
|
|
QString tmsConvention = tms ? QStringLiteral( "true" ) : QStringLiteral( "false" );
|
|
QString attr = attribution.isEmpty() ? QStringLiteral( "Created by QGIS" ) : attribution;
|
|
QString tileSource = QStringLiteral( "'file:///%1/{z}/{x}/{y}.%2'" )
|
|
.arg( outputDir.replace( "\\", "/" ).toHtmlEscaped() ).arg( mTileFormat.toLower() );
|
|
|
|
QString html = QStringLiteral(
|
|
"<!DOCTYPE html><html><head><title>%1</title><meta charset=\"utf-8\"/>"
|
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
|
|
"<link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.3/dist/leaflet.css\""
|
|
"integrity=\"sha384-o/2yZuJZWGJ4s/adjxVW71R+EO/LyCwdQfP5UWSgX/w87iiTXuvDZaejd3TsN7mf\""
|
|
"crossorigin=\"\"/>"
|
|
"<script src=\"https://unpkg.com/leaflet@1.9.3/dist/leaflet.js\""
|
|
"integrity=\"sha384-okbbMvvx/qfQkmiQKfd5VifbKZ/W8p1qIsWvE1ROPUfHWsDcC8/BnHohF7vPg2T6\""
|
|
"crossorigin=\"\"></script>"
|
|
"<style type=\"text/css\">body {margin: 0;padding: 0;} html, body, #map{width: 100%;height: 100%;}</style></head>"
|
|
"<body><div id=\"map\"></div><script>"
|
|
"var map = L.map('map', {attributionControl: false}).setView([%2, %3], %4);"
|
|
"L.control.attribution({prefix: false}).addTo(map);"
|
|
"%5"
|
|
"var tilesource_layer = L.tileLayer(%6, {minZoom: %7, maxZoom: %8, tms: %9, attribution: '%10'}).addTo(map);"
|
|
"</script></body></html>"
|
|
)
|
|
.arg( title.isEmpty() ? QStringLiteral( "Leaflet preview" ) : title )
|
|
.arg( mWgs84Extent.center().y() )
|
|
.arg( mWgs84Extent.center().x() )
|
|
.arg( ( mMaxZoom + mMinZoom ) / 2 )
|
|
.arg( addOsm )
|
|
.arg( tileSource )
|
|
.arg( mMinZoom )
|
|
.arg( mMaxZoom )
|
|
.arg( tmsConvention )
|
|
.arg( attr );
|
|
|
|
QFile htmlFile( outputHtml );
|
|
if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Could not html file %1" ).arg( outputHtml ) );
|
|
}
|
|
QTextStream fout( &htmlFile );
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
fout.setCodec( "UTF-8" );
|
|
#endif
|
|
fout << html;
|
|
|
|
results.insert( QStringLiteral( "OUTPUT_HTML" ), outputHtml );
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
void QgsXyzTilesDirectoryAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
|
|
{
|
|
MetaTile metaTile = mRendererJobs.value( job );
|
|
QImage img = job->renderedImage();
|
|
|
|
QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
|
|
while ( it != metaTile.tiles.constEnd() )
|
|
{
|
|
QPair<int, int> tm = it.key();
|
|
Tile tile = it.value();
|
|
QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
|
|
QDir tileDir( QStringLiteral( "%1/%2/%3" ).arg( mOutputDir ).arg( tile.z ).arg( tile.x ) );
|
|
tileDir.mkpath( tileDir.absolutePath() );
|
|
int y = tile.y;
|
|
if ( mTms )
|
|
{
|
|
y = tile2tms( y, tile.z );
|
|
}
|
|
tileImage.save( QStringLiteral( "%1/%2.%3" ).arg( tileDir.absolutePath() ).arg( y ).arg( mTileFormat.toLower() ), mTileFormat.toStdString().c_str(), mJpgQuality );
|
|
++it;
|
|
}
|
|
|
|
mRendererJobs.remove( job );
|
|
job->deleteLater();
|
|
|
|
mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
|
|
|
|
if ( mFeedback->isCanceled() )
|
|
{
|
|
while ( mRendererJobs.size() > 0 )
|
|
{
|
|
QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
|
|
j->cancel();
|
|
mRendererJobs.remove( j );
|
|
j->deleteLater();
|
|
}
|
|
mRendererJobs.clear();
|
|
mEventLoop->exit();
|
|
return;
|
|
}
|
|
|
|
if ( mMetaTiles.size() > 0 )
|
|
{
|
|
startJobs();
|
|
}
|
|
else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
|
|
{
|
|
mEventLoop->exit();
|
|
}
|
|
}
|
|
|
|
// Native XYZ tiles (MBTiles) algorithm
|
|
|
|
QString QgsXyzTilesMbtilesAlgorithm::name() const
|
|
{
|
|
return QStringLiteral( "tilesxyzmbtiles" );
|
|
}
|
|
|
|
QString QgsXyzTilesMbtilesAlgorithm::displayName() const
|
|
{
|
|
return QObject::tr( "Generate XYZ tiles (MBTiles)" );
|
|
}
|
|
|
|
QStringList QgsXyzTilesMbtilesAlgorithm::tags() const
|
|
{
|
|
return QObject::tr( "tiles,xyz,tms,mbtiles" ).split( ',' );
|
|
}
|
|
|
|
QString QgsXyzTilesMbtilesAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "Generates XYZ tiles of map canvas content and saves them as an MBTiles file." );
|
|
}
|
|
|
|
QgsXyzTilesMbtilesAlgorithm *QgsXyzTilesMbtilesAlgorithm::createInstance() const
|
|
{
|
|
return new QgsXyzTilesMbtilesAlgorithm();
|
|
}
|
|
|
|
void QgsXyzTilesMbtilesAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
createCommonParameters();
|
|
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_FILE" ), QObject::tr( "Output" ), QObject::tr( "MBTiles files (*.mbtiles *.MBTILES)" ) ) );
|
|
}
|
|
|
|
QVariantMap QgsXyzTilesMbtilesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
const QString outputFile = parameterAsString( parameters, QStringLiteral( "OUTPUT_FILE" ), context );
|
|
|
|
mMbtilesWriter = std::make_unique<QgsMbTiles>( outputFile );
|
|
if ( !mMbtilesWriter->create() )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
|
|
}
|
|
mMbtilesWriter->setMetadataValue( "format", mTileFormat.toLower() );
|
|
mMbtilesWriter->setMetadataValue( "name", QFileInfo( outputFile ).baseName() );
|
|
mMbtilesWriter->setMetadataValue( "version", QStringLiteral( "1.1" ) );
|
|
mMbtilesWriter->setMetadataValue( "type", QStringLiteral( "overlay" ) );
|
|
mMbtilesWriter->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
|
|
mMbtilesWriter->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
|
|
QString boundsStr = QString( "%1,%2,%3,%4" )
|
|
.arg( mWgs84Extent.xMinimum() ).arg( mWgs84Extent.yMinimum() )
|
|
.arg( mWgs84Extent.xMaximum() ).arg( mWgs84Extent.yMaximum() );
|
|
mMbtilesWriter->setMetadataValue( "bounds", boundsStr );
|
|
|
|
for ( int z = mMinZoom; z <= mMaxZoom; z++ )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
break;
|
|
|
|
mMetaTiles += getMetatiles( mWgs84Extent, z, mMetaTileSize );
|
|
}
|
|
|
|
mTotalTiles = mMetaTiles.size();
|
|
|
|
QEventLoop loop;
|
|
// cppcheck-suppress danglingLifetime
|
|
mEventLoop = &loop;
|
|
startJobs();
|
|
loop.exec();
|
|
|
|
QVariantMap results;
|
|
results.insert( QStringLiteral( "OUTPUT_FILE" ), outputFile );
|
|
return results;
|
|
}
|
|
|
|
void QgsXyzTilesMbtilesAlgorithm::processMetaTile( QgsMapRendererSequentialJob *job )
|
|
{
|
|
MetaTile metaTile = mRendererJobs.value( job );
|
|
QImage img = job->renderedImage();
|
|
|
|
QMap<QPair<int, int>, Tile>::const_iterator it = metaTile.tiles.constBegin();
|
|
while ( it != metaTile.tiles.constEnd() )
|
|
{
|
|
QPair<int, int> tm = it.key();
|
|
Tile tile = it.value();
|
|
QImage tileImage = img.copy( mTileWidth * tm.first, mTileHeight * tm.second, mTileWidth, mTileHeight );
|
|
QByteArray ba;
|
|
QBuffer buffer( &ba );
|
|
buffer.open( QIODevice::WriteOnly );
|
|
tileImage.save( &buffer, mTileFormat.toStdString().c_str(), mJpgQuality );
|
|
mMbtilesWriter->setTileData( tile.z, tile.x, tile2tms( tile.y, tile.z ), ba );
|
|
++it;
|
|
}
|
|
|
|
mRendererJobs.remove( job );
|
|
job->deleteLater();
|
|
|
|
mFeedback->setProgress( 100.0 * ( mProcessedTiles++ ) / mTotalTiles );
|
|
|
|
if ( mFeedback->isCanceled() )
|
|
{
|
|
while ( mRendererJobs.size() > 0 )
|
|
{
|
|
QgsMapRendererSequentialJob *j = mRendererJobs.firstKey();
|
|
j->cancel();
|
|
mRendererJobs.remove( j );
|
|
j->deleteLater();
|
|
}
|
|
mRendererJobs.clear();
|
|
mEventLoop->exit();
|
|
return;
|
|
}
|
|
|
|
if ( mMetaTiles.size() > 0 )
|
|
{
|
|
startJobs();
|
|
}
|
|
else if ( mMetaTiles.size() == 0 && mRendererJobs.size() == 0 )
|
|
{
|
|
mEventLoop->exit();
|
|
}
|
|
}
|
|
|
|
///@endcond
|