LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_tile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1623 1666 97.4 %
Date: 2025-06-19 12:30:01 Functions: 49 52 94.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster tile" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_tile.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_mem_cache.h"
      17             : #include "cpl_worker_thread_pool.h"
      18             : #include "gdal_alg_priv.h"
      19             : #include "gdal_priv.h"
      20             : #include "gdalwarper.h"
      21             : #include "gdal_utils.h"
      22             : #include "ogr_spatialref.h"
      23             : #include "memdataset.h"
      24             : #include "tilematrixset.hpp"
      25             : 
      26             : #include <algorithm>
      27             : #include <array>
      28             : #include <atomic>
      29             : #include <cmath>
      30             : #include <mutex>
      31             : 
      32             : //! @cond Doxygen_Suppress
      33             : 
      34             : #ifndef _
      35             : #define _(x) (x)
      36             : #endif
      37             : 
      38             : /************************************************************************/
      39             : /*           GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()         */
      40             : /************************************************************************/
      41             : 
      42          98 : GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()
      43          98 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      44             : {
      45          98 :     AddProgressArg();
      46          98 :     AddOpenOptionsArg(&m_openOptions);
      47          98 :     AddInputFormatsArg(&m_inputFormats)
      48         196 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
      49          98 :     AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER);
      50          98 :     AddOutputFormatArg(&m_outputFormat)
      51          98 :         .SetDefault(m_outputFormat)
      52             :         .AddMetadataItem(
      53             :             GAAMDI_REQUIRED_CAPABILITIES,
      54         490 :             {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
      55         196 :         .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
      56          98 :     AddCreationOptionsArg(&m_creationOptions);
      57             : 
      58         196 :     AddArg("output", 'o', _("Output directory"), &m_outputDirectory)
      59          98 :         .SetRequired()
      60          98 :         .SetMinCharCount(1)
      61          98 :         .SetPositional();
      62             : 
      63         392 :     std::vector<std::string> tilingSchemes{"raster"};
      64         882 :     for (const std::string &scheme :
      65        1862 :          gdal::TileMatrixSet::listPredefinedTileMatrixSets())
      66             :     {
      67        1764 :         auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
      68        1764 :         OGRSpatialReference oSRS_TMS;
      69        1764 :         if (poTMS && !poTMS->hasVariableMatrixWidth() &&
      70         882 :             oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
      71             :         {
      72         882 :             std::string identifier = scheme == "GoogleMapsCompatible"
      73             :                                          ? "WebMercatorQuad"
      74        1764 :                                          : poTMS->identifier();
      75         882 :             m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
      76         882 :             tilingSchemes.push_back(std::move(identifier));
      77             :         }
      78             :     }
      79         196 :     AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
      80          98 :         .SetDefault("WebMercatorQuad")
      81          98 :         .SetChoices(tilingSchemes)
      82             :         .SetHiddenChoices(
      83             :             "GoogleMapsCompatible",  // equivalent of WebMercatorQuad
      84             :             "mercator",              // gdal2tiles equivalent of WebMercatorQuad
      85             :             "geodetic"  // gdal2tiles (not totally) equivalent of WorldCRS84Quad
      86          98 :         );
      87             : 
      88         196 :     AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
      89          98 :         .SetMinValueIncluded(0);
      90         196 :     AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
      91          98 :         .SetMinValueIncluded(0);
      92             : 
      93         196 :     AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
      94          98 :         .SetMinValueIncluded(0);
      95         196 :     AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
      96          98 :         .SetMinValueIncluded(0);
      97         196 :     AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
      98          98 :         .SetMinValueIncluded(0);
      99         196 :     AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
     100          98 :         .SetMinValueIncluded(0);
     101             :     AddArg("no-intersection-ok", 0,
     102             :            _("Whether dataset extent not intersecting tile matrix is only a "
     103             :              "warning"),
     104          98 :            &m_noIntersectionIsOK);
     105             : 
     106             :     AddArg("resampling", 'r', _("Resampling method for max zoom"),
     107         196 :            &m_resampling)
     108             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     109             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     110          98 :                     "sum")
     111          98 :         .SetDefault("cubic")
     112          98 :         .SetHiddenChoices("near");
     113             :     AddArg("overview-resampling", 0, _("Resampling method for overviews"),
     114         196 :            &m_overviewResampling)
     115             :         .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
     116             :                     "average", "rms", "mode", "min", "max", "med", "q1", "q3",
     117          98 :                     "sum")
     118          98 :         .SetHiddenChoices("near");
     119             : 
     120             :     AddArg("convention", 0,
     121             :            _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
     122         196 :            &m_convention)
     123          98 :         .SetDefault(m_convention)
     124          98 :         .SetChoices("xyz", "tms");
     125         196 :     AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
     126          98 :         .SetMinValueIncluded(64)
     127          98 :         .SetMaxValueIncluded(32768);
     128             :     AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
     129         196 :            &m_addalpha)
     130          98 :         .SetMutualExclusionGroup("alpha");
     131             :     AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
     132         196 :            &m_noalpha)
     133          98 :         .SetMutualExclusionGroup("alpha");
     134             :     auto &dstNoDataArg =
     135          98 :         AddArg("dst-nodata", 0, _("Destination nodata value"), &m_dstNoData);
     136          98 :     AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
     137             : 
     138             :     {
     139             :         auto &arg = AddArg("metadata", 0,
     140         196 :                            _("Add metadata item to output tiles"), &m_metadata)
     141         196 :                         .SetMetaVar("<KEY>=<VALUE>")
     142          98 :                         .SetPackedValuesAllowed(false);
     143           4 :         arg.AddValidationAction([this, &arg]()
     144         102 :                                 { return ParseAndValidateKeyValue(arg); });
     145          98 :         arg.AddHiddenAlias("mo");
     146             :     }
     147             :     AddArg("copy-src-metadata", 0,
     148             :            _("Whether to copy metadata from source dataset"),
     149          98 :            &m_copySrcMetadata);
     150             : 
     151             :     AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
     152          98 :            &m_auxXML);
     153          98 :     AddArg("kml", 0, _("Generate KML files"), &m_kml);
     154          98 :     AddArg("resume", 0, _("Generate only missing files"), &m_resume);
     155             : 
     156          98 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     157             : 
     158          98 :     constexpr const char *ADVANCED_RESAMPLING_CATEGORY = "Advanced Resampling";
     159             :     auto &excludedValuesArg =
     160             :         AddArg("excluded-values", 0,
     161             :                _("Tuples of values (e.g. <R>,<G>,<B> or (<R1>,<G1>,<B1>),"
     162             :                  "(<R2>,<G2>,<B2>)) that must beignored as contributing source "
     163             :                  "pixels during (average) resampling"),
     164         196 :                &m_excludedValues)
     165          98 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     166             :     auto &excludedValuesPctThresholdArg =
     167             :         AddArg(
     168             :             "excluded-values-pct-threshold", 0,
     169             :             _("Minimum percentage of source pixels that must be set at one of "
     170             :               "the --excluded-values to cause the excluded value to be used as "
     171             :               "the target pixel value"),
     172         196 :             &m_excludedValuesPctThreshold)
     173          98 :             .SetDefault(m_excludedValuesPctThreshold)
     174          98 :             .SetMinValueIncluded(0)
     175          98 :             .SetMaxValueIncluded(100)
     176          98 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     177             :     auto &nodataValuesPctThresholdArg =
     178             :         AddArg(
     179             :             "nodata-values-pct-threshold", 0,
     180             :             _("Minimum percentage of source pixels that must be set at one of "
     181             :               "nodata (or alpha=0 or any other way to express transparent pixel"
     182             :               "to cause the target pixel value to be transparent"),
     183         196 :             &m_nodataValuesPctThreshold)
     184          98 :             .SetDefault(m_nodataValuesPctThreshold)
     185          98 :             .SetMinValueIncluded(0)
     186          98 :             .SetMaxValueIncluded(100)
     187          98 :             .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
     188             : 
     189          98 :     constexpr const char *PUBLICATION_CATEGORY = "Publication";
     190         196 :     AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
     191          98 :         .SetDefault("all")
     192          98 :         .SetChoices("none", "all", "leaflet", "openlayers", "mapml")
     193          98 :         .SetCategory(PUBLICATION_CATEGORY);
     194             :     AddArg("url", 0,
     195             :            _("URL address where the generated tiles are going to be published"),
     196         196 :            &m_url)
     197          98 :         .SetCategory(PUBLICATION_CATEGORY);
     198         196 :     AddArg("title", 0, _("Title of the map"), &m_title)
     199          98 :         .SetCategory(PUBLICATION_CATEGORY);
     200         196 :     AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
     201          98 :         .SetCategory(PUBLICATION_CATEGORY);
     202             :     AddArg("mapml-template", 0,
     203             :            _("Filename of a template mapml file where variables will be "
     204             :              "substituted"),
     205         196 :            &m_mapmlTemplate)
     206          98 :         .SetMinCharCount(1)
     207          98 :         .SetCategory(PUBLICATION_CATEGORY);
     208             : 
     209          98 :     AddValidationAction(
     210          93 :         [this, &dstNoDataArg, &excludedValuesArg,
     211         564 :          &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
     212             :         {
     213          93 :             if (m_minTileX >= 0 && m_maxTileX >= 0 && m_minTileX > m_maxTileX)
     214             :             {
     215           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     216             :                             "'min-x' must be lesser or equal to 'max-x'");
     217           1 :                 return false;
     218             :             }
     219             : 
     220          92 :             if (m_minTileY >= 0 && m_maxTileY >= 0 && m_minTileY > m_maxTileY)
     221             :             {
     222           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     223             :                             "'min-y' must be lesser or equal to 'max-y'");
     224           1 :                 return false;
     225             :             }
     226             : 
     227          91 :             if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
     228          15 :                 m_minZoomLevel > m_maxZoomLevel)
     229             :             {
     230           1 :                 ReportError(CE_Failure, CPLE_IllegalArg,
     231             :                             "'min-zoom' must be lesser or equal to 'max-zoom'");
     232           1 :                 return false;
     233             :             }
     234             : 
     235          90 :             if (m_addalpha && dstNoDataArg.IsExplicitlySet())
     236             :             {
     237           1 :                 ReportError(
     238             :                     CE_Failure, CPLE_IllegalArg,
     239             :                     "'add-alpha' and 'dst-nodata' are mutually exclusive");
     240           1 :                 return false;
     241             :             }
     242             : 
     243         261 :             for (const auto *arg :
     244             :                  {&excludedValuesArg, &excludedValuesPctThresholdArg,
     245         350 :                   &nodataValuesPctThresholdArg})
     246             :             {
     247         263 :                 if (arg->IsExplicitlySet() && m_resampling != "average")
     248             :                 {
     249           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     250             :                                 "'%s' can only be specified if 'resampling' is "
     251             :                                 "set to 'average'",
     252           1 :                                 arg->GetName().c_str());
     253           2 :                     return false;
     254             :                 }
     255         263 :                 if (arg->IsExplicitlySet() && !m_overviewResampling.empty() &&
     256           1 :                     m_overviewResampling != "average")
     257             :                 {
     258           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     259             :                                 "'%s' can only be specified if "
     260             :                                 "'overview-resampling' is set to 'average'",
     261           1 :                                 arg->GetName().c_str());
     262           1 :                     return false;
     263             :                 }
     264             :             }
     265             : 
     266          87 :             return true;
     267             :         });
     268          98 : }
     269             : 
     270             : /************************************************************************/
     271             : /*                          GetTileIndices()                            */
     272             : /************************************************************************/
     273             : 
     274         177 : static bool GetTileIndices(gdal::TileMatrixSet::TileMatrix &tileMatrix,
     275             :                            bool bInvertAxisTMS, int tileSize,
     276             :                            const double adfExtent[4], int &nMinTileX,
     277             :                            int &nMinTileY, int &nMaxTileX, int &nMaxTileY,
     278             :                            bool noIntersectionIsOK, bool &bIntersects,
     279             :                            bool checkRasterOverflow = true)
     280             : {
     281         177 :     if (tileSize > 0)
     282             :     {
     283          18 :         tileMatrix.mResX *=
     284          18 :             static_cast<double>(tileMatrix.mTileWidth) / tileSize;
     285          18 :         tileMatrix.mResY *=
     286          18 :             static_cast<double>(tileMatrix.mTileHeight) / tileSize;
     287          18 :         tileMatrix.mTileWidth = tileSize;
     288          18 :         tileMatrix.mTileHeight = tileSize;
     289             :     }
     290             : 
     291         177 :     if (bInvertAxisTMS)
     292           2 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
     293             : 
     294         177 :     const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
     295         177 :     const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
     296             : 
     297         177 :     constexpr double EPSILON = 1e-3;
     298         177 :     const double dfMinTileX =
     299         177 :         (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
     300         177 :     nMinTileX = static_cast<int>(
     301         354 :         std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
     302         177 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     303         177 :     const double dfMinTileY =
     304         177 :         (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
     305         177 :     nMinTileY = static_cast<int>(
     306         354 :         std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
     307         177 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     308         177 :     const double dfMaxTileX =
     309         177 :         (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
     310         177 :     nMaxTileX = static_cast<int>(
     311         354 :         std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
     312         177 :                    static_cast<double>(tileMatrix.mMatrixWidth - 1)));
     313         177 :     const double dfMaxTileY =
     314         177 :         (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
     315         177 :     nMaxTileY = static_cast<int>(
     316         354 :         std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
     317         177 :                    static_cast<double>(tileMatrix.mMatrixHeight - 1)));
     318             : 
     319         177 :     bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
     320         354 :                    dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
     321         177 :     if (!bIntersects)
     322             :     {
     323           2 :         CPLDebug("gdal_raster_tile",
     324             :                  "dfMinTileX=%g dfMinTileY=%g dfMaxTileX=%g dfMaxTileY=%g",
     325             :                  dfMinTileX, dfMinTileY, dfMaxTileX, dfMaxTileY);
     326           2 :         CPLError(noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
     327             :                  "Extent of source dataset is not compatible with extent of "
     328             :                  "tile matrix %s",
     329             :                  tileMatrix.mId.c_str());
     330           2 :         return noIntersectionIsOK;
     331             :     }
     332         175 :     if (checkRasterOverflow)
     333             :     {
     334         107 :         if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
     335         107 :             nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
     336             :         {
     337           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
     338           0 :             return false;
     339             :         }
     340             :     }
     341         175 :     return true;
     342             : }
     343             : 
     344             : /************************************************************************/
     345             : /*                           GetFileY()                                 */
     346             : /************************************************************************/
     347             : 
     348        1981 : static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     349             :                     const std::string &convention)
     350             : {
     351        1981 :     return convention == "xyz" ? iY : tileMatrix.mMatrixHeight - 1 - iY;
     352             : }
     353             : 
     354             : /************************************************************************/
     355             : /*                          GenerateTile()                              */
     356             : /************************************************************************/
     357             : 
     358         263 : static bool GenerateTile(
     359             :     GDALDataset *poSrcDS, GDALDriver *poDstDriver, const char *pszExtension,
     360             :     CSLConstList creationOptions, GDALWarpOperation &oWO,
     361             :     const OGRSpatialReference &oSRS_TMS, GDALDataType eWorkingDataType,
     362             :     const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     363             :     const std::string &outputDirectory, int nBands, const double *pdfDstNoData,
     364             :     int nZoomLevel, int iX, int iY, const std::string &convention,
     365             :     int nMinTileX, int nMinTileY, bool bSkipBlank, bool bUserAskedForAlpha,
     366             :     bool bAuxXML, bool bResume, const std::vector<std::string> &metadata,
     367             :     const GDALColorTable *poColorTable, std::vector<GByte> &dstBuffer)
     368             : {
     369             :     const std::string osDirZ = CPLFormFilenameSafe(
     370         526 :         outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
     371             :     const std::string osDirX =
     372         526 :         CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
     373         263 :     const int iFileY = GetFileY(iY, tileMatrix, convention);
     374             :     const std::string osFilename = CPLFormFilenameSafe(
     375         526 :         osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
     376             : 
     377         263 :     if (bResume)
     378             :     {
     379             :         VSIStatBufL sStat;
     380           5 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     381           5 :             return true;
     382             :     }
     383             : 
     384         258 :     const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
     385         258 :     const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
     386         258 :     memset(dstBuffer.data(), 0, dstBuffer.size());
     387         516 :     const CPLErr eErr = oWO.WarpRegionToBuffer(
     388         258 :         nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     389         258 :         dstBuffer.data(), eWorkingDataType);
     390         258 :     if (eErr != CE_None)
     391           0 :         return false;
     392             : 
     393             :     const bool bDstHasAlpha =
     394         287 :         nBands > poSrcDS->GetRasterCount() ||
     395          29 :         (nBands == poSrcDS->GetRasterCount() &&
     396          28 :          poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
     397         258 :              GCI_AlphaBand);
     398         258 :     const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
     399         258 :                                  tileMatrix.mTileHeight *
     400         258 :                                  GDALGetDataTypeSizeBytes(eWorkingDataType);
     401         258 :     if (bDstHasAlpha && bSkipBlank)
     402             :     {
     403           9 :         bool bBlank = true;
     404      321952 :         for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
     405             :         {
     406      322022 :             bBlank = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 0);
     407             :         }
     408           0 :         if (bBlank)
     409           5 :             return true;
     410             :     }
     411         174 :     if (bDstHasAlpha && !bUserAskedForAlpha)
     412             :     {
     413         238 :         bool bAllOpaque = true;
     414    11162700 :         for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
     415             :         {
     416    11174300 :             bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
     417             :         }
     418           0 :         if (bAllOpaque)
     419         163 :             nBands--;
     420             :     }
     421             : 
     422           0 :     VSIMkdir(osDirZ.c_str(), 0755);
     423         253 :     VSIMkdir(osDirX.c_str(), 0755);
     424             : 
     425             :     auto memDS = std::unique_ptr<GDALDataset>(
     426         253 :         MEMDataset::Create("", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0,
     427         506 :                            eWorkingDataType, nullptr));
     428        1023 :     for (int i = 0; i < nBands; ++i)
     429             :     {
     430         770 :         char szBuffer[32] = {'\0'};
     431        1540 :         int nRet = CPLPrintPointer(
     432         770 :             szBuffer, dstBuffer.data() + i * nBytesPerBand, sizeof(szBuffer));
     433         770 :         szBuffer[nRet] = 0;
     434             : 
     435         770 :         char szOption[64] = {'\0'};
     436         770 :         snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s", szBuffer);
     437             : 
     438         770 :         char *apszOptions[] = {szOption, nullptr};
     439             : 
     440         770 :         memDS->AddBand(eWorkingDataType, apszOptions);
     441         770 :         auto poDstBand = memDS->GetRasterBand(i + 1);
     442         770 :         if (i + 1 <= poSrcDS->GetRasterCount())
     443         695 :             poDstBand->SetColorInterpretation(
     444         695 :                 poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation());
     445             :         else
     446          75 :             poDstBand->SetColorInterpretation(GCI_AlphaBand);
     447         770 :         if (pdfDstNoData)
     448           9 :             poDstBand->SetNoDataValue(*pdfDstNoData);
     449         770 :         if (i == 0 && poColorTable)
     450           1 :             poDstBand->SetColorTable(
     451           1 :                 const_cast<GDALColorTable *>(poColorTable));
     452             :     }
     453         506 :     const CPLStringList aosMD(metadata);
     454         261 :     for (const auto [key, value] : cpl::IterateNameValue(aosMD))
     455             :     {
     456           8 :         memDS->SetMetadataItem(key, value);
     457             :     }
     458             : 
     459             :     double adfGT[6];
     460         252 :     adfGT[0] =
     461         252 :         tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
     462         252 :     adfGT[1] = tileMatrix.mResX;
     463         252 :     adfGT[2] = 0;
     464         252 :     adfGT[3] =
     465         252 :         tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
     466         252 :     adfGT[4] = 0;
     467         252 :     adfGT[5] = -tileMatrix.mResY;
     468         252 :     memDS->SetGeoTransform(adfGT);
     469             : 
     470         253 :     memDS->SetSpatialRef(&oSRS_TMS);
     471             : 
     472             :     CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
     473         506 :                                   false);
     474             : 
     475         505 :     const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
     476             : 
     477             :     std::unique_ptr<GDALDataset> poOutDS(
     478             :         poDstDriver->CreateCopy(osTmpFilename.c_str(), memDS.get(), false,
     479         253 :                                 creationOptions, nullptr, nullptr));
     480         253 :     bool bRet = poOutDS && poOutDS->Close() == CE_None;
     481         253 :     poOutDS.reset();
     482         253 :     if (bRet)
     483             :     {
     484         236 :         bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
     485         236 :         if (bAuxXML)
     486             :         {
     487           4 :             VSIRename((osTmpFilename + ".aux.xml").c_str(),
     488           8 :                       (osFilename + ".aux.xml").c_str());
     489             :         }
     490             :     }
     491             :     else
     492             :     {
     493          17 :         VSIUnlink(osTmpFilename.c_str());
     494             :     }
     495         253 :     return bRet;
     496             : }
     497             : 
     498             : /************************************************************************/
     499             : /*                    GenerateOverviewTile()                            */
     500             : /************************************************************************/
     501             : 
     502             : static bool
     503          71 : GenerateOverviewTile(GDALDataset &oSrcDS, GDALDriver *poDstDriver,
     504             :                      const std::string &outputFormat, const char *pszExtension,
     505             :                      CSLConstList creationOptions,
     506             :                      CSLConstList papszWarpOptions,
     507             :                      const std::string &resampling,
     508             :                      const gdal::TileMatrixSet::TileMatrix &tileMatrix,
     509             :                      const std::string &outputDirectory, int nZoomLevel, int iX,
     510             :                      int iY, const std::string &convention, bool bSkipBlank,
     511             :                      bool bUserAskedForAlpha, bool bAuxXML, bool bResume)
     512             : {
     513             :     const std::string osDirZ = CPLFormFilenameSafe(
     514         142 :         outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
     515             :     const std::string osDirX =
     516         142 :         CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
     517             : 
     518          71 :     const int iFileY = GetFileY(iY, tileMatrix, convention);
     519             :     const std::string osFilename = CPLFormFilenameSafe(
     520         142 :         osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
     521             : 
     522          71 :     if (bResume)
     523             :     {
     524             :         VSIStatBufL sStat;
     525           2 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     526           1 :             return true;
     527             :     }
     528             : 
     529          70 :     VSIMkdir(osDirZ.c_str(), 0755);
     530          70 :     VSIMkdir(osDirX.c_str(), 0755);
     531             : 
     532         140 :     CPLStringList aosOptions;
     533             : 
     534          70 :     aosOptions.AddString("-of");
     535          70 :     aosOptions.AddString(outputFormat.c_str());
     536             : 
     537          94 :     for (const char *pszCO : cpl::Iterate(creationOptions))
     538             :     {
     539          24 :         aosOptions.AddString("-co");
     540          24 :         aosOptions.AddString(pszCO);
     541             :     }
     542             :     CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
     543         140 :                                   false);
     544             : 
     545          70 :     aosOptions.AddString("-r");
     546          70 :     aosOptions.AddString(resampling.c_str());
     547             : 
     548          70 :     std::unique_ptr<GDALDataset> poOutDS;
     549          70 :     const double dfMinX =
     550          70 :         tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
     551          70 :     const double dfMaxY =
     552          70 :         tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
     553          70 :     const double dfMaxX = dfMinX + tileMatrix.mResX * tileMatrix.mTileWidth;
     554          70 :     const double dfMinY = dfMaxY - tileMatrix.mResY * tileMatrix.mTileHeight;
     555             : 
     556             :     const bool resamplingCompatibleOfTranslate =
     557         199 :         papszWarpOptions == nullptr &&
     558         190 :         (resampling == "nearest" || resampling == "average" ||
     559         125 :          resampling == "bilinear" || resampling == "cubic" ||
     560           9 :          resampling == "cubicspline" || resampling == "lanczos" ||
     561           3 :          resampling == "mode");
     562             : 
     563         140 :     const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
     564             : 
     565          70 :     if (resamplingCompatibleOfTranslate)
     566             :     {
     567             :         double adfUpperGT[6];
     568          64 :         oSrcDS.GetGeoTransform(adfUpperGT);
     569          64 :         const double dfMinXUpper = adfUpperGT[0];
     570             :         const double dfMaxXUpper =
     571          64 :             dfMinXUpper + adfUpperGT[1] * oSrcDS.GetRasterXSize();
     572          64 :         const double dfMaxYUpper = adfUpperGT[3];
     573             :         const double dfMinYUpper =
     574          64 :             dfMaxYUpper + adfUpperGT[5] * oSrcDS.GetRasterYSize();
     575          64 :         if (dfMinX >= dfMinXUpper && dfMaxX <= dfMaxXUpper &&
     576          48 :             dfMinY >= dfMinYUpper && dfMaxY <= dfMaxYUpper)
     577             :         {
     578             :             // If the overview tile is fully within the extent of the
     579             :             // upper zoom level, we can use GDALDataset::RasterIO() directly.
     580             : 
     581          48 :             const auto eDT = oSrcDS.GetRasterBand(1)->GetRasterDataType();
     582             :             const size_t nBytesPerBand =
     583          48 :                 static_cast<size_t>(tileMatrix.mTileWidth) *
     584          48 :                 tileMatrix.mTileHeight * GDALGetDataTypeSizeBytes(eDT);
     585             :             std::vector<GByte> dstBuffer(nBytesPerBand *
     586          48 :                                          oSrcDS.GetRasterCount());
     587             : 
     588          48 :             const double dfXOff = (dfMinX - dfMinXUpper) / adfUpperGT[1];
     589          48 :             const double dfYOff = (dfMaxYUpper - dfMaxY) / -adfUpperGT[5];
     590          48 :             const double dfXSize = (dfMaxX - dfMinX) / adfUpperGT[1];
     591          48 :             const double dfYSize = (dfMaxY - dfMinY) / -adfUpperGT[5];
     592             :             GDALRasterIOExtraArg sExtraArg;
     593          48 :             INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     594          48 :             CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
     595          48 :             sExtraArg.eResampleAlg =
     596          48 :                 GDALRasterIOGetResampleAlg(resampling.c_str());
     597          48 :             sExtraArg.dfXOff = dfXOff;
     598          48 :             sExtraArg.dfYOff = dfYOff;
     599          48 :             sExtraArg.dfXSize = dfXSize;
     600          48 :             sExtraArg.dfYSize = dfYSize;
     601          48 :             sExtraArg.bFloatingPointWindowValidity =
     602          48 :                 sExtraArg.eResampleAlg != GRIORA_NearestNeighbour;
     603          48 :             constexpr double EPSILON = 1e-3;
     604          48 :             if (oSrcDS.RasterIO(GF_Read, static_cast<int>(dfXOff + EPSILON),
     605          48 :                                 static_cast<int>(dfYOff + EPSILON),
     606          48 :                                 static_cast<int>(dfXSize + 0.5),
     607          48 :                                 static_cast<int>(dfYSize + 0.5),
     608          48 :                                 dstBuffer.data(), tileMatrix.mTileWidth,
     609          48 :                                 tileMatrix.mTileHeight, eDT,
     610             :                                 oSrcDS.GetRasterCount(), nullptr, 0, 0, 0,
     611          48 :                                 &sExtraArg) == CE_None)
     612             :             {
     613          47 :                 int nDstBands = oSrcDS.GetRasterCount();
     614             :                 const bool bDstHasAlpha =
     615          47 :                     oSrcDS.GetRasterBand(nDstBands)->GetColorInterpretation() ==
     616          47 :                     GCI_AlphaBand;
     617          47 :                 if (bDstHasAlpha && bSkipBlank)
     618             :                 {
     619           2 :                     bool bBlank = true;
     620       65545 :                     for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
     621             :                     {
     622       65543 :                         bBlank =
     623       65543 :                             (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
     624             :                              0);
     625             :                     }
     626           2 :                     if (bBlank)
     627           1 :                         return true;
     628           1 :                     bSkipBlank = false;
     629             :                 }
     630          46 :                 if (bDstHasAlpha && !bUserAskedForAlpha)
     631             :                 {
     632          46 :                     bool bAllOpaque = true;
     633     2818100 :                     for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
     634             :                     {
     635     2818050 :                         bAllOpaque =
     636     2818050 :                             (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
     637             :                              255);
     638             :                     }
     639          46 :                     if (bAllOpaque)
     640          43 :                         nDstBands--;
     641             :                 }
     642             : 
     643          92 :                 auto memDS = std::unique_ptr<GDALDataset>(MEMDataset::Create(
     644          46 :                     "", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0, eDT,
     645          92 :                     nullptr));
     646         187 :                 for (int i = 0; i < nDstBands; ++i)
     647             :                 {
     648         141 :                     char szBuffer[32] = {'\0'};
     649         282 :                     int nRet = CPLPrintPointer(
     650         141 :                         szBuffer, dstBuffer.data() + i * nBytesPerBand,
     651             :                         sizeof(szBuffer));
     652         141 :                     szBuffer[nRet] = 0;
     653             : 
     654         141 :                     char szOption[64] = {'\0'};
     655         141 :                     snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s",
     656             :                              szBuffer);
     657             : 
     658         141 :                     char *apszOptions[] = {szOption, nullptr};
     659             : 
     660         141 :                     memDS->AddBand(eDT, apszOptions);
     661         141 :                     auto poSrcBand = oSrcDS.GetRasterBand(i + 1);
     662         141 :                     auto poDstBand = memDS->GetRasterBand(i + 1);
     663         141 :                     poDstBand->SetColorInterpretation(
     664         141 :                         poSrcBand->GetColorInterpretation());
     665         141 :                     int bHasNoData = false;
     666             :                     const double dfNoData =
     667         141 :                         poSrcBand->GetNoDataValue(&bHasNoData);
     668         141 :                     if (bHasNoData)
     669           0 :                         poDstBand->SetNoDataValue(dfNoData);
     670         141 :                     if (auto poCT = poSrcBand->GetColorTable())
     671           0 :                         poDstBand->SetColorTable(poCT);
     672             :                 }
     673          46 :                 memDS->SetMetadata(oSrcDS.GetMetadata());
     674             :                 double adfGT[6];
     675          46 :                 adfGT[0] = dfMinX;
     676          46 :                 adfGT[1] = tileMatrix.mResX;
     677          46 :                 adfGT[2] = 0;
     678          46 :                 adfGT[3] = dfMaxY;
     679          46 :                 adfGT[4] = 0;
     680          46 :                 adfGT[5] = -tileMatrix.mResY;
     681          46 :                 memDS->SetGeoTransform(adfGT);
     682             : 
     683          46 :                 memDS->SetSpatialRef(oSrcDS.GetSpatialRef());
     684             : 
     685          46 :                 poOutDS.reset(poDstDriver->CreateCopy(
     686             :                     osTmpFilename.c_str(), memDS.get(), false, creationOptions,
     687             :                     nullptr, nullptr));
     688          47 :             }
     689             :         }
     690             :         else
     691             :         {
     692             :             // If the overview tile is not fully within the extent of the
     693             :             // upper zoom level, use GDALTranslate() to use VRT padding
     694             : 
     695          16 :             aosOptions.AddString("-q");
     696             : 
     697          16 :             aosOptions.AddString("-projwin");
     698          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
     699          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
     700          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
     701          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
     702             : 
     703          16 :             aosOptions.AddString("-outsize");
     704          16 :             aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
     705          16 :             aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
     706             : 
     707             :             GDALTranslateOptions *psOptions =
     708          16 :                 GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     709          16 :             poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
     710             :                 osTmpFilename.c_str(), GDALDataset::ToHandle(&oSrcDS),
     711             :                 psOptions, nullptr)));
     712          16 :             GDALTranslateOptionsFree(psOptions);
     713             :         }
     714             :     }
     715             :     else
     716             :     {
     717           6 :         aosOptions.AddString("-te");
     718           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
     719           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
     720           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
     721           6 :         aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
     722             : 
     723           6 :         aosOptions.AddString("-ts");
     724           6 :         aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
     725           6 :         aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
     726             : 
     727          11 :         for (int i = 0; papszWarpOptions && papszWarpOptions[i]; ++i)
     728             :         {
     729           5 :             aosOptions.AddString("-wo");
     730           5 :             aosOptions.AddString(papszWarpOptions[i]);
     731             :         }
     732             : 
     733             :         GDALWarpAppOptions *psOptions =
     734           6 :             GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
     735           6 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(&oSrcDS);
     736           6 :         poOutDS.reset(GDALDataset::FromHandle(GDALWarp(
     737             :             osTmpFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr)));
     738           6 :         GDALWarpAppOptionsFree(psOptions);
     739             :     }
     740             : 
     741          69 :     bool bRet = poOutDS != nullptr;
     742          69 :     if (bRet && bSkipBlank)
     743             :     {
     744          10 :         auto poLastBand = poOutDS->GetRasterBand(poOutDS->GetRasterCount());
     745          10 :         if (poLastBand->GetColorInterpretation() == GCI_AlphaBand)
     746             :         {
     747             :             std::vector<GByte> buffer(
     748           1 :                 static_cast<size_t>(tileMatrix.mTileWidth) *
     749           1 :                 tileMatrix.mTileHeight *
     750           1 :                 GDALGetDataTypeSizeBytes(poLastBand->GetRasterDataType()));
     751           2 :             CPL_IGNORE_RET_VAL(poLastBand->RasterIO(
     752           1 :                 GF_Read, 0, 0, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     753           1 :                 buffer.data(), tileMatrix.mTileWidth, tileMatrix.mTileHeight,
     754             :                 poLastBand->GetRasterDataType(), 0, 0, nullptr));
     755           1 :             bool bBlank = true;
     756       65537 :             for (size_t i = 0; i < buffer.size() && bBlank; ++i)
     757             :             {
     758       65536 :                 bBlank = (buffer[i] == 0);
     759             :             }
     760           1 :             if (bBlank)
     761             :             {
     762           1 :                 poOutDS.reset();
     763           1 :                 VSIUnlink(osTmpFilename.c_str());
     764           1 :                 if (bAuxXML)
     765           0 :                     VSIUnlink((osTmpFilename + ".aux.xml").c_str());
     766           1 :                 return true;
     767             :             }
     768             :         }
     769             :     }
     770          68 :     bRet = bRet && poOutDS->Close() == CE_None;
     771          68 :     poOutDS.reset();
     772          68 :     if (bRet)
     773             :     {
     774          67 :         bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
     775          67 :         if (bAuxXML)
     776             :         {
     777           4 :             VSIRename((osTmpFilename + ".aux.xml").c_str(),
     778           8 :                       (osFilename + ".aux.xml").c_str());
     779             :         }
     780             :     }
     781             :     else
     782             :     {
     783           1 :         VSIUnlink(osTmpFilename.c_str());
     784             :     }
     785          68 :     return bRet;
     786             : }
     787             : 
     788             : namespace
     789             : {
     790             : 
     791             : /************************************************************************/
     792             : /*                     FakeMaxZoomRasterBand                            */
     793             : /************************************************************************/
     794             : 
     795             : class FakeMaxZoomRasterBand : public GDALRasterBand
     796             : {
     797             :     void *m_pDstBuffer = nullptr;
     798             :     CPL_DISALLOW_COPY_ASSIGN(FakeMaxZoomRasterBand)
     799             : 
     800             :   public:
     801         601 :     FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
     802             :                           int nBlockXSizeIn, int nBlockYSizeIn,
     803             :                           GDALDataType eDT, void *pDstBuffer)
     804         601 :         : m_pDstBuffer(pDstBuffer)
     805             :     {
     806         601 :         nBand = nBandIn;
     807         601 :         nRasterXSize = nWidth;
     808         601 :         nRasterYSize = nHeight;
     809         601 :         nBlockXSize = nBlockXSizeIn;
     810         601 :         nBlockYSize = nBlockYSizeIn;
     811         601 :         eDataType = eDT;
     812         601 :     }
     813             : 
     814           0 :     CPLErr IReadBlock(int, int, void *) override
     815             :     {
     816           0 :         CPLAssert(false);
     817             :         return CE_Failure;
     818             :     }
     819             : 
     820             : #ifdef DEBUG
     821           0 :     CPLErr IWriteBlock(int, int, void *) override
     822             :     {
     823           0 :         CPLAssert(false);
     824             :         return CE_Failure;
     825             :     }
     826             : #endif
     827             : 
     828         490 :     CPLErr IRasterIO(GDALRWFlag eRWFlag, [[maybe_unused]] int nXOff,
     829             :                      [[maybe_unused]] int nYOff, [[maybe_unused]] int nXSize,
     830             :                      [[maybe_unused]] int nYSize, void *pData,
     831             :                      [[maybe_unused]] int nBufXSize,
     832             :                      [[maybe_unused]] int nBufYSize, GDALDataType eBufType,
     833             :                      GSpacing nPixelSpace, [[maybe_unused]] GSpacing nLineSpace,
     834             :                      GDALRasterIOExtraArg *) override
     835             :     {
     836             :         // For sake of implementation simplicity, check various assumptions of
     837             :         // how GDALAlphaMask code does I/O
     838         490 :         CPLAssert((nXOff % nBlockXSize) == 0);
     839         490 :         CPLAssert((nYOff % nBlockYSize) == 0);
     840         490 :         CPLAssert(nXSize == nBufXSize);
     841         490 :         CPLAssert(nXSize == nBlockXSize);
     842         490 :         CPLAssert(nYSize == nBufYSize);
     843         490 :         CPLAssert(nYSize == nBlockYSize);
     844         490 :         CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
     845         490 :         CPLAssert(
     846             :             nBand ==
     847             :             poDS->GetRasterCount());  // only alpha band is accessed this way
     848         490 :         if (eRWFlag == GF_Read)
     849             :         {
     850         245 :             double dfZero = 0;
     851         245 :             GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
     852             :                             static_cast<int>(nPixelSpace),
     853         245 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
     854             :         }
     855             :         else
     856             :         {
     857         245 :             GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
     858             :                             m_pDstBuffer, eDataType,
     859             :                             GDALGetDataTypeSizeBytes(eDataType),
     860         245 :                             static_cast<size_t>(nBlockXSize) * nBlockYSize);
     861             :         }
     862         490 :         return CE_None;
     863             :     }
     864             : };
     865             : 
     866             : /************************************************************************/
     867             : /*                       FakeMaxZoomDataset                             */
     868             : /************************************************************************/
     869             : 
     870             : // This class is used to create a fake output dataset for GDALWarpOperation.
     871             : // In particular we need to implement GDALRasterBand::IRasterIO(GF_Write, ...)
     872             : // to catch writes (of one single tile) to the alpha band and redirect them
     873             : // to the dstBuffer passed to FakeMaxZoomDataset constructor.
     874             : 
     875             : class FakeMaxZoomDataset : public GDALDataset
     876             : {
     877             :     const int m_nBlockXSize;
     878             :     const int m_nBlockYSize;
     879             :     const OGRSpatialReference m_oSRS;
     880             :     double m_adfGT[6];
     881             : 
     882             :   public:
     883         172 :     FakeMaxZoomDataset(int nWidth, int nHeight, int nBandsIn, int nBlockXSize,
     884             :                        int nBlockYSize, GDALDataType eDT, const double adfGT[6],
     885             :                        const OGRSpatialReference &oSRS,
     886             :                        std::vector<GByte> &dstBuffer)
     887         172 :         : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS)
     888             :     {
     889         172 :         eAccess = GA_Update;
     890         172 :         nRasterXSize = nWidth;
     891         172 :         nRasterYSize = nHeight;
     892         172 :         memcpy(m_adfGT, adfGT, sizeof(double) * 6);
     893         773 :         for (int i = 1; i <= nBandsIn; ++i)
     894             :         {
     895         601 :             SetBand(i,
     896             :                     new FakeMaxZoomRasterBand(
     897             :                         i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
     898         601 :                         dstBuffer.data() + static_cast<size_t>(i - 1) *
     899        1202 :                                                nBlockXSize * nBlockYSize *
     900         601 :                                                GDALGetDataTypeSizeBytes(eDT)));
     901             :         }
     902         172 :     }
     903             : 
     904         296 :     const OGRSpatialReference *GetSpatialRef() const override
     905             :     {
     906         296 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     907             :     }
     908             : 
     909          60 :     CPLErr GetGeoTransform(double *padfGT) override
     910             :     {
     911          60 :         memcpy(padfGT, m_adfGT, sizeof(double) * 6);
     912          60 :         return CE_None;
     913             :     }
     914             : 
     915             :     using GDALDataset::Clone;
     916             : 
     917             :     std::unique_ptr<FakeMaxZoomDataset>
     918         112 :     Clone(std::vector<GByte> &dstBuffer) const
     919             :     {
     920             :         return std::make_unique<FakeMaxZoomDataset>(
     921         112 :             nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
     922         224 :             GetRasterBand(1)->GetRasterDataType(), m_adfGT, m_oSRS, dstBuffer);
     923             :     }
     924             : };
     925             : 
     926             : /************************************************************************/
     927             : /*                          MosaicRasterBand                            */
     928             : /************************************************************************/
     929             : 
     930             : class MosaicRasterBand : public GDALRasterBand
     931             : {
     932             :     const int m_tileMinX;
     933             :     const int m_tileMinY;
     934             :     const GDALColorInterp m_eColorInterp;
     935             :     const gdal::TileMatrixSet::TileMatrix m_oTM;
     936             :     const std::string m_convention;
     937             :     const std::string m_directory;
     938             :     const std::string m_extension;
     939             :     const bool m_hasNoData;
     940             :     const double m_noData;
     941             :     std::unique_ptr<GDALColorTable> m_poColorTable{};
     942             : 
     943             :   public:
     944         158 :     MosaicRasterBand(GDALDataset *poDSIn, int nBandIn, int nWidth, int nHeight,
     945             :                      int nBlockXSizeIn, int nBlockYSizeIn, GDALDataType eDT,
     946             :                      GDALColorInterp eColorInterp, int nTileMinX, int nTileMinY,
     947             :                      const gdal::TileMatrixSet::TileMatrix &oTM,
     948             :                      const std::string &convention,
     949             :                      const std::string &directory, const std::string &extension,
     950             :                      const double *pdfDstNoData,
     951             :                      const GDALColorTable *poColorTable)
     952         158 :         : m_tileMinX(nTileMinX), m_tileMinY(nTileMinY),
     953             :           m_eColorInterp(eColorInterp), m_oTM(oTM), m_convention(convention),
     954             :           m_directory(directory), m_extension(extension),
     955         158 :           m_hasNoData(pdfDstNoData != nullptr),
     956         158 :           m_noData(pdfDstNoData ? *pdfDstNoData : 0),
     957         316 :           m_poColorTable(poColorTable ? poColorTable->Clone() : nullptr)
     958             :     {
     959         158 :         poDS = poDSIn;
     960         158 :         nBand = nBandIn;
     961         158 :         nRasterXSize = nWidth;
     962         158 :         nRasterYSize = nHeight;
     963         158 :         nBlockXSize = nBlockXSizeIn;
     964         158 :         nBlockYSize = nBlockYSizeIn;
     965         158 :         eDataType = eDT;
     966         158 :     }
     967             : 
     968             :     CPLErr IReadBlock(int nXBlock, int nYBlock, void *pData) override;
     969             : 
     970        2024 :     GDALColorTable *GetColorTable() override
     971             :     {
     972        2024 :         return m_poColorTable.get();
     973             :     }
     974             : 
     975         325 :     GDALColorInterp GetColorInterpretation() override
     976             :     {
     977         325 :         return m_eColorInterp;
     978             :     }
     979             : 
     980        1635 :     double GetNoDataValue(int *pbHasNoData) override
     981             :     {
     982        1635 :         if (pbHasNoData)
     983        1626 :             *pbHasNoData = m_hasNoData;
     984        1635 :         return m_noData;
     985             :     }
     986             : };
     987             : 
     988             : /************************************************************************/
     989             : /*                         MosaicDataset                                */
     990             : /************************************************************************/
     991             : 
     992             : // This class is to expose the tiles of a given level as a mosaic that
     993             : // can be used as a source to generate the immediately below zoom level.
     994             : 
     995             : class MosaicDataset : public GDALDataset
     996             : {
     997             :     friend class MosaicRasterBand;
     998             : 
     999             :     const std::string m_directory;
    1000             :     const std::string m_extension;
    1001             :     const std::string m_format;
    1002             :     GDALDataset *const m_poSrcDS;
    1003             :     const gdal::TileMatrixSet::TileMatrix &m_oTM;
    1004             :     const OGRSpatialReference m_oSRS;
    1005             :     const int m_nTileMinX;
    1006             :     const int m_nTileMinY;
    1007             :     const int m_nTileMaxX;
    1008             :     const int m_nTileMaxY;
    1009             :     const std::string m_convention;
    1010             :     const GDALDataType m_eDT;
    1011             :     const double *const m_pdfDstNoData;
    1012             :     const std::vector<std::string> &m_metadata;
    1013             :     const GDALColorTable *const m_poCT;
    1014             : 
    1015             :     double m_adfGT[6];
    1016             :     lru11::Cache<std::string, std::shared_ptr<GDALDataset>> m_oCacheTile{};
    1017             : 
    1018             :     CPL_DISALLOW_COPY_ASSIGN(MosaicDataset)
    1019             : 
    1020             :   public:
    1021          51 :     MosaicDataset(const std::string &directory, const std::string &extension,
    1022             :                   const std::string &format, GDALDataset *poSrcDS,
    1023             :                   const gdal::TileMatrixSet::TileMatrix &oTM,
    1024             :                   const OGRSpatialReference &oSRS, int nTileMinX, int nTileMinY,
    1025             :                   int nTileMaxX, int nTileMaxY, const std::string &convention,
    1026             :                   int nBandsIn, GDALDataType eDT, const double *pdfDstNoData,
    1027             :                   const std::vector<std::string> &metadata,
    1028             :                   const GDALColorTable *poCT)
    1029          51 :         : m_directory(directory), m_extension(extension), m_format(format),
    1030             :           m_poSrcDS(poSrcDS), m_oTM(oTM), m_oSRS(oSRS), m_nTileMinX(nTileMinX),
    1031             :           m_nTileMinY(nTileMinY), m_nTileMaxX(nTileMaxX),
    1032             :           m_nTileMaxY(nTileMaxY), m_convention(convention), m_eDT(eDT),
    1033          51 :           m_pdfDstNoData(pdfDstNoData), m_metadata(metadata), m_poCT(poCT)
    1034             :     {
    1035          51 :         nRasterXSize = (nTileMaxX - nTileMinX + 1) * oTM.mTileWidth;
    1036          51 :         nRasterYSize = (nTileMaxY - nTileMinY + 1) * oTM.mTileHeight;
    1037          51 :         m_adfGT[0] = oTM.mTopLeftX + nTileMinX * oTM.mResX * oTM.mTileWidth;
    1038          51 :         m_adfGT[1] = oTM.mResX;
    1039          51 :         m_adfGT[2] = 0;
    1040          51 :         m_adfGT[3] = oTM.mTopLeftY - nTileMinY * oTM.mResY * oTM.mTileHeight;
    1041          51 :         m_adfGT[4] = 0;
    1042          51 :         m_adfGT[5] = -oTM.mResY;
    1043         209 :         for (int i = 1; i <= nBandsIn; ++i)
    1044             :         {
    1045             :             const GDALColorInterp eColorInterp =
    1046         158 :                 (i <= poSrcDS->GetRasterCount())
    1047         158 :                     ? poSrcDS->GetRasterBand(i)->GetColorInterpretation()
    1048         158 :                     : GCI_AlphaBand;
    1049         158 :             SetBand(i, new MosaicRasterBand(
    1050         158 :                            this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
    1051         158 :                            oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
    1052             :                            nTileMinY, oTM, convention, directory, extension,
    1053         158 :                            pdfDstNoData, poCT));
    1054             :         }
    1055          51 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1056         102 :         const CPLStringList aosMD(metadata);
    1057          59 :         for (const auto [key, value] : cpl::IterateNameValue(aosMD))
    1058             :         {
    1059           8 :             SetMetadataItem(key, value);
    1060             :         }
    1061          51 :     }
    1062             : 
    1063          92 :     const OGRSpatialReference *GetSpatialRef() const override
    1064             :     {
    1065          92 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1066             :     }
    1067             : 
    1068         102 :     CPLErr GetGeoTransform(double *padfGT) override
    1069             :     {
    1070         102 :         memcpy(padfGT, m_adfGT, sizeof(double) * 6);
    1071         102 :         return CE_None;
    1072             :     }
    1073             : 
    1074             :     using GDALDataset::Clone;
    1075             : 
    1076          16 :     std::unique_ptr<MosaicDataset> Clone() const
    1077             :     {
    1078             :         return std::make_unique<MosaicDataset>(
    1079          16 :             m_directory, m_extension, m_format, m_poSrcDS, m_oTM, m_oSRS,
    1080          16 :             m_nTileMinX, m_nTileMinY, m_nTileMaxX, m_nTileMaxY, m_convention,
    1081          16 :             nBands, m_eDT, m_pdfDstNoData, m_metadata, m_poCT);
    1082             :     }
    1083             : };
    1084             : 
    1085             : /************************************************************************/
    1086             : /*                   MosaicRasterBand::IReadBlock()                     */
    1087             : /************************************************************************/
    1088             : 
    1089        1632 : CPLErr MosaicRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pData)
    1090             : {
    1091        1632 :     auto poThisDS = cpl::down_cast<MosaicDataset *>(poDS);
    1092             :     std::string filename = CPLFormFilenameSafe(
    1093        3263 :         m_directory.c_str(), CPLSPrintf("%d", m_tileMinX + nXBlock), nullptr);
    1094        1631 :     const int iFileY = GetFileY(m_tileMinY + nYBlock, m_oTM, m_convention);
    1095        3264 :     filename = CPLFormFilenameSafe(filename.c_str(), CPLSPrintf("%d", iFileY),
    1096        1632 :                                    m_extension.c_str());
    1097             : 
    1098        1632 :     std::shared_ptr<GDALDataset> poTileDS;
    1099        1632 :     if (!poThisDS->m_oCacheTile.tryGet(filename, poTileDS))
    1100             :     {
    1101         422 :         const char *const apszAllowedDrivers[] = {poThisDS->m_format.c_str(),
    1102         422 :                                                   nullptr};
    1103         422 :         const char *const apszAllowedDriversForCOG[] = {"GTiff", "LIBERTIFF",
    1104             :                                                         nullptr};
    1105         422 :         poTileDS.reset(GDALDataset::Open(
    1106             :             filename.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    1107         422 :             EQUAL(poThisDS->m_format.c_str(), "COG") ? apszAllowedDriversForCOG
    1108             :                                                      : apszAllowedDrivers));
    1109         423 :         if (!poTileDS)
    1110             :         {
    1111             :             VSIStatBufL sStat;
    1112           6 :             if (VSIStatL(filename.c_str(), &sStat) == 0)
    1113             :             {
    1114           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1115             :                          "File %s exists but cannot be opened with %s driver",
    1116             :                          filename.c_str(), poThisDS->m_format.c_str());
    1117           1 :                 return CE_Failure;
    1118             :             }
    1119             :         }
    1120         422 :         poThisDS->m_oCacheTile.insert(filename, poTileDS);
    1121             :     }
    1122        1631 :     if (!poTileDS || nBand > poTileDS->GetRasterCount())
    1123             :     {
    1124         767 :         memset(pData,
    1125         381 :                (poTileDS && (nBand == poTileDS->GetRasterCount() + 1)) ? 255
    1126             :                                                                        : 0,
    1127         386 :                static_cast<size_t>(nBlockXSize) * nBlockYSize *
    1128         386 :                    GDALGetDataTypeSizeBytes(eDataType));
    1129         386 :         return CE_None;
    1130             :     }
    1131             :     else
    1132             :     {
    1133        1244 :         return poTileDS->GetRasterBand(nBand)->RasterIO(
    1134             :             GF_Read, 0, 0, nBlockXSize, nBlockYSize, pData, nBlockXSize,
    1135        1245 :             nBlockYSize, eDataType, 0, 0, nullptr);
    1136             :     }
    1137             : }
    1138             : 
    1139             : }  // namespace
    1140             : 
    1141             : /************************************************************************/
    1142             : /*                         ApplySubstitutions()                         */
    1143             : /************************************************************************/
    1144             : 
    1145          59 : static void ApplySubstitutions(CPLString &s,
    1146             :                                const std::map<std::string, std::string> &substs)
    1147             : {
    1148         932 :     for (const auto &[key, value] : substs)
    1149             :     {
    1150         873 :         s.replaceAll("%(" + key + ")s", value);
    1151         873 :         s.replaceAll("%(" + key + ")d", value);
    1152         873 :         s.replaceAll("%(" + key + ")f", value);
    1153         873 :         s.replaceAll("${" + key + "}", value);
    1154             :     }
    1155          59 : }
    1156             : 
    1157             : /************************************************************************/
    1158             : /*                           GenerateLeaflet()                          */
    1159             : /************************************************************************/
    1160             : 
    1161          13 : static void GenerateLeaflet(const std::string &osDirectory,
    1162             :                             const std::string &osTitle, double dfSouthLat,
    1163             :                             double dfWestLon, double dfNorthLat,
    1164             :                             double dfEastLon, int nMinZoom, int nMaxZoom,
    1165             :                             int nTileSize, const std::string &osExtension,
    1166             :                             const std::string &osURL,
    1167             :                             const std::string &osCopyright, bool bXYZ)
    1168             : {
    1169          13 :     if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
    1170             :     {
    1171          26 :         const std::string osFilename(pszTemplate);
    1172          26 :         std::map<std::string, std::string> substs;
    1173             : 
    1174             :         // For tests
    1175             :         const char *pszFmt =
    1176          13 :             atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    1177             :                 ? "%.10g"
    1178          13 :                 : "%.17g";
    1179             : 
    1180          26 :         substs["double_quote_escaped_title"] =
    1181          39 :             CPLString(osTitle).replaceAll('"', "\\\"");
    1182          13 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1183          13 :         substs["xml_escaped_title"] = pszStr;
    1184          13 :         CPLFree(pszStr);
    1185          13 :         substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
    1186          13 :         substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
    1187          13 :         substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
    1188          13 :         substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
    1189          13 :         substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
    1190          13 :         substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
    1191          13 :         substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    1192          13 :         substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    1193          13 :         substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
    1194          13 :         substs["tile_size"] = CPLSPrintf("%d", nTileSize);  // not used
    1195          13 :         substs["tileformat"] = osExtension;
    1196          13 :         substs["publishurl"] = osURL;  // not used
    1197          13 :         substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
    1198          13 :         substs["tms"] = bXYZ ? "0" : "1";
    1199             : 
    1200          13 :         GByte *pabyRet = nullptr;
    1201          13 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    1202             :                                          nullptr, 10 * 1024 * 1024));
    1203          13 :         if (pabyRet)
    1204             :         {
    1205          26 :             CPLString osHTML(reinterpret_cast<char *>(pabyRet));
    1206          13 :             CPLFree(pabyRet);
    1207             : 
    1208          13 :             ApplySubstitutions(osHTML, substs);
    1209             : 
    1210          13 :             VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
    1211             :                                                         "leaflet.html", nullptr)
    1212             :                                         .c_str(),
    1213             :                                     "wb");
    1214          13 :             if (f)
    1215             :             {
    1216          13 :                 VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
    1217          13 :                 VSIFCloseL(f);
    1218             :             }
    1219             :         }
    1220             :     }
    1221          13 : }
    1222             : 
    1223             : /************************************************************************/
    1224             : /*                           GenerateMapML()                            */
    1225             : /************************************************************************/
    1226             : 
    1227             : static void
    1228          14 : GenerateMapML(const std::string &osDirectory, const std::string &mapmlTemplate,
    1229             :               const std::string &osTitle, int nMinTileX, int nMinTileY,
    1230             :               int nMaxTileX, int nMaxTileY, int nMinZoom, int nMaxZoom,
    1231             :               const std::string &osExtension, const std::string &osURL,
    1232             :               const std::string &osCopyright, const gdal::TileMatrixSet &tms)
    1233             : {
    1234          14 :     if (const char *pszTemplate =
    1235          14 :             (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
    1236          14 :                                    : mapmlTemplate.c_str()))
    1237             :     {
    1238          28 :         const std::string osFilename(pszTemplate);
    1239          28 :         std::map<std::string, std::string> substs;
    1240             : 
    1241          14 :         if (tms.identifier() == "GoogleMapsCompatible")
    1242          13 :             substs["TILING_SCHEME"] = "OSMTILE";
    1243           1 :         else if (tms.identifier() == "WorldCRS84Quad")
    1244           1 :             substs["TILING_SCHEME"] = "WGS84";
    1245             :         else
    1246           0 :             substs["TILING_SCHEME"] = tms.identifier();
    1247             : 
    1248          14 :         substs["URL"] = osURL.empty() ? "./" : osURL;
    1249          14 :         substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
    1250          14 :         substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
    1251          14 :         substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
    1252          14 :         substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
    1253          14 :         substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
    1254          14 :         substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
    1255          14 :         substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
    1256          14 :         substs["TILEEXT"] = osExtension;
    1257          14 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1258          14 :         substs["TITLE"] = pszStr;
    1259          14 :         CPLFree(pszStr);
    1260          14 :         substs["COPYRIGHT"] = osCopyright;
    1261             : 
    1262          14 :         GByte *pabyRet = nullptr;
    1263          14 :         CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
    1264             :                                          nullptr, 10 * 1024 * 1024));
    1265          14 :         if (pabyRet)
    1266             :         {
    1267          28 :             CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
    1268          14 :             CPLFree(pabyRet);
    1269             : 
    1270          14 :             ApplySubstitutions(osMAPML, substs);
    1271             : 
    1272          14 :             VSILFILE *f = VSIFOpenL(
    1273          28 :                 CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
    1274             :                     .c_str(),
    1275             :                 "wb");
    1276          14 :             if (f)
    1277             :             {
    1278          14 :                 VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
    1279          14 :                 VSIFCloseL(f);
    1280             :             }
    1281             :         }
    1282             :     }
    1283          14 : }
    1284             : 
    1285             : /************************************************************************/
    1286             : /*                           GenerateOpenLayers()                       */
    1287             : /************************************************************************/
    1288             : 
    1289          21 : static void GenerateOpenLayers(
    1290             :     const std::string &osDirectory, const std::string &osTitle, double dfMinX,
    1291             :     double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
    1292             :     int nTileSize, const std::string &osExtension, const std::string &osURL,
    1293             :     const std::string &osCopyright, const gdal::TileMatrixSet &tms,
    1294             :     bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
    1295             : {
    1296          42 :     std::map<std::string, std::string> substs;
    1297             : 
    1298             :     // For tests
    1299             :     const char *pszFmt =
    1300          21 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
    1301             :             ? "%.10g"
    1302          21 :             : "%.17g";
    1303             : 
    1304          21 :     char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1305          21 :     substs["xml_escaped_title"] = pszStr;
    1306          21 :     CPLFree(pszStr);
    1307          21 :     substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
    1308          21 :     substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
    1309          21 :     substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
    1310          21 :     substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
    1311          21 :     substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
    1312          21 :     substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
    1313          21 :     substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
    1314          21 :     substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
    1315          21 :     substs["tile_size"] = CPLSPrintf("%d", nTileSize);
    1316          21 :     substs["tileformat"] = osExtension;
    1317          21 :     substs["publishurl"] = osURL;
    1318          21 :     substs["copyright"] = osCopyright;
    1319          21 :     substs["sign_y"] = bXYZ ? "" : "-";
    1320             : 
    1321             :     CPLString s(R"raw(<!DOCTYPE html>
    1322             : <html>
    1323             : <head>
    1324             :     <title>%(xml_escaped_title)s</title>
    1325             :     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    1326             :     <meta http-equiv='imagetoolbar' content='no'/>
    1327             :     <style type="text/css"> v\:* {behavior:url(#default#VML);}
    1328             :         html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
    1329             :         body { margin: 10px; background: #fff; }
    1330             :         h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
    1331             :         #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
    1332             :         #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
    1333             :         #map { height: 90%; border: 1px solid #888; }
    1334             :     </style>
    1335             :     <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.css" type="text/css">
    1336             :     <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
    1337             :     <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
    1338             :     <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
    1339             : </head>
    1340             : <body>
    1341             :     <div id="header"><h1>%(xml_escaped_title)s</h1></div>
    1342             :     <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a>&nbsp;&nbsp;&nbsp;&nbsp;</div>
    1343             :     <div id="map" class="map"></div>
    1344             :     <div id="mouse-position"></div>
    1345             :     <script type="text/javascript">
    1346             :         var mousePositionControl = new ol.control.MousePosition({
    1347             :             className: 'custom-mouse-position',
    1348             :             target: document.getElementById('mouse-position'),
    1349             :             undefinedHTML: '&nbsp;'
    1350             :         });
    1351             :         var map = new ol.Map({
    1352             :             controls: ol.control.defaults.defaults().extend([mousePositionControl]),
    1353          42 :             target: 'map',)raw");
    1354             : 
    1355          29 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    1356           8 :         tms.identifier() == "WorldCRS84Quad")
    1357             :     {
    1358          15 :         s += R"raw(
    1359             :             layers: [
    1360             :                 new ol.layer.Group({
    1361             :                         title: 'Base maps',
    1362             :                         layers: [
    1363             :                             new ol.layer.Tile({
    1364             :                                 title: 'OpenStreetMap',
    1365             :                                 type: 'base',
    1366             :                                 visible: true,
    1367             :                                 source: new ol.source.OSM()
    1368             :                             }),
    1369             :                         ]
    1370             :                 }),)raw";
    1371             :     }
    1372             : 
    1373          21 :     if (tms.identifier() == "GoogleMapsCompatible")
    1374             :     {
    1375          13 :         s += R"raw(new ol.layer.Group({
    1376             :                     title: 'Overlay',
    1377             :                     layers: [
    1378             :                         new ol.layer.Tile({
    1379             :                             title: 'Overlay',
    1380             :                             // opacity: 0.7,
    1381             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    1382             :                             source: new ol.source.XYZ({
    1383             :                                 attributions: '%(copyright)s',
    1384             :                                 minZoom: %(minzoom)d,
    1385             :                                 maxZoom: %(maxzoom)d,
    1386             :                                 url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
    1387             :                                 tileSize: [%(tile_size)d, %(tile_size)d]
    1388             :                             })
    1389             :                         }),
    1390             :                     ]
    1391             :                 }),)raw";
    1392             :     }
    1393           8 :     else if (tms.identifier() == "WorldCRS84Quad")
    1394             :     {
    1395           2 :         const double base_res = 180.0 / nTileSize;
    1396           4 :         std::string resolutions = "[";
    1397           4 :         for (int i = 0; i <= nMaxZoom; ++i)
    1398             :         {
    1399           2 :             if (i > 0)
    1400           0 :                 resolutions += ",";
    1401           2 :             resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
    1402             :         }
    1403           2 :         resolutions += "]";
    1404           2 :         substs["resolutions"] = std::move(resolutions);
    1405             : 
    1406           2 :         if (bXYZ)
    1407             :         {
    1408           1 :             substs["origin"] = "[-180,90]";
    1409           1 :             substs["y_formula"] = "tileCoord[2]";
    1410             :         }
    1411             :         else
    1412             :         {
    1413           1 :             substs["origin"] = "[-180,-90]";
    1414           1 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    1415             :         }
    1416             : 
    1417           2 :         s += R"raw(
    1418             :                 new ol.layer.Group({
    1419             :                     title: 'Overlay',
    1420             :                     layers: [
    1421             :                         new ol.layer.Tile({
    1422             :                             title: 'Overlay',
    1423             :                             // opacity: 0.7,
    1424             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    1425             :                             source: new ol.source.TileImage({
    1426             :                                 attributions: '%(copyright)s',
    1427             :                                 projection: 'EPSG:4326',
    1428             :                                 minZoom: %(minzoom)d,
    1429             :                                 maxZoom: %(maxzoom)d,
    1430             :                                 tileGrid: new ol.tilegrid.TileGrid({
    1431             :                                     extent: [-180,-90,180,90],
    1432             :                                     origin: %(origin)s,
    1433             :                                     resolutions: %(resolutions)s,
    1434             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    1435             :                                 }),
    1436             :                                 tileUrlFunction: function(tileCoord) {
    1437             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    1438             :                                         .replace('{z}', String(tileCoord[0]))
    1439             :                                         .replace('{x}', String(tileCoord[1]))
    1440             :                                         .replace('{y}', String(%(y_formula)s)));
    1441             :                                 },
    1442             :                             })
    1443             :                         }),
    1444             :                     ]
    1445             :                 }),)raw";
    1446             :     }
    1447             :     else
    1448             :     {
    1449          12 :         substs["maxres"] =
    1450          12 :             CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
    1451          12 :         std::string resolutions = "[";
    1452          16 :         for (int i = 0; i <= nMaxZoom; ++i)
    1453             :         {
    1454          10 :             if (i > 0)
    1455           4 :                 resolutions += ",";
    1456          10 :             resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
    1457             :         }
    1458           6 :         resolutions += "]";
    1459           6 :         substs["resolutions"] = std::move(resolutions);
    1460             : 
    1461          12 :         std::string matrixsizes = "[";
    1462          16 :         for (int i = 0; i <= nMaxZoom; ++i)
    1463             :         {
    1464          10 :             if (i > 0)
    1465           4 :                 matrixsizes += ",";
    1466             :             matrixsizes +=
    1467          10 :                 CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
    1468          20 :                            tms.tileMatrixList()[i].mMatrixHeight);
    1469             :         }
    1470           6 :         matrixsizes += "]";
    1471           6 :         substs["matrixsizes"] = std::move(matrixsizes);
    1472             : 
    1473           6 :         double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
    1474           6 :         double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
    1475           6 :         if (bInvertAxisTMS)
    1476           0 :             std::swap(dfTopLeftX, dfTopLeftY);
    1477             : 
    1478           6 :         if (bXYZ)
    1479             :         {
    1480          10 :             substs["origin"] =
    1481          10 :                 CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
    1482           5 :             substs["y_formula"] = "tileCoord[2]";
    1483             :         }
    1484             :         else
    1485             :         {
    1486           2 :             substs["origin"] = CPLSPrintf(
    1487             :                 "[%.17g,%.17g]", dfTopLeftX,
    1488           1 :                 dfTopLeftY - tms.tileMatrixList()[0].mResY *
    1489           3 :                                  tms.tileMatrixList()[0].mTileHeight);
    1490           1 :             substs["y_formula"] = "- 1 - tileCoord[2]";
    1491             :         }
    1492             : 
    1493          12 :         substs["tilegrid_extent"] =
    1494             :             CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
    1495           6 :                        dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
    1496           6 :                                         tms.tileMatrixList()[0].mResY *
    1497           6 :                                         tms.tileMatrixList()[0].mTileHeight,
    1498           6 :                        dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
    1499           6 :                                         tms.tileMatrixList()[0].mResX *
    1500           6 :                                         tms.tileMatrixList()[0].mTileWidth,
    1501          24 :                        dfTopLeftY);
    1502             : 
    1503           6 :         s += R"raw(
    1504             :             layers: [
    1505             :                 new ol.layer.Group({
    1506             :                     title: 'Overlay',
    1507             :                     layers: [
    1508             :                         new ol.layer.Tile({
    1509             :                             title: 'Overlay',
    1510             :                             // opacity: 0.7,
    1511             :                             extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
    1512             :                             source: new ol.source.TileImage({
    1513             :                                 attributions: '%(copyright)s',
    1514             :                                 minZoom: %(minzoom)d,
    1515             :                                 maxZoom: %(maxzoom)d,
    1516             :                                 tileGrid: new ol.tilegrid.TileGrid({
    1517             :                                     extent: %(tilegrid_extent)s,
    1518             :                                     origin: %(origin)s,
    1519             :                                     resolutions: %(resolutions)s,
    1520             :                                     sizes: %(matrixsizes)s,
    1521             :                                     tileSize: [%(tile_size)d, %(tile_size)d]
    1522             :                                 }),
    1523             :                                 tileUrlFunction: function(tileCoord) {
    1524             :                                     return ('./{z}/{x}/{y}.%(tileformat)s'
    1525             :                                         .replace('{z}', String(tileCoord[0]))
    1526             :                                         .replace('{x}', String(tileCoord[1]))
    1527             :                                         .replace('{y}', String(%(y_formula)s)));
    1528             :                                 },
    1529             :                             })
    1530             :                         }),
    1531             :                     ]
    1532             :                 }),)raw";
    1533             :     }
    1534             : 
    1535          21 :     s += R"raw(
    1536             :             ],
    1537             :             view: new ol.View({
    1538             :                 center: [%(center_x)f, %(center_y)f],)raw";
    1539             : 
    1540          29 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    1541           8 :         tms.identifier() == "WorldCRS84Quad")
    1542             :     {
    1543          15 :         substs["view_zoom"] = substs["minzoom"];
    1544          15 :         if (tms.identifier() == "WorldCRS84Quad")
    1545             :         {
    1546           2 :             substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
    1547             :         }
    1548             : 
    1549          15 :         s += R"raw(
    1550             :                 zoom: %(view_zoom)d,)raw";
    1551             :     }
    1552             :     else
    1553             :     {
    1554           6 :         s += R"raw(
    1555             :                 resolution: %(maxres)f,)raw";
    1556             :     }
    1557             : 
    1558          21 :     if (tms.identifier() == "WorldCRS84Quad")
    1559             :     {
    1560           2 :         s += R"raw(
    1561             :                 projection: 'EPSG:4326',)raw";
    1562             :     }
    1563          19 :     else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
    1564             :     {
    1565           5 :         const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
    1566           5 :         const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
    1567           5 :         if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    1568             :         {
    1569           3 :             substs["epsg_code"] = pszAuthCode;
    1570           3 :             if (oSRS_TMS.IsGeographic())
    1571             :             {
    1572           1 :                 substs["units"] = "deg";
    1573             :             }
    1574             :             else
    1575             :             {
    1576           2 :                 const char *pszUnits = "";
    1577           2 :                 if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
    1578           2 :                     substs["units"] = "m";
    1579             :                 else
    1580           0 :                     substs["units"] = pszUnits;
    1581             :             }
    1582           3 :             s += R"raw(
    1583             :                 projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
    1584             :         }
    1585             :     }
    1586             : 
    1587          21 :     s += R"raw(
    1588             :             })
    1589             :         });)raw";
    1590             : 
    1591          29 :     if (tms.identifier() == "GoogleMapsCompatible" ||
    1592           8 :         tms.identifier() == "WorldCRS84Quad")
    1593             :     {
    1594          15 :         s += R"raw(
    1595             :         map.addControl(new ol.control.LayerSwitcher());)raw";
    1596             :     }
    1597             : 
    1598          21 :     s += R"raw(
    1599             :     </script>
    1600             : </body>
    1601             : </html>)raw";
    1602             : 
    1603          21 :     ApplySubstitutions(s, substs);
    1604             : 
    1605          21 :     VSILFILE *f = VSIFOpenL(
    1606          42 :         CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
    1607             :             .c_str(),
    1608             :         "wb");
    1609          21 :     if (f)
    1610             :     {
    1611          21 :         VSIFWriteL(s.data(), 1, s.size(), f);
    1612          21 :         VSIFCloseL(f);
    1613             :     }
    1614          21 : }
    1615             : 
    1616             : /************************************************************************/
    1617             : /*                           GetTileBoundingBox()                       */
    1618             : /************************************************************************/
    1619             : 
    1620           6 : static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
    1621             :                                const gdal::TileMatrixSet *poTMS,
    1622             :                                bool bInvertAxisTMS,
    1623             :                                OGRCoordinateTransformation *poCTToWGS84,
    1624             :                                double &dfTLX, double &dfTLY, double &dfTRX,
    1625             :                                double &dfTRY, double &dfLLX, double &dfLLY,
    1626             :                                double &dfLRX, double &dfLRY)
    1627             : {
    1628             :     gdal::TileMatrixSet::TileMatrix tileMatrix =
    1629          12 :         poTMS->tileMatrixList()[nTileZ];
    1630           6 :     if (bInvertAxisTMS)
    1631           0 :         std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
    1632             : 
    1633           6 :     dfTLX = tileMatrix.mTopLeftX +
    1634           6 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    1635           6 :     dfTLY = tileMatrix.mTopLeftY -
    1636           6 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    1637           6 :     poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
    1638             : 
    1639           6 :     dfTRX = tileMatrix.mTopLeftX +
    1640           6 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    1641           6 :     dfTRY = tileMatrix.mTopLeftY -
    1642           6 :             nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    1643           6 :     poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
    1644             : 
    1645           6 :     dfLLX = tileMatrix.mTopLeftX +
    1646           6 :             nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    1647           6 :     dfLLY = tileMatrix.mTopLeftY -
    1648           6 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    1649           6 :     poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
    1650             : 
    1651           6 :     dfLRX = tileMatrix.mTopLeftX +
    1652           6 :             (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
    1653           6 :     dfLRY = tileMatrix.mTopLeftY -
    1654           6 :             (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
    1655           6 :     poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
    1656           6 : }
    1657             : 
    1658             : /************************************************************************/
    1659             : /*                           GenerateKML()                              */
    1660             : /************************************************************************/
    1661             : 
    1662             : namespace
    1663             : {
    1664             : struct TileCoordinates
    1665             : {
    1666             :     int nTileX = 0;
    1667             :     int nTileY = 0;
    1668             :     int nTileZ = 0;
    1669             : };
    1670             : }  // namespace
    1671             : 
    1672           5 : static void GenerateKML(const std::string &osDirectory,
    1673             :                         const std::string &osTitle, int nTileX, int nTileY,
    1674             :                         int nTileZ, int nTileSize,
    1675             :                         const std::string &osExtension,
    1676             :                         const std::string &osURL,
    1677             :                         const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
    1678             :                         const std::string &convention,
    1679             :                         OGRCoordinateTransformation *poCTToWGS84,
    1680             :                         const std::vector<TileCoordinates> &children)
    1681             : {
    1682          10 :     std::map<std::string, std::string> substs;
    1683             : 
    1684           5 :     const bool bIsTileKML = nTileX >= 0;
    1685             : 
    1686             :     // For tests
    1687             :     const char *pszFmt =
    1688           5 :         atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
    1689             :             ? "%.10f"
    1690           5 :             : "%.14f";
    1691             : 
    1692           5 :     substs["tx"] = CPLSPrintf("%d", nTileX);
    1693           5 :     substs["tz"] = CPLSPrintf("%d", nTileZ);
    1694           5 :     substs["tileformat"] = osExtension;
    1695           5 :     substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
    1696          10 :     substs["maxlodpixels"] =
    1697          10 :         children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
    1698             : 
    1699           5 :     double dfTLX = 0;
    1700           5 :     double dfTLY = 0;
    1701           5 :     double dfTRX = 0;
    1702           5 :     double dfTRY = 0;
    1703           5 :     double dfLLX = 0;
    1704           5 :     double dfLLY = 0;
    1705           5 :     double dfLRX = 0;
    1706           5 :     double dfLRY = 0;
    1707             : 
    1708           5 :     int nFileY = -1;
    1709           5 :     if (!bIsTileKML)
    1710             :     {
    1711           2 :         char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
    1712           2 :         substs["xml_escaped_title"] = pszStr;
    1713           2 :         CPLFree(pszStr);
    1714             :     }
    1715             :     else
    1716             :     {
    1717           3 :         nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
    1718           3 :         substs["realtiley"] = CPLSPrintf("%d", nFileY);
    1719           6 :         substs["xml_escaped_title"] =
    1720           6 :             CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
    1721             : 
    1722           3 :         GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
    1723             :                            poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
    1724             :                            dfLLY, dfLRX, dfLRY);
    1725             :     }
    1726             : 
    1727          10 :     substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
    1728           4 :                                            : nTileX > 0 ? 2 * nTileZ
    1729          14 :                                                         : 0);
    1730             : 
    1731           5 :     substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
    1732             : 
    1733           5 :     const bool bIsRectangle =
    1734           5 :         (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
    1735           5 :     const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
    1736             : 
    1737          10 :     substs["xmlns_gx"] = bUseGXNamespace
    1738             :                              ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
    1739          10 :                              : "";
    1740             : 
    1741             :     CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
    1742             : <kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
    1743             :   <Document>
    1744             :     <name>%(xml_escaped_title)s</name>
    1745             :     <description></description>
    1746             :     <Style>
    1747             :       <ListStyle id="hideChildren">
    1748             :         <listItemType>checkHideChildren</listItemType>
    1749             :       </ListStyle>
    1750             :     </Style>
    1751          10 : )raw");
    1752           5 :     ApplySubstitutions(s, substs);
    1753             : 
    1754           5 :     if (bIsTileKML)
    1755             :     {
    1756             :         CPLString s2(R"raw(    <Region>
    1757             :       <LatLonAltBox>
    1758             :         <north>%(north)f</north>
    1759             :         <south>%(south)f</south>
    1760             :         <east>%(east)f</east>
    1761             :         <west>%(west)f</west>
    1762             :       </LatLonAltBox>
    1763             :       <Lod>
    1764             :         <minLodPixels>%(minlodpixels)d</minLodPixels>
    1765             :         <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
    1766             :       </Lod>
    1767             :     </Region>
    1768             :     <GroundOverlay>
    1769             :       <drawOrder>%(drawOrder)d</drawOrder>
    1770             :       <Icon>
    1771             :         <href>%(realtiley)d.%(tileformat)s</href>
    1772             :       </Icon>
    1773             :       <LatLonBox>
    1774             :         <north>%(north)f</north>
    1775             :         <south>%(south)f</south>
    1776             :         <east>%(east)f</east>
    1777             :         <west>%(west)f</west>
    1778             :       </LatLonBox>
    1779           6 : )raw");
    1780             : 
    1781           3 :         if (!bIsRectangle)
    1782             :         {
    1783             :             s2 +=
    1784           1 :                 R"raw(      <gx:LatLonQuad><coordinates>%(LLX)f,%(LLY)f %(LRX)f,%(LRY)f %(TRX)f,%(TRY)f %(TLX)f,%(TLY)f</coordinates></gx:LatLonQuad>
    1785             : )raw";
    1786             :         }
    1787             : 
    1788           3 :         s2 += R"raw(    </GroundOverlay>
    1789             : )raw";
    1790           3 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    1791           3 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    1792           3 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    1793           3 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    1794             : 
    1795           3 :         if (!bIsRectangle)
    1796             :         {
    1797           1 :             substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
    1798           1 :             substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
    1799           1 :             substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
    1800           1 :             substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
    1801           1 :             substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
    1802           1 :             substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
    1803           1 :             substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
    1804           1 :             substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
    1805             :         }
    1806             : 
    1807           3 :         ApplySubstitutions(s2, substs);
    1808           3 :         s += s2;
    1809             :     }
    1810             : 
    1811           8 :     for (const auto &child : children)
    1812             :     {
    1813           3 :         substs["tx"] = CPLSPrintf("%d", child.nTileX);
    1814           3 :         substs["tz"] = CPLSPrintf("%d", child.nTileZ);
    1815           6 :         substs["realtiley"] = CPLSPrintf(
    1816           3 :             "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
    1817           6 :                            convention));
    1818             : 
    1819           3 :         GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
    1820             :                            bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
    1821             :                            dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
    1822             : 
    1823             :         CPLString s2(R"raw(    <NetworkLink>
    1824             :       <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
    1825             :       <Region>
    1826             :         <LatLonAltBox>
    1827             :           <north>%(north)f</north>
    1828             :           <south>%(south)f</south>
    1829             :           <east>%(east)f</east>
    1830             :           <west>%(west)f</west>
    1831             :         </LatLonAltBox>
    1832             :         <Lod>
    1833             :           <minLodPixels>%(minlodpixels)d</minLodPixels>
    1834             :           <maxLodPixels>-1</maxLodPixels>
    1835             :         </Lod>
    1836             :       </Region>
    1837             :       <Link>
    1838             :         <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
    1839             :         <viewRefreshMode>onRegion</viewRefreshMode>
    1840             :         <viewFormat/>
    1841             :       </Link>
    1842             :     </NetworkLink>
    1843           6 : )raw");
    1844           3 :         substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
    1845           3 :         substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
    1846           3 :         substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
    1847           3 :         substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
    1848           3 :         ApplySubstitutions(s2, substs);
    1849           3 :         s += s2;
    1850             :     }
    1851             : 
    1852           5 :     s += R"raw(</Document>
    1853             : </kml>)raw";
    1854             : 
    1855          10 :     std::string osFilename(osDirectory);
    1856           5 :     if (!bIsTileKML)
    1857             :     {
    1858             :         osFilename =
    1859           2 :             CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
    1860             :     }
    1861             :     else
    1862             :     {
    1863           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    1864           3 :                                          CPLSPrintf("%d", nTileZ), nullptr);
    1865           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    1866           3 :                                          CPLSPrintf("%d", nTileX), nullptr);
    1867           6 :         osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    1868           3 :                                          CPLSPrintf("%d.kml", nFileY), nullptr);
    1869             :     }
    1870             : 
    1871           5 :     VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
    1872           5 :     if (f)
    1873             :     {
    1874           5 :         VSIFWriteL(s.data(), 1, s.size(), f);
    1875           5 :         VSIFCloseL(f);
    1876             :     }
    1877           5 : }
    1878             : 
    1879             : namespace
    1880             : {
    1881             : 
    1882             : /************************************************************************/
    1883             : /*                            ResourceManager                           */
    1884             : /************************************************************************/
    1885             : 
    1886             : // Generic cache managing resources
    1887             : template <class Resource> class ResourceManager /* non final */
    1888             : {
    1889             :   public:
    1890          94 :     virtual ~ResourceManager() = default;
    1891             : 
    1892         270 :     std::unique_ptr<Resource> AcquireResources()
    1893             :     {
    1894         542 :         std::lock_guard oLock(m_oMutex);
    1895         272 :         if (!m_oResources.empty())
    1896             :         {
    1897         288 :             auto ret = std::move(m_oResources.back());
    1898         144 :             m_oResources.pop_back();
    1899         144 :             return ret;
    1900             :         }
    1901             : 
    1902         128 :         return CreateResources();
    1903             :     }
    1904             : 
    1905         256 :     void ReleaseResources(std::unique_ptr<Resource> resources)
    1906             :     {
    1907         512 :         std::lock_guard oLock(m_oMutex);
    1908         256 :         m_oResources.push_back(std::move(resources));
    1909         256 :     }
    1910             : 
    1911          16 :     void SetError()
    1912             :     {
    1913          32 :         std::lock_guard oLock(m_oMutex);
    1914          16 :         if (m_errorMsg.empty())
    1915           1 :             m_errorMsg = CPLGetLastErrorMsg();
    1916          16 :     }
    1917             : 
    1918          33 :     const std::string &GetErrorMsg() const
    1919             :     {
    1920          33 :         std::lock_guard oLock(m_oMutex);
    1921          66 :         return m_errorMsg;
    1922             :     }
    1923             : 
    1924             :   protected:
    1925             :     virtual std::unique_ptr<Resource> CreateResources() = 0;
    1926             : 
    1927             :   private:
    1928             :     mutable std::mutex m_oMutex{};
    1929             :     std::vector<std::unique_ptr<Resource>> m_oResources{};
    1930             :     std::string m_errorMsg{};
    1931             : };
    1932             : 
    1933             : /************************************************************************/
    1934             : /*                         PerThreadMaxZoomResources                    */
    1935             : /************************************************************************/
    1936             : 
    1937             : // Per-thread resources for generation of tiles at full resolution
    1938             : struct PerThreadMaxZoomResources
    1939             : {
    1940             :     struct GDALDatasetReleaser
    1941             :     {
    1942         112 :         void operator()(GDALDataset *poDS)
    1943             :         {
    1944         112 :             if (poDS)
    1945         112 :                 poDS->ReleaseRef();
    1946         112 :         }
    1947             :     };
    1948             : 
    1949             :     std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
    1950             :     std::vector<GByte> dstBuffer{};
    1951             :     std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
    1952             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
    1953             :         nullptr, GDALDestroyTransformer};
    1954             :     std::unique_ptr<GDALWarpOperation> poWO{};
    1955             : };
    1956             : 
    1957             : /************************************************************************/
    1958             : /*                      PerThreadMaxZoomResourceManager                 */
    1959             : /************************************************************************/
    1960             : 
    1961             : // Manage a cache of PerThreadMaxZoomResources instances
    1962             : class PerThreadMaxZoomResourceManager final
    1963             :     : public ResourceManager<PerThreadMaxZoomResources>
    1964             : {
    1965             :   public:
    1966          59 :     PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
    1967             :                                     const GDALWarpOptions *psWO,
    1968             :                                     void *pTransformerArg,
    1969             :                                     const FakeMaxZoomDataset &oFakeMaxZoomDS,
    1970             :                                     size_t nBufferSize)
    1971          59 :         : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
    1972             :           m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
    1973          59 :           m_nBufferSize(nBufferSize)
    1974             :     {
    1975          59 :     }
    1976             : 
    1977             :   protected:
    1978         112 :     std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
    1979             :     {
    1980         224 :         auto ret = std::make_unique<PerThreadMaxZoomResources>();
    1981             : 
    1982         112 :         ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
    1983         112 :         if (!ret->poSrcDS)
    1984           0 :             return nullptr;
    1985             : 
    1986             :         try
    1987             :         {
    1988         112 :             ret->dstBuffer.resize(m_nBufferSize);
    1989             :         }
    1990           0 :         catch (const std::exception &)
    1991             :         {
    1992           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    1993             :                      "Out of memory allocating temporary buffer");
    1994           0 :             return nullptr;
    1995             :         }
    1996             : 
    1997         112 :         ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
    1998             : 
    1999         112 :         ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
    2000         112 :         if (!ret->poTransformer)
    2001           0 :             return nullptr;
    2002             : 
    2003             :         auto psWO =
    2004             :             std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
    2005         224 :                 GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
    2006         112 :         if (!psWO)
    2007           0 :             return nullptr;
    2008             : 
    2009         112 :         psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
    2010         112 :         psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
    2011         112 :         psWO->pTransformerArg = ret->poTransformer.get();
    2012         112 :         psWO->pfnTransformer = m_psWOSource->pfnTransformer;
    2013             : 
    2014         112 :         ret->poWO = std::make_unique<GDALWarpOperation>();
    2015         112 :         if (ret->poWO->Initialize(psWO.get()) != CE_None)
    2016           0 :             return nullptr;
    2017             : 
    2018         112 :         return ret;
    2019             :     }
    2020             : 
    2021             :   private:
    2022             :     GDALDataset *const m_poSrcDS;
    2023             :     const GDALWarpOptions *const m_psWOSource;
    2024             :     void *const m_pTransformerArg;
    2025             :     const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
    2026             :     const size_t m_nBufferSize;
    2027             : 
    2028             :     CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
    2029             : };
    2030             : 
    2031             : /************************************************************************/
    2032             : /*                       PerThreadLowerZoomResources                    */
    2033             : /************************************************************************/
    2034             : 
    2035             : // Per-thread resources for generation of tiles at zoom level < max
    2036             : struct PerThreadLowerZoomResources
    2037             : {
    2038             :     std::unique_ptr<GDALDataset> poSrcDS{};
    2039             : };
    2040             : 
    2041             : /************************************************************************/
    2042             : /*                   PerThreadLowerZoomResourceManager                  */
    2043             : /************************************************************************/
    2044             : 
    2045             : // Manage a cache of PerThreadLowerZoomResources instances
    2046             : class PerThreadLowerZoomResourceManager final
    2047             :     : public ResourceManager<PerThreadLowerZoomResources>
    2048             : {
    2049             :   public:
    2050          35 :     explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
    2051          35 :         : m_oSrcDS(oSrcDS)
    2052             :     {
    2053          35 :     }
    2054             : 
    2055             :   protected:
    2056          16 :     std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
    2057             :     {
    2058          16 :         auto ret = std::make_unique<PerThreadLowerZoomResources>();
    2059          16 :         ret->poSrcDS = m_oSrcDS.Clone();
    2060          16 :         return ret;
    2061             :     }
    2062             : 
    2063             :   private:
    2064             :     const MosaicDataset &m_oSrcDS;
    2065             : };
    2066             : 
    2067             : }  // namespace
    2068             : 
    2069             : /************************************************************************/
    2070             : /*                  GDALRasterTileAlgorithm::RunImpl()                  */
    2071             : /************************************************************************/
    2072             : 
    2073          87 : bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
    2074             :                                       void *pProgressData)
    2075             : {
    2076          87 :     auto poSrcDS = m_dataset.GetDatasetRef();
    2077          87 :     CPLAssert(poSrcDS);
    2078          87 :     const int nSrcWidth = poSrcDS->GetRasterXSize();
    2079          87 :     const int nSrcHeight = poSrcDS->GetRasterYSize();
    2080          87 :     if (poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
    2081             :     {
    2082           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
    2083           1 :         return false;
    2084             :     }
    2085             : 
    2086          86 :     if (m_resampling == "near")
    2087           1 :         m_resampling = "nearest";
    2088          86 :     if (m_overviewResampling == "near")
    2089           1 :         m_overviewResampling = "nearest";
    2090          85 :     else if (m_overviewResampling.empty())
    2091          80 :         m_overviewResampling = m_resampling;
    2092             : 
    2093         172 :     CPLStringList aosWarpOptions;
    2094          86 :     if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
    2095             :     {
    2096             :         aosWarpOptions.SetNameValue(
    2097             :             "NODATA_VALUES_PCT_THRESHOLD",
    2098           3 :             CPLSPrintf("%g", m_nodataValuesPctThreshold));
    2099           3 :         if (!m_excludedValues.empty())
    2100             :         {
    2101             :             aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
    2102           1 :                                         m_excludedValues.c_str());
    2103             :             aosWarpOptions.SetNameValue(
    2104             :                 "EXCLUDED_VALUES_PCT_THRESHOLD",
    2105           1 :                 CPLSPrintf("%g", m_excludedValuesPctThreshold));
    2106             :         }
    2107             :     }
    2108             : 
    2109          86 :     if (poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
    2110          90 :             GCI_PaletteIndex &&
    2111           4 :         ((m_resampling != "nearest" && m_resampling != "mode") ||
    2112           1 :          (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
    2113             :     {
    2114           1 :         ReportError(CE_Failure, CPLE_NotSupported,
    2115             :                     "Datasets with color table not supported with non-nearest "
    2116             :                     "or non-mode resampling. Run 'gdal raster "
    2117             :                     "color-map' before or set the 'resampling' argument to "
    2118             :                     "'nearest' or 'mode'.");
    2119           1 :         return false;
    2120             :     }
    2121             : 
    2122          85 :     const auto eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    2123             :     auto poDstDriver =
    2124          85 :         GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
    2125          85 :     if (!poDstDriver)
    2126             :     {
    2127           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    2128             :                     "Invalid value for argument 'output-format'. Driver '%s' "
    2129             :                     "does not exist",
    2130             :                     m_outputFormat.c_str());
    2131           1 :         return false;
    2132             :     }
    2133             : 
    2134          84 :     if (m_outputFormat == "PNG")
    2135             :     {
    2136          60 :         if (poSrcDS->GetRasterCount() > 4)
    2137             :         {
    2138           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2139             :                         "Only up to 4 bands supported for PNG.");
    2140           1 :             return false;
    2141             :         }
    2142          59 :         if (eSrcDT != GDT_Byte && eSrcDT != GDT_UInt16)
    2143             :         {
    2144           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2145             :                         "Only Byte and UInt16 data types supported for PNG.");
    2146           1 :             return false;
    2147             :         }
    2148             :     }
    2149          24 :     else if (m_outputFormat == "JPEG")
    2150             :     {
    2151           5 :         if (poSrcDS->GetRasterCount() > 4)
    2152             :         {
    2153           1 :             ReportError(
    2154             :                 CE_Failure, CPLE_NotSupported,
    2155             :                 "Only up to 4 bands supported for JPEG (with alpha ignored).");
    2156           1 :             return false;
    2157             :         }
    2158           8 :         const bool bUInt16Supported = strstr(
    2159           4 :             poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES), "UInt16");
    2160           4 :         if (eSrcDT != GDT_Byte && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
    2161             :         {
    2162           1 :             ReportError(
    2163             :                 CE_Failure, CPLE_NotSupported,
    2164             :                 bUInt16Supported
    2165             :                     ? "Only Byte and UInt16 data types supported for JPEG."
    2166             :                     : "Only Byte data type supported for JPEG.");
    2167           1 :             return false;
    2168             :         }
    2169           3 :         if (eSrcDT == GDT_UInt16)
    2170             :         {
    2171           3 :             if (const char *pszNBITS =
    2172           6 :                     poSrcDS->GetRasterBand(1)->GetMetadataItem(
    2173           3 :                         "NBITS", "IMAGE_STRUCTURE"))
    2174             :             {
    2175           1 :                 if (atoi(pszNBITS) > 12)
    2176             :                 {
    2177           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    2178             :                                 "JPEG output only supported up to 12 bits");
    2179           1 :                     return false;
    2180             :                 }
    2181             :             }
    2182             :             else
    2183             :             {
    2184           2 :                 double adfMinMax[2] = {0, 0};
    2185           2 :                 poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
    2186           2 :                     /* bApproxOK = */ true, adfMinMax);
    2187           2 :                 if (adfMinMax[1] >= (1 << 12))
    2188             :                 {
    2189           1 :                     ReportError(CE_Failure, CPLE_NotSupported,
    2190             :                                 "JPEG output only supported up to 12 bits");
    2191           1 :                     return false;
    2192             :                 }
    2193             :             }
    2194             :         }
    2195             :     }
    2196          19 :     else if (m_outputFormat == "WEBP")
    2197             :     {
    2198           2 :         if (poSrcDS->GetRasterCount() != 3 && poSrcDS->GetRasterCount() != 4)
    2199             :         {
    2200           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2201             :                         "Only 3 or 4 bands supported for WEBP.");
    2202           1 :             return false;
    2203             :         }
    2204           1 :         if (eSrcDT != GDT_Byte)
    2205             :         {
    2206           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2207             :                         "Only Byte data type supported for WEBP.");
    2208           1 :             return false;
    2209             :         }
    2210             :     }
    2211             : 
    2212             :     const char *pszExtensions =
    2213          76 :         poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
    2214          76 :     CPLAssert(pszExtensions && pszExtensions[0] != 0);
    2215             :     const CPLStringList aosExtensions(
    2216         152 :         CSLTokenizeString2(pszExtensions, " ", 0));
    2217          76 :     const char *pszExtension = aosExtensions[0];
    2218             :     std::array<double, 6> adfSrcGeoTransform;
    2219             :     const bool bHasSrcGT =
    2220          76 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform.data()) == CE_None;
    2221          74 :     const bool bHasNorthUpSrcGT = bHasSrcGT && adfSrcGeoTransform[2] == 0 &&
    2222         224 :                                   adfSrcGeoTransform[4] == 0 &&
    2223          74 :                                   adfSrcGeoTransform[5] < 0;
    2224         152 :     OGRSpatialReference oSRS_TMS;
    2225             : 
    2226          76 :     if (m_tilingScheme == "raster")
    2227             :     {
    2228           4 :         if (const auto poSRS = poSrcDS->GetSpatialRef())
    2229           3 :             oSRS_TMS = *poSRS;
    2230             :     }
    2231             :     else
    2232             :     {
    2233           1 :         if (!bHasSrcGT && poSrcDS->GetGCPCount() == 0 &&
    2234          74 :             poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    2235           1 :             poSrcDS->GetMetadata("RPC") == nullptr)
    2236             :         {
    2237           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2238             :                         "Ungeoreferenced datasets are not supported, unless "
    2239             :                         "'tiling-scheme' is set to 'raster'");
    2240           1 :             return false;
    2241             :         }
    2242             : 
    2243          71 :         if (poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
    2244          71 :             poSrcDS->GetMetadata("RPC") == nullptr &&
    2245         143 :             poSrcDS->GetSpatialRef() == nullptr &&
    2246           1 :             poSrcDS->GetGCPSpatialRef() == nullptr)
    2247             :         {
    2248           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    2249             :                         "Ungeoreferenced datasets are not supported, unless "
    2250             :                         "'tiling-scheme' is set to 'raster'");
    2251           1 :             return false;
    2252             :         }
    2253             :     }
    2254             : 
    2255          74 :     if (m_copySrcMetadata)
    2256             :     {
    2257           8 :         CPLStringList aosMD(CSLDuplicate(poSrcDS->GetMetadata()));
    2258           4 :         const CPLStringList aosNewMD(m_metadata);
    2259           8 :         for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
    2260             :         {
    2261           4 :             aosMD.SetNameValue(key, value);
    2262             :         }
    2263           4 :         m_metadata = aosMD;
    2264             :     }
    2265             : 
    2266          74 :     std::array<double, 6> adfSrcGeoTransformModif{0, 1, 0, 0, 0, -1};
    2267             : 
    2268          74 :     if (m_tilingScheme == "mercator")
    2269           1 :         m_tilingScheme = "WebMercatorQuad";
    2270          73 :     else if (m_tilingScheme == "geodetic")
    2271           1 :         m_tilingScheme = "WorldCRS84Quad";
    2272          72 :     else if (m_tilingScheme == "raster")
    2273             :     {
    2274           4 :         if (m_tileSize == 0)
    2275           4 :             m_tileSize = 256;
    2276           4 :         if (m_maxZoomLevel < 0)
    2277             :         {
    2278           3 :             m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
    2279           6 :                 std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
    2280             :         }
    2281           4 :         if (bHasNorthUpSrcGT)
    2282             :         {
    2283           3 :             adfSrcGeoTransformModif = adfSrcGeoTransform;
    2284             :         }
    2285             :     }
    2286             : 
    2287             :     auto poTMS =
    2288          74 :         m_tilingScheme == "raster"
    2289             :             ? gdal::TileMatrixSet::createRaster(
    2290           4 :                   nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
    2291           8 :                   adfSrcGeoTransformModif[0], adfSrcGeoTransformModif[3],
    2292           8 :                   adfSrcGeoTransformModif[1], -adfSrcGeoTransformModif[5],
    2293          78 :                   oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
    2294             :             : gdal::TileMatrixSet::parse(
    2295         152 :                   m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
    2296             :     // Enforced by SetChoices() on the m_tilingScheme argument
    2297          74 :     CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
    2298             : 
    2299         148 :     CPLStringList aosTO;
    2300          74 :     if (m_tilingScheme == "raster")
    2301             :     {
    2302           4 :         aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
    2303             :     }
    2304             :     else
    2305             :     {
    2306          70 :         CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
    2307          70 :         aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
    2308             :     }
    2309             : 
    2310          74 :     const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
    2311          74 :     const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
    2312          74 :     const int nEPSGCode =
    2313          73 :         (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    2314         147 :             ? atoi(pszAuthCode)
    2315             :             : 0;
    2316             : 
    2317             :     const bool bInvertAxisTMS =
    2318         144 :         m_tilingScheme != "raster" &&
    2319          70 :         (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
    2320          70 :          oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
    2321             : 
    2322          74 :     oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2323             : 
    2324             :     std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
    2325         148 :         nullptr, GDALDestroyTransformer);
    2326             : 
    2327             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    2328             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    2329             :     // EPSG:3857.
    2330          74 :     std::unique_ptr<GDALDataset> poTmpDS;
    2331          74 :     bool bEPSG3857Adjust = false;
    2332          74 :     if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
    2333             :     {
    2334          48 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    2335          48 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    2336             :         {
    2337          17 :             double maxLat = adfSrcGeoTransform[3];
    2338             :             double minLat =
    2339          17 :                 adfSrcGeoTransform[3] + nSrcHeight * adfSrcGeoTransform[5];
    2340             :             // Corresponds to the latitude of below MAX_GM
    2341          17 :             constexpr double MAX_LAT = 85.0511287798066;
    2342          17 :             bool bModified = false;
    2343          17 :             if (maxLat > MAX_LAT)
    2344             :             {
    2345          16 :                 maxLat = MAX_LAT;
    2346          16 :                 bModified = true;
    2347             :             }
    2348          17 :             if (minLat < -MAX_LAT)
    2349             :             {
    2350          16 :                 minLat = -MAX_LAT;
    2351          16 :                 bModified = true;
    2352             :             }
    2353          17 :             if (bModified)
    2354             :             {
    2355          32 :                 CPLStringList aosOptions;
    2356          16 :                 aosOptions.AddString("-of");
    2357          16 :                 aosOptions.AddString("VRT");
    2358          16 :                 aosOptions.AddString("-projwin");
    2359             :                 aosOptions.AddString(
    2360          16 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
    2361          16 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    2362             :                 aosOptions.AddString(
    2363          16 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
    2364          16 :                                             nSrcWidth * adfSrcGeoTransform[1]));
    2365          16 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    2366             :                 auto psOptions =
    2367          16 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    2368          16 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    2369             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    2370          16 :                 GDALTranslateOptionsFree(psOptions);
    2371          16 :                 if (poTmpDS)
    2372             :                 {
    2373          16 :                     bEPSG3857Adjust = true;
    2374          16 :                     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    2375          16 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    2376          16 :                         aosTO.List()));
    2377             :                 }
    2378             :             }
    2379             :         }
    2380             :     }
    2381             : 
    2382             :     std::array<double, 6> adfDstGeoTransform;
    2383             :     double adfExtent[4];
    2384             :     int nXSize, nYSize;
    2385             : 
    2386             :     bool bSuggestOK;
    2387          74 :     if (m_tilingScheme == "raster")
    2388             :     {
    2389           4 :         bSuggestOK = true;
    2390           4 :         nXSize = nSrcWidth;
    2391           4 :         nYSize = nSrcHeight;
    2392           4 :         adfDstGeoTransform = adfSrcGeoTransformModif;
    2393           4 :         adfExtent[0] = adfDstGeoTransform[0];
    2394           4 :         adfExtent[1] =
    2395           4 :             adfDstGeoTransform[3] + nSrcHeight * adfDstGeoTransform[5];
    2396           4 :         adfExtent[2] =
    2397           4 :             adfDstGeoTransform[0] + nSrcWidth * adfDstGeoTransform[1];
    2398           4 :         adfExtent[3] = adfDstGeoTransform[3];
    2399             :     }
    2400             :     else
    2401             :     {
    2402          70 :         if (!hTransformArg)
    2403             :         {
    2404          54 :             hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    2405          54 :                 poSrcDS, nullptr, aosTO.List()));
    2406             :         }
    2407          70 :         if (!hTransformArg)
    2408             :         {
    2409           1 :             return false;
    2410             :         }
    2411          69 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    2412          69 :         bSuggestOK =
    2413         138 :             (GDALSuggestedWarpOutput2(
    2414             :                  poSrcDS,
    2415          69 :                  static_cast<GDALTransformerInfo *>(hTransformArg.get())
    2416             :                      ->pfnTransform,
    2417             :                  hTransformArg.get(), adfDstGeoTransform.data(), &nXSize,
    2418             :                  &nYSize, adfExtent, 0) == CE_None);
    2419             :     }
    2420          73 :     if (!bSuggestOK)
    2421             :     {
    2422           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    2423             :                     "Cannot determine extent of raster in target CRS");
    2424           1 :         return false;
    2425             :     }
    2426             : 
    2427          72 :     poTmpDS.reset();
    2428             : 
    2429          72 :     if (bEPSG3857Adjust)
    2430             :     {
    2431          16 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    2432          16 :         constexpr double MAX_GM =
    2433             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    2434          16 :         double maxNorthing = adfDstGeoTransform[3];
    2435             :         double minNorthing =
    2436          16 :             adfDstGeoTransform[3] + adfDstGeoTransform[5] * nYSize;
    2437          16 :         bool bChanged = false;
    2438          16 :         if (maxNorthing > MAX_GM)
    2439             :         {
    2440          13 :             bChanged = true;
    2441          13 :             maxNorthing = MAX_GM;
    2442             :         }
    2443          16 :         if (minNorthing < -MAX_GM)
    2444             :         {
    2445          13 :             bChanged = true;
    2446          13 :             minNorthing = -MAX_GM;
    2447             :         }
    2448          16 :         if (bChanged)
    2449             :         {
    2450          13 :             adfDstGeoTransform[3] = maxNorthing;
    2451          13 :             nYSize = int(
    2452          13 :                 (maxNorthing - minNorthing) / (-adfDstGeoTransform[5]) + 0.5);
    2453          13 :             adfExtent[1] = maxNorthing + nYSize * adfDstGeoTransform[5];
    2454          13 :             adfExtent[3] = maxNorthing;
    2455             :         }
    2456             :     }
    2457             : 
    2458          72 :     const auto &tileMatrixList = poTMS->tileMatrixList();
    2459          72 :     if (m_maxZoomLevel >= 0)
    2460             :     {
    2461          23 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    2462             :         {
    2463           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    2464             :                         "max-zoom = %d is invalid. It must be in [0,%d] range",
    2465             :                         m_maxZoomLevel,
    2466           1 :                         static_cast<int>(tileMatrixList.size()) - 1);
    2467           1 :             return false;
    2468             :         }
    2469             :     }
    2470             :     else
    2471             :     {
    2472          49 :         const double dfComputedRes = adfDstGeoTransform[1];
    2473          49 :         double dfPrevRes = 0.0;
    2474          49 :         double dfRes = 0.0;
    2475          49 :         constexpr double EPSILON = 1e-8;
    2476             : 
    2477          49 :         if (m_minZoomLevel >= 0)
    2478          17 :             m_maxZoomLevel = m_minZoomLevel;
    2479             :         else
    2480          32 :             m_maxZoomLevel = 0;
    2481             : 
    2482         164 :         for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
    2483         115 :              m_maxZoomLevel++)
    2484             :         {
    2485         163 :             dfRes = tileMatrixList[m_maxZoomLevel].mResX;
    2486         163 :             if (dfComputedRes > dfRes ||
    2487         115 :                 fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
    2488             :                 break;
    2489         115 :             dfPrevRes = dfRes;
    2490             :         }
    2491          49 :         if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
    2492             :         {
    2493           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    2494             :                         "Could not find an appropriate zoom level. Perhaps "
    2495             :                         "min-zoom is too large?");
    2496           1 :             return false;
    2497             :         }
    2498             : 
    2499          48 :         if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
    2500             :         {
    2501             :             // Round to closest resolution
    2502          43 :             if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    2503          23 :                 m_maxZoomLevel--;
    2504             :         }
    2505             :     }
    2506          70 :     if (m_minZoomLevel < 0)
    2507          40 :         m_minZoomLevel = m_maxZoomLevel;
    2508             : 
    2509         140 :     auto tileMatrix = tileMatrixList[m_maxZoomLevel];
    2510          70 :     int nMinTileX = 0;
    2511          70 :     int nMinTileY = 0;
    2512          70 :     int nMaxTileX = 0;
    2513          70 :     int nMaxTileY = 0;
    2514          70 :     bool bIntersects = false;
    2515          70 :     if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    2516             :                         nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
    2517          70 :                         m_noIntersectionIsOK, bIntersects,
    2518             :                         /* checkRasterOverflow = */ false))
    2519             :     {
    2520           1 :         return false;
    2521             :     }
    2522          69 :     if (!bIntersects)
    2523           1 :         return true;
    2524             : 
    2525             :     // Potentially restrict tiling to user specified coordinates
    2526          68 :     if (m_minTileX >= tileMatrix.mMatrixWidth)
    2527             :     {
    2528           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    2529             :                     "'min-x' value must be in [0,%d] range",
    2530           1 :                     tileMatrix.mMatrixWidth - 1);
    2531           1 :         return false;
    2532             :     }
    2533          67 :     if (m_maxTileX >= tileMatrix.mMatrixWidth)
    2534             :     {
    2535           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    2536             :                     "'max-x' value must be in [0,%d] range",
    2537           1 :                     tileMatrix.mMatrixWidth - 1);
    2538           1 :         return false;
    2539             :     }
    2540          66 :     if (m_minTileY >= tileMatrix.mMatrixHeight)
    2541             :     {
    2542           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    2543             :                     "'min-y' value must be in [0,%d] range",
    2544           1 :                     tileMatrix.mMatrixHeight - 1);
    2545           1 :         return false;
    2546             :     }
    2547          65 :     if (m_maxTileY >= tileMatrix.mMatrixHeight)
    2548             :     {
    2549           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
    2550             :                     "'max-y' value must be in [0,%d] range",
    2551           1 :                     tileMatrix.mMatrixHeight - 1);
    2552           1 :         return false;
    2553             :     }
    2554             : 
    2555          64 :     if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
    2556          62 :         (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
    2557          62 :         (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
    2558          62 :         (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
    2559             :     {
    2560           2 :         ReportError(
    2561           2 :             m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
    2562             :             "Dataset extent not intersecting specified min/max X/Y tile "
    2563             :             "coordinates");
    2564           2 :         return m_noIntersectionIsOK;
    2565             :     }
    2566          62 :     if (m_minTileX >= 0 && m_minTileX > nMinTileX)
    2567             :     {
    2568           2 :         nMinTileX = m_minTileX;
    2569           2 :         adfExtent[0] = tileMatrix.mTopLeftX +
    2570           2 :                        nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
    2571             :     }
    2572          62 :     if (m_minTileY >= 0 && m_minTileY > nMinTileY)
    2573             :     {
    2574           1 :         nMinTileY = m_minTileY;
    2575           1 :         adfExtent[3] = tileMatrix.mTopLeftY -
    2576           1 :                        nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
    2577             :     }
    2578          62 :     if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
    2579             :     {
    2580           2 :         nMaxTileX = m_maxTileX;
    2581           2 :         adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
    2582           2 :                                                   tileMatrix.mResX *
    2583           2 :                                                   tileMatrix.mTileWidth;
    2584             :     }
    2585          62 :     if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
    2586             :     {
    2587           2 :         nMaxTileY = m_maxTileY;
    2588           2 :         adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
    2589           2 :                                                   tileMatrix.mResY *
    2590           2 :                                                   tileMatrix.mTileHeight;
    2591             :     }
    2592             : 
    2593          62 :     if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
    2594          61 :         nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
    2595             :     {
    2596           1 :         ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
    2597           1 :         return false;
    2598             :     }
    2599             : 
    2600         122 :     adfDstGeoTransform[0] = tileMatrix.mTopLeftX + nMinTileX *
    2601          61 :                                                        tileMatrix.mResX *
    2602          61 :                                                        tileMatrix.mTileWidth;
    2603          61 :     adfDstGeoTransform[1] = tileMatrix.mResX;
    2604          61 :     adfDstGeoTransform[2] = 0;
    2605         122 :     adfDstGeoTransform[3] = tileMatrix.mTopLeftY - nMinTileY *
    2606          61 :                                                        tileMatrix.mResY *
    2607          61 :                                                        tileMatrix.mTileHeight;
    2608          61 :     adfDstGeoTransform[4] = 0;
    2609          61 :     adfDstGeoTransform[5] = -tileMatrix.mResY;
    2610             : 
    2611             :     /* -------------------------------------------------------------------- */
    2612             :     /*      Setup warp options.                                             */
    2613             :     /* -------------------------------------------------------------------- */
    2614             :     std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
    2615         122 :         GDALCreateWarpOptions(), GDALDestroyWarpOptions);
    2616             : 
    2617          61 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    2618         122 :     psWO->papszWarpOptions =
    2619          61 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    2620         122 :     psWO->papszWarpOptions =
    2621          61 :         CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
    2622             : 
    2623          61 :     int bHasSrcNoData = false;
    2624             :     const double dfSrcNoDataValue =
    2625          61 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
    2626             : 
    2627             :     const bool bLastSrcBandIsAlpha =
    2628          93 :         (poSrcDS->GetRasterCount() > 1 &&
    2629          32 :          poSrcDS->GetRasterBand(poSrcDS->GetRasterCount())
    2630          32 :                  ->GetColorInterpretation() == GCI_AlphaBand);
    2631             : 
    2632          61 :     const bool bOutputSupportsAlpha = !EQUAL(m_outputFormat.c_str(), "JPEG");
    2633          61 :     const bool bOutputSupportsNoData = EQUAL(m_outputFormat.c_str(), "GTiff");
    2634          61 :     const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
    2635             :     const GDALColorTable *poColorTable =
    2636          61 :         poSrcDS->GetRasterBand(1)->GetColorTable();
    2637             : 
    2638          61 :     const bool bUserAskedForAlpha = m_addalpha;
    2639          61 :     if (!m_noalpha && !m_addalpha)
    2640             :     {
    2641          71 :         m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
    2642          71 :                      !bDstNoDataSpecified && poColorTable == nullptr;
    2643             :     }
    2644          61 :     m_addalpha &= bOutputSupportsAlpha;
    2645             : 
    2646          61 :     psWO->nBandCount = poSrcDS->GetRasterCount();
    2647          61 :     if (bLastSrcBandIsAlpha)
    2648             :     {
    2649           5 :         --psWO->nBandCount;
    2650           5 :         psWO->nSrcAlphaBand = poSrcDS->GetRasterCount();
    2651             :     }
    2652             : 
    2653          61 :     if (bHasSrcNoData)
    2654             :     {
    2655          26 :         psWO->padfSrcNoDataReal =
    2656          13 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    2657          32 :         for (int i = 0; i < psWO->nBandCount; ++i)
    2658             :         {
    2659          19 :             psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
    2660             :         }
    2661             :     }
    2662             : 
    2663          61 :     if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
    2664             :         bDstNoDataSpecified)
    2665             :     {
    2666          18 :         psWO->padfDstNoDataReal =
    2667           9 :             static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
    2668          18 :         for (int i = 0; i < psWO->nBandCount; ++i)
    2669             :         {
    2670           9 :             psWO->padfDstNoDataReal[i] =
    2671           9 :                 bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
    2672             :         }
    2673             :     }
    2674             : 
    2675          61 :     psWO->eWorkingDataType = eSrcDT;
    2676             : 
    2677          61 :     GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
    2678             : 
    2679             :     /* -------------------------------------------------------------------- */
    2680             :     /*      Setup band mapping.                                             */
    2681             :     /* -------------------------------------------------------------------- */
    2682             : 
    2683         122 :     psWO->panSrcBands =
    2684          61 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    2685         122 :     psWO->panDstBands =
    2686          61 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    2687             : 
    2688       65719 :     for (int i = 0; i < psWO->nBandCount; i++)
    2689             :     {
    2690       65658 :         psWO->panSrcBands[i] = i + 1;
    2691       65658 :         psWO->panDstBands[i] = i + 1;
    2692             :     }
    2693             : 
    2694          61 :     if (m_addalpha)
    2695          48 :         psWO->nDstAlphaBand = psWO->nBandCount + 1;
    2696             : 
    2697             :     const int nDstBands =
    2698          61 :         psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
    2699             : 
    2700         122 :     std::vector<GByte> dstBuffer;
    2701             :     const uint64_t dstBufferSize =
    2702          61 :         static_cast<uint64_t>(tileMatrix.mTileWidth) * tileMatrix.mTileHeight *
    2703          61 :         nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType);
    2704             :     const uint64_t nUsableRAM =
    2705          61 :         std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
    2706          61 :     if (dstBufferSize <=
    2707          61 :         (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
    2708             :     {
    2709             :         try
    2710             :         {
    2711          60 :             dstBuffer.resize(static_cast<size_t>(dstBufferSize));
    2712             :         }
    2713           0 :         catch (const std::exception &)
    2714             :         {
    2715             :         }
    2716             :     }
    2717          61 :     if (dstBuffer.size() < dstBufferSize)
    2718             :     {
    2719           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    2720             :                     "Tile size and/or number of bands too large compared to "
    2721             :                     "available RAM");
    2722           1 :         return false;
    2723             :     }
    2724             : 
    2725             :     FakeMaxZoomDataset oFakeMaxZoomDS(
    2726          60 :         (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth,
    2727          60 :         (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight, nDstBands,
    2728          60 :         tileMatrix.mTileWidth, tileMatrix.mTileHeight, psWO->eWorkingDataType,
    2729         180 :         adfDstGeoTransform.data(), oSRS_TMS, dstBuffer);
    2730          60 :     CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
    2731             : 
    2732          60 :     psWO->hSrcDS = GDALDataset::ToHandle(poSrcDS);
    2733          60 :     psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
    2734             : 
    2735          60 :     std::unique_ptr<GDALDataset> tmpSrcDS;
    2736          60 :     if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
    2737             :     {
    2738           1 :         CPLStringList aosOptions;
    2739           1 :         aosOptions.AddString("-of");
    2740           1 :         aosOptions.AddString("VRT");
    2741           1 :         aosOptions.AddString("-a_ullr");
    2742           1 :         aosOptions.AddString(CPLSPrintf("%.17g", adfSrcGeoTransformModif[0]));
    2743           1 :         aosOptions.AddString(CPLSPrintf("%.17g", adfSrcGeoTransformModif[3]));
    2744             :         aosOptions.AddString(
    2745           1 :             CPLSPrintf("%.17g", adfSrcGeoTransformModif[0] +
    2746           1 :                                     nSrcWidth * adfSrcGeoTransformModif[1]));
    2747             :         aosOptions.AddString(
    2748           1 :             CPLSPrintf("%.17g", adfSrcGeoTransformModif[3] +
    2749           1 :                                     nSrcHeight * adfSrcGeoTransformModif[5]));
    2750           1 :         if (oSRS_TMS.IsEmpty())
    2751             :         {
    2752           1 :             aosOptions.AddString("-a_srs");
    2753           1 :             aosOptions.AddString("none");
    2754             :         }
    2755             : 
    2756             :         GDALTranslateOptions *psOptions =
    2757           1 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    2758             : 
    2759           1 :         tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
    2760             :             "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    2761           1 :         GDALTranslateOptionsFree(psOptions);
    2762           1 :         if (!tmpSrcDS)
    2763           0 :             return false;
    2764             :     }
    2765          61 :     hTransformArg.reset(GDALCreateGenImgProjTransformer2(
    2766          61 :         tmpSrcDS ? tmpSrcDS.get() : poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
    2767          60 :     CPLAssert(hTransformArg);
    2768             : 
    2769             :     /* -------------------------------------------------------------------- */
    2770             :     /*      Warp the transformer with a linear approximator                 */
    2771             :     /* -------------------------------------------------------------------- */
    2772          60 :     hTransformArg.reset(GDALCreateApproxTransformer(
    2773             :         GDALGenImgProjTransform, hTransformArg.release(), 0.125));
    2774          60 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
    2775             : 
    2776          60 :     psWO->pfnTransformer = GDALApproxTransform;
    2777          60 :     psWO->pTransformerArg = hTransformArg.get();
    2778             : 
    2779             :     /* -------------------------------------------------------------------- */
    2780             :     /*      Determine total number of tiles                                 */
    2781             :     /* -------------------------------------------------------------------- */
    2782          60 :     uint64_t nTotalTiles = static_cast<uint64_t>(nMaxTileY - nMinTileY + 1) *
    2783          60 :                            (nMaxTileX - nMinTileX + 1);
    2784          60 :     const uint64_t nBaseTiles = nTotalTiles;
    2785          60 :     std::atomic<uint64_t> nCurTile = 0;
    2786          60 :     bool bRet = true;
    2787             : 
    2788          95 :     for (int iZ = m_maxZoomLevel - 1;
    2789          95 :          bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
    2790             :     {
    2791          70 :         auto ovrTileMatrix = tileMatrixList[iZ];
    2792          35 :         int nOvrMinTileX = 0;
    2793          35 :         int nOvrMinTileY = 0;
    2794          35 :         int nOvrMaxTileX = 0;
    2795          35 :         int nOvrMaxTileY = 0;
    2796             :         bRet =
    2797          70 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    2798             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    2799          35 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
    2800          35 :         if (bIntersects)
    2801             :         {
    2802          35 :             nTotalTiles +=
    2803          35 :                 static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
    2804          35 :                 (nOvrMaxTileX - nOvrMinTileX + 1);
    2805             :         }
    2806             :     }
    2807             : 
    2808             :     /* -------------------------------------------------------------------- */
    2809             :     /*      Generate tiles at max zoom level                                */
    2810             :     /* -------------------------------------------------------------------- */
    2811         120 :     GDALWarpOperation oWO;
    2812             : 
    2813          60 :     bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
    2814             : 
    2815             :     const auto GetUpdatedCreationOptions =
    2816         260 :         [this](const gdal::TileMatrixSet::TileMatrix &oTM)
    2817             :     {
    2818          94 :         CPLStringList aosCreationOptions(m_creationOptions);
    2819          94 :         if (m_outputFormat == "GTiff")
    2820             :         {
    2821          48 :             if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
    2822          24 :                 aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
    2823             :             {
    2824          24 :                 if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
    2825             :                 {
    2826          22 :                     aosCreationOptions.SetNameValue(
    2827          22 :                         "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
    2828             :                 }
    2829             :                 else
    2830             :                 {
    2831           2 :                     aosCreationOptions.SetNameValue("TILED", "YES");
    2832             :                 }
    2833             :             }
    2834          24 :             if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
    2835          24 :                 aosCreationOptions.SetNameValue("COMPRESS", "LZW");
    2836             :         }
    2837          70 :         else if (m_outputFormat == "COG")
    2838             :         {
    2839           2 :             if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
    2840             :                 nullptr)
    2841             :             {
    2842             :                 aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
    2843           2 :                                                 m_overviewResampling.c_str());
    2844             :             }
    2845           2 :             if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
    2846           2 :                 oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
    2847             :             {
    2848             :                 aosCreationOptions.SetNameValue(
    2849           2 :                     "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
    2850             :             }
    2851             :         }
    2852          94 :         return aosCreationOptions;
    2853          60 :     };
    2854             : 
    2855          60 :     VSIMkdir(m_outputDirectory.c_str(), 0755);
    2856             :     VSIStatBufL sStat;
    2857         119 :     if (VSIStatL(m_outputDirectory.c_str(), &sStat) != 0 ||
    2858          59 :         !VSI_ISDIR(sStat.st_mode))
    2859             :     {
    2860           1 :         ReportError(CE_Failure, CPLE_FileIO,
    2861             :                     "Cannot create output directory %s",
    2862             :                     m_outputDirectory.c_str());
    2863           1 :         return false;
    2864             :     }
    2865             : 
    2866         118 :     OGRSpatialReference oWGS84;
    2867          59 :     oWGS84.importFromEPSG(4326);
    2868          59 :     oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2869             : 
    2870          59 :     std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
    2871          59 :     if (!oSRS_TMS.IsEmpty())
    2872             :     {
    2873         116 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    2874          58 :         poCTToWGS84.reset(
    2875             :             OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
    2876             :     }
    2877             : 
    2878          61 :     const bool kmlCompatible = m_kml &&
    2879          17 :                                [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
    2880             :     {
    2881           2 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    2882           2 :         double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
    2883           2 :         double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
    2884           2 :         if (bInvertAxisTMS)
    2885           0 :             std::swap(dfX, dfY);
    2886           3 :         return (m_minZoomLevel == m_maxZoomLevel ||
    2887           1 :                 (poTMS->haveAllLevelsSameTopLeft() &&
    2888           1 :                  poTMS->haveAllLevelsSameTileSize() &&
    2889           3 :                  poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
    2890           7 :                poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
    2891           2 :     }();
    2892             :     const int kmlTileSize =
    2893          59 :         m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
    2894          59 :     if (m_kml && !kmlCompatible)
    2895             :     {
    2896           0 :         ReportError(CE_Failure, CPLE_NotSupported,
    2897             :                     "Tiling scheme not compatible with KML output");
    2898           0 :         return false;
    2899             :     }
    2900             : 
    2901          59 :     if (m_title.empty())
    2902          57 :         m_title = CPLGetFilename(m_dataset.GetName().c_str());
    2903             : 
    2904          59 :     if (!m_url.empty())
    2905             :     {
    2906           2 :         if (m_url.back() != '/')
    2907           2 :             m_url += '/';
    2908           4 :         std::string out_path = m_outputDirectory;
    2909           2 :         if (m_outputDirectory.back() == '/')
    2910           0 :             out_path.pop_back();
    2911           2 :         m_url += CPLGetFilename(out_path.c_str());
    2912             :     }
    2913             : 
    2914          59 :     CPLWorkerThreadPool oThreadPool;
    2915             : 
    2916             :     {
    2917             :         PerThreadMaxZoomResourceManager oResourceManager(
    2918          59 :             poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
    2919         177 :             dstBuffer.size());
    2920             : 
    2921             :         const CPLStringList aosCreationOptions(
    2922         118 :             GetUpdatedCreationOptions(tileMatrix));
    2923             : 
    2924          59 :         CPLDebug("gdal_raster_tile",
    2925             :                  "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
    2926             :                  nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
    2927             : 
    2928          59 :         if (static_cast<uint64_t>(m_numThreads) > nBaseTiles)
    2929          37 :             m_numThreads = static_cast<int>(nBaseTiles);
    2930             : 
    2931          59 :         if (bRet && m_numThreads > 1)
    2932             :         {
    2933          28 :             CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
    2934          28 :             bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
    2935             :         }
    2936             : 
    2937          59 :         std::atomic<bool> bFailure = false;
    2938          59 :         std::atomic<int> nQueuedJobs = 0;
    2939             : 
    2940         156 :         for (int iY = nMinTileY; bRet && iY <= nMaxTileY; ++iY)
    2941             :         {
    2942         360 :             for (int iX = nMinTileX; bRet && iX <= nMaxTileX; ++iX)
    2943             :             {
    2944         263 :                 if (m_numThreads > 1)
    2945             :                 {
    2946         232 :                     auto job = [this, &oResourceManager, &bFailure, &nCurTile,
    2947             :                                 &nQueuedJobs, poDstDriver, pszExtension,
    2948             :                                 &aosCreationOptions, &psWO, &tileMatrix,
    2949             :                                 nDstBands, iX, iY, nMinTileX, nMinTileY,
    2950        1870 :                                 poColorTable, bUserAskedForAlpha]()
    2951             :                     {
    2952         464 :                         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    2953             : 
    2954         231 :                         --nQueuedJobs;
    2955         463 :                         auto resources = oResourceManager.AcquireResources();
    2956         464 :                         if (resources &&
    2957         696 :                             GenerateTile(
    2958         232 :                                 resources->poSrcDS.get(), poDstDriver,
    2959             :                                 pszExtension, aosCreationOptions.List(),
    2960         232 :                                 *(resources->poWO.get()),
    2961         232 :                                 *(resources->poFakeMaxZoomDS->GetSpatialRef()),
    2962         232 :                                 psWO->eWorkingDataType, tileMatrix,
    2963         232 :                                 m_outputDirectory, nDstBands,
    2964         232 :                                 psWO->padfDstNoDataReal
    2965           0 :                                     ? &(psWO->padfDstNoDataReal[0])
    2966             :                                     : nullptr,
    2967         232 :                                 m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
    2968         232 :                                 nMinTileY, m_skipBlank, bUserAskedForAlpha,
    2969         232 :                                 m_auxXML, m_resume, m_metadata, poColorTable,
    2970         464 :                                 resources->dstBuffer))
    2971             :                         {
    2972         216 :                             oResourceManager.ReleaseResources(
    2973         216 :                                 std::move(resources));
    2974             :                         }
    2975             :                         else
    2976             :                         {
    2977          16 :                             oResourceManager.SetError();
    2978          16 :                             bFailure = true;
    2979             :                         }
    2980         232 :                         ++nCurTile;
    2981         464 :                     };
    2982             : 
    2983             :                     // Avoid queueing too many jobs at once
    2984         253 :                     while (bRet && nQueuedJobs / 10 > m_numThreads)
    2985             :                     {
    2986          21 :                         oThreadPool.WaitEvent();
    2987             : 
    2988          21 :                         bRet &=
    2989          32 :                             !bFailure &&
    2990          11 :                             (!pfnProgress ||
    2991          22 :                              pfnProgress(static_cast<double>(nCurTile) /
    2992          11 :                                              static_cast<double>(nTotalTiles),
    2993          21 :                                          "", pProgressData));
    2994             :                     }
    2995             : 
    2996         232 :                     ++nQueuedJobs;
    2997         232 :                     oThreadPool.SubmitJob(std::move(job));
    2998             :                 }
    2999             :                 else
    3000             :                 {
    3001          62 :                     bRet = GenerateTile(
    3002             :                         poSrcDS, poDstDriver, pszExtension,
    3003             :                         aosCreationOptions.List(), oWO, oSRS_TMS,
    3004          31 :                         psWO->eWorkingDataType, tileMatrix, m_outputDirectory,
    3005             :                         nDstBands,
    3006          31 :                         psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
    3007             :                                                 : nullptr,
    3008          31 :                         m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
    3009          31 :                         nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
    3010          31 :                         m_resume, m_metadata, poColorTable, dstBuffer);
    3011             : 
    3012          31 :                     ++nCurTile;
    3013          33 :                     bRet &= (!pfnProgress ||
    3014           4 :                              pfnProgress(static_cast<double>(nCurTile) /
    3015           2 :                                              static_cast<double>(nTotalTiles),
    3016          31 :                                          "", pProgressData));
    3017             :                 }
    3018             :             }
    3019             :         }
    3020             : 
    3021          59 :         if (m_numThreads > 1)
    3022             :         {
    3023             :             // Wait for completion of all jobs
    3024         154 :             while (bRet && nQueuedJobs > 0)
    3025             :             {
    3026         126 :                 oThreadPool.WaitEvent();
    3027         175 :                 bRet &= !bFailure &&
    3028          49 :                         (!pfnProgress ||
    3029          98 :                          pfnProgress(static_cast<double>(nCurTile) /
    3030          49 :                                          static_cast<double>(nTotalTiles),
    3031         126 :                                      "", pProgressData));
    3032             :             }
    3033          28 :             oThreadPool.WaitCompletion();
    3034          28 :             bRet &=
    3035          29 :                 !bFailure && (!pfnProgress ||
    3036           2 :                               pfnProgress(static_cast<double>(nCurTile) /
    3037           1 :                                               static_cast<double>(nTotalTiles),
    3038          28 :                                           "", pProgressData));
    3039             : 
    3040          28 :             if (!oResourceManager.GetErrorMsg().empty())
    3041             :             {
    3042             :                 // Re-emit error message from worker thread to main thread
    3043           1 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s",
    3044           1 :                             oResourceManager.GetErrorMsg().c_str());
    3045             :             }
    3046             :         }
    3047             : 
    3048          59 :         if (m_kml && bRet)
    3049             :         {
    3050           4 :             for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
    3051             :             {
    3052           4 :                 for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
    3053             :                 {
    3054             :                     const int nFileY =
    3055           2 :                         GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
    3056           2 :                                  m_convention);
    3057             :                     std::string osFilename = CPLFormFilenameSafe(
    3058             :                         m_outputDirectory.c_str(),
    3059           4 :                         CPLSPrintf("%d", m_maxZoomLevel), nullptr);
    3060           4 :                     osFilename = CPLFormFilenameSafe(
    3061           2 :                         osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
    3062           4 :                     osFilename = CPLFormFilenameSafe(
    3063             :                         osFilename.c_str(),
    3064           2 :                         CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    3065           2 :                     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    3066             :                     {
    3067           4 :                         GenerateKML(m_outputDirectory, m_title, iX, iY,
    3068             :                                     m_maxZoomLevel, kmlTileSize, pszExtension,
    3069           2 :                                     m_url, poTMS.get(), bInvertAxisTMS,
    3070           2 :                                     m_convention, poCTToWGS84.get(), {});
    3071             :                     }
    3072             :                 }
    3073             :             }
    3074             :         }
    3075             :     }
    3076             : 
    3077             :     /* -------------------------------------------------------------------- */
    3078             :     /*      Generate tiles at lower zoom levels                             */
    3079             :     /* -------------------------------------------------------------------- */
    3080          94 :     for (int iZ = m_maxZoomLevel - 1; bRet && iZ >= m_minZoomLevel; --iZ)
    3081             :     {
    3082          70 :         auto srcTileMatrix = tileMatrixList[iZ + 1];
    3083          35 :         int nSrcMinTileX = 0;
    3084          35 :         int nSrcMinTileY = 0;
    3085          35 :         int nSrcMaxTileX = 0;
    3086          35 :         int nSrcMaxTileY = 0;
    3087             : 
    3088          35 :         CPL_IGNORE_RET_VAL(
    3089          35 :             GetTileIndices(srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    3090             :                            nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX,
    3091          35 :                            nSrcMaxTileY, m_noIntersectionIsOK, bIntersects));
    3092             : 
    3093             :         MosaicDataset oSrcDS(
    3094          70 :             CPLFormFilenameSafe(m_outputDirectory.c_str(),
    3095             :                                 CPLSPrintf("%d", iZ + 1), nullptr),
    3096          35 :             pszExtension, m_outputFormat, poSrcDS, srcTileMatrix, oSRS_TMS,
    3097             :             nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
    3098          35 :             m_convention, nDstBands, psWO->eWorkingDataType,
    3099           9 :             psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0]) : nullptr,
    3100         184 :             m_metadata, poColorTable);
    3101             : 
    3102          70 :         auto ovrTileMatrix = tileMatrixList[iZ];
    3103          35 :         int nOvrMinTileX = 0;
    3104          35 :         int nOvrMinTileY = 0;
    3105          35 :         int nOvrMaxTileX = 0;
    3106          35 :         int nOvrMaxTileY = 0;
    3107          35 :         CPL_IGNORE_RET_VAL(
    3108          35 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    3109             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    3110          35 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
    3111          35 :         bRet = bIntersects;
    3112             : 
    3113          35 :         if (bRet)
    3114             :         {
    3115          35 :             CPLDebug("gdal_raster_tile",
    3116             :                      "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
    3117             :                      nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
    3118             :         }
    3119             : 
    3120             :         const CPLStringList aosCreationOptions(
    3121          70 :             GetUpdatedCreationOptions(ovrTileMatrix));
    3122             : 
    3123          70 :         PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
    3124          35 :         std::atomic<bool> bFailure = false;
    3125          35 :         std::atomic<int> nQueuedJobs = 0;
    3126             : 
    3127          35 :         const bool bUseThreads =
    3128          52 :             m_numThreads > 1 &&
    3129          17 :             (nOvrMaxTileY > nOvrMinTileY || nOvrMaxTileX > nOvrMinTileX);
    3130             : 
    3131          78 :         for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
    3132             :         {
    3133         114 :             for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
    3134             :             {
    3135          71 :                 if (bUseThreads)
    3136             :                 {
    3137          40 :                     auto job = [this, &oResourceManager, poDstDriver, &bFailure,
    3138             :                                 &nCurTile, &nQueuedJobs, pszExtension,
    3139             :                                 &aosCreationOptions, &aosWarpOptions,
    3140             :                                 &ovrTileMatrix, iZ, iX, iY,
    3141         280 :                                 bUserAskedForAlpha]()
    3142             :                     {
    3143          80 :                         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    3144             : 
    3145          40 :                         --nQueuedJobs;
    3146          80 :                         auto resources = oResourceManager.AcquireResources();
    3147          80 :                         if (resources &&
    3148          80 :                             GenerateOverviewTile(
    3149          40 :                                 *(resources->poSrcDS.get()), poDstDriver,
    3150          40 :                                 m_outputFormat, pszExtension,
    3151             :                                 aosCreationOptions.List(),
    3152          40 :                                 aosWarpOptions.List(), m_overviewResampling,
    3153          40 :                                 ovrTileMatrix, m_outputDirectory, iZ, iX, iY,
    3154          40 :                                 m_convention, m_skipBlank, bUserAskedForAlpha,
    3155          80 :                                 m_auxXML, m_resume))
    3156             :                         {
    3157          40 :                             oResourceManager.ReleaseResources(
    3158          40 :                                 std::move(resources));
    3159             :                         }
    3160             :                         else
    3161             :                         {
    3162           0 :                             oResourceManager.SetError();
    3163           0 :                             bFailure = true;
    3164             :                         }
    3165          40 :                         ++nCurTile;
    3166          80 :                     };
    3167             : 
    3168             :                     // Avoid queueing too many jobs at once
    3169          40 :                     while (bRet && nQueuedJobs / 10 > m_numThreads)
    3170             :                     {
    3171           0 :                         oThreadPool.WaitEvent();
    3172             : 
    3173           0 :                         bRet &=
    3174           0 :                             !bFailure &&
    3175           0 :                             (!pfnProgress ||
    3176           0 :                              pfnProgress(static_cast<double>(nCurTile) /
    3177           0 :                                              static_cast<double>(nTotalTiles),
    3178           0 :                                          "", pProgressData));
    3179             :                     }
    3180             : 
    3181          40 :                     ++nQueuedJobs;
    3182          40 :                     oThreadPool.SubmitJob(std::move(job));
    3183             :                 }
    3184             :                 else
    3185             :                 {
    3186          31 :                     bRet = GenerateOverviewTile(
    3187          31 :                         oSrcDS, poDstDriver, m_outputFormat, pszExtension,
    3188          31 :                         aosCreationOptions.List(), aosWarpOptions.List(),
    3189          31 :                         m_overviewResampling, ovrTileMatrix, m_outputDirectory,
    3190          31 :                         iZ, iX, iY, m_convention, m_skipBlank,
    3191          31 :                         bUserAskedForAlpha, m_auxXML, m_resume);
    3192             : 
    3193          31 :                     ++nCurTile;
    3194          32 :                     bRet &= (!pfnProgress ||
    3195           2 :                              pfnProgress(static_cast<double>(nCurTile) /
    3196           1 :                                              static_cast<double>(nTotalTiles),
    3197          31 :                                          "", pProgressData));
    3198             :                 }
    3199             :             }
    3200             :         }
    3201             : 
    3202          35 :         if (bUseThreads)
    3203             :         {
    3204             :             // Wait for completion of all jobs
    3205          30 :             while (bRet && nQueuedJobs > 0)
    3206             :             {
    3207          26 :                 oThreadPool.WaitEvent();
    3208          39 :                 bRet &= !bFailure &&
    3209          13 :                         (!pfnProgress ||
    3210          26 :                          pfnProgress(static_cast<double>(nCurTile) /
    3211          13 :                                          static_cast<double>(nTotalTiles),
    3212          26 :                                      "", pProgressData));
    3213             :             }
    3214           4 :             oThreadPool.WaitCompletion();
    3215           4 :             bRet &=
    3216           6 :                 !bFailure && (!pfnProgress ||
    3217           4 :                               pfnProgress(static_cast<double>(nCurTile) /
    3218           2 :                                               static_cast<double>(nTotalTiles),
    3219           4 :                                           "", pProgressData));
    3220             : 
    3221           4 :             if (!oResourceManager.GetErrorMsg().empty())
    3222             :             {
    3223             :                 // Re-emit error message from worker thread to main thread
    3224           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s",
    3225           0 :                             oResourceManager.GetErrorMsg().c_str());
    3226             :             }
    3227             :         }
    3228             : 
    3229          35 :         if (m_kml && bRet)
    3230             :         {
    3231           2 :             for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
    3232             :             {
    3233           2 :                 for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
    3234             :                 {
    3235             :                     int nFileY =
    3236           1 :                         GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
    3237             :                     std::string osFilename =
    3238             :                         CPLFormFilenameSafe(m_outputDirectory.c_str(),
    3239           2 :                                             CPLSPrintf("%d", iZ), nullptr);
    3240           2 :                     osFilename = CPLFormFilenameSafe(
    3241           1 :                         osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
    3242           2 :                     osFilename = CPLFormFilenameSafe(
    3243             :                         osFilename.c_str(),
    3244           1 :                         CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    3245           1 :                     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    3246             :                     {
    3247           1 :                         std::vector<TileCoordinates> children;
    3248             : 
    3249           3 :                         for (int iChildY = 0; iChildY <= 1; ++iChildY)
    3250             :                         {
    3251           6 :                             for (int iChildX = 0; iChildX <= 1; ++iChildX)
    3252             :                             {
    3253             :                                 nFileY =
    3254           4 :                                     GetFileY(iY * 2 + iChildY,
    3255           4 :                                              poTMS->tileMatrixList()[iZ + 1],
    3256           4 :                                              m_convention);
    3257           8 :                                 osFilename = CPLFormFilenameSafe(
    3258             :                                     m_outputDirectory.c_str(),
    3259           4 :                                     CPLSPrintf("%d", iZ + 1), nullptr);
    3260           8 :                                 osFilename = CPLFormFilenameSafe(
    3261             :                                     osFilename.c_str(),
    3262           4 :                                     CPLSPrintf("%d", iX * 2 + iChildX),
    3263           4 :                                     nullptr);
    3264           8 :                                 osFilename = CPLFormFilenameSafe(
    3265             :                                     osFilename.c_str(),
    3266             :                                     CPLSPrintf("%d.%s", nFileY, pszExtension),
    3267           4 :                                     nullptr);
    3268           4 :                                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    3269             :                                 {
    3270           1 :                                     TileCoordinates tc;
    3271           1 :                                     tc.nTileX = iX * 2 + iChildX;
    3272           1 :                                     tc.nTileY = iY * 2 + iChildY;
    3273           1 :                                     tc.nTileZ = iZ + 1;
    3274           1 :                                     children.push_back(std::move(tc));
    3275             :                                 }
    3276             :                             }
    3277             :                         }
    3278             : 
    3279           2 :                         GenerateKML(m_outputDirectory, m_title, iX, iY, iZ,
    3280           1 :                                     kmlTileSize, pszExtension, m_url,
    3281           1 :                                     poTMS.get(), bInvertAxisTMS, m_convention,
    3282             :                                     poCTToWGS84.get(), children);
    3283             :                     }
    3284             :                 }
    3285             :             }
    3286             :         }
    3287             :     }
    3288             : 
    3289         450 :     const auto IsWebViewerEnabled = [this](const char *name)
    3290             :     {
    3291         150 :         return std::find_if(m_webviewers.begin(), m_webviewers.end(),
    3292         253 :                             [name](const std::string &s) {
    3293         152 :                                 return s == "all" || s == name;
    3294         150 :                             }) != m_webviewers.end();
    3295          59 :     };
    3296             : 
    3297          97 :     if (bRet && poTMS->identifier() == "GoogleMapsCompatible" &&
    3298          38 :         IsWebViewerEnabled("leaflet"))
    3299             :     {
    3300          13 :         double dfSouthLat = -90;
    3301          13 :         double dfWestLon = -180;
    3302          13 :         double dfNorthLat = 90;
    3303          13 :         double dfEastLon = 180;
    3304             : 
    3305          13 :         if (poCTToWGS84)
    3306             :         {
    3307          13 :             poCTToWGS84->TransformBounds(
    3308             :                 adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
    3309          13 :                 &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
    3310             :         }
    3311             : 
    3312          13 :         GenerateLeaflet(m_outputDirectory, m_title, dfSouthLat, dfWestLon,
    3313             :                         dfNorthLat, dfEastLon, m_minZoomLevel, m_maxZoomLevel,
    3314          13 :                         tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
    3315          13 :                         m_convention == "xyz");
    3316             :     }
    3317             : 
    3318          59 :     if (bRet && IsWebViewerEnabled("openlayers"))
    3319             :     {
    3320          42 :         GenerateOpenLayers(
    3321          21 :             m_outputDirectory, m_title, adfExtent[0], adfExtent[1],
    3322             :             adfExtent[2], adfExtent[3], m_minZoomLevel, m_maxZoomLevel,
    3323          21 :             tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
    3324          21 :             *(poTMS.get()), bInvertAxisTMS, oSRS_TMS, m_convention == "xyz");
    3325             :     }
    3326             : 
    3327          75 :     if (bRet && IsWebViewerEnabled("mapml") &&
    3328         134 :         poTMS->identifier() != "raster" && m_convention == "xyz")
    3329             :     {
    3330          14 :         GenerateMapML(m_outputDirectory, m_mapmlTemplate, m_title, nMinTileX,
    3331             :                       nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
    3332          14 :                       m_maxZoomLevel, pszExtension, m_url, m_copyright,
    3333          14 :                       *(poTMS.get()));
    3334             :     }
    3335             : 
    3336          59 :     if (bRet && m_kml)
    3337             :     {
    3338           4 :         std::vector<TileCoordinates> children;
    3339             : 
    3340           2 :         auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
    3341           2 :         int nOvrMinTileX = 0;
    3342           2 :         int nOvrMinTileY = 0;
    3343           2 :         int nOvrMaxTileX = 0;
    3344           2 :         int nOvrMaxTileY = 0;
    3345           2 :         CPL_IGNORE_RET_VAL(
    3346           2 :             GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
    3347             :                            nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
    3348           2 :                            nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
    3349             : 
    3350           4 :         for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
    3351             :         {
    3352           4 :             for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
    3353             :             {
    3354           2 :                 int nFileY = GetFileY(
    3355           2 :                     iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
    3356             :                 std::string osFilename = CPLFormFilenameSafe(
    3357             :                     m_outputDirectory.c_str(), CPLSPrintf("%d", m_minZoomLevel),
    3358           4 :                     nullptr);
    3359           4 :                 osFilename = CPLFormFilenameSafe(osFilename.c_str(),
    3360           2 :                                                  CPLSPrintf("%d", iX), nullptr);
    3361           4 :                 osFilename = CPLFormFilenameSafe(
    3362             :                     osFilename.c_str(),
    3363           2 :                     CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
    3364           2 :                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    3365             :                 {
    3366           2 :                     TileCoordinates tc;
    3367           2 :                     tc.nTileX = iX;
    3368           2 :                     tc.nTileY = iY;
    3369           2 :                     tc.nTileZ = m_minZoomLevel;
    3370           2 :                     children.push_back(std::move(tc));
    3371             :                 }
    3372             :             }
    3373             :         }
    3374           4 :         GenerateKML(m_outputDirectory, m_title, -1, -1, -1, kmlTileSize,
    3375           2 :                     pszExtension, m_url, poTMS.get(), bInvertAxisTMS,
    3376           2 :                     m_convention, poCTToWGS84.get(), children);
    3377             :     }
    3378             : 
    3379          59 :     return bRet;
    3380             : }
    3381             : 
    3382             : //! @endcond

Generated by: LCOV version 1.14